diff --git a/1079_pre-release-cleanup@main/coverage-report/index.html b/1079_pre-release-cleanup@main/coverage-report/index.html index b8dcbfbcf2..43e4f54b4c 100644 --- a/1079_pre-release-cleanup@main/coverage-report/index.html +++ b/1079_pre-release-cleanup@main/coverage-report/index.html @@ -1,22 +1,21 @@ -
- + - + - - + + - + - + - - - + + + @@ -445,42 +444,48 @@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",
types = c("data.frame", "reactive", "MultiAssayExperiment")
checkmate::assert_class(join_keys, "join_keys", null.ok = TRUE)
checkmate::assert_multi_class(code, c("character", "reactive"))
checkmate::assert_list(metadata, names = "unique", null.ok = TRUE)
checkmate::assert_subset(names(metadata), names(data))
if (is.reactive(code)) {
isolate(checkmate::assert_class(code(), "character", .var.name = "code"))
for (x in names(data)) {
if (!is.reactive(data[[x]])) {
data[[x]] <- do.call(reactive, list(as.name(x)), envir = list2env(data[x]))
attr(data, "code") <- if (is.reactive(code)) code else reactive(code)
attr(data, "join_keys") <- join_keys
attr(data, "metadata") <- metadata
class(data) <- c("tdata", class(data))
data
checkmate::assert_class(data, "tdata")
list2env(lapply(data, function(x) if (is.reactive(x)) x() else x))
checkmate::assert_class(data, "tdata")
attr(data, "code")()
attr(data, "join_keys")
checkmate::assert_string(dataname)
UseMethod("get_metadata", data)
metadata <- attr(data, "metadata")
if (is.null(metadata)) {
return(NULL)
metadata[[dataname]]
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)
checkmate::assert_string(label)
checkmate::assert_list(server_args, names = "named")
checkmate::assert_true(all(names(server_args) %in% names(formals(teal.reporter::reporter_previewer_srv))))
logger::log_info("Initializing reporter_previewer_module")
srv <- function(id, reporter, ...) {
ui <- function(id, ...) {
module <- module(
label = "temporary label",
server = srv, ui = ui,
server_args = server_args, ui_args = list(), datanames = NULL
class(module) <- c("teal_module_previewer", class(module))
module$label <- label
module
moduleServer(id, function(input, output, session) {
observeEvent(input$show, {
filter_manager_srv("filter_manager", filtered_data_list, filter)
moduleServer(id, function(input, output, session) {
logger::log_trace("filter_manager_srv initializing for: { paste(names(filtered_data_list), collapse = ', ')}.")
is_module_specific <- isTRUE(attr(filter, "module_specific"))
slices_global <- reactiveVal(filter)
filtered_data_list <-
if (!is_module_specific) {
list(global_filters = unlist(filtered_data_list)[[1]])
flatten_nested <- function(x, name = NULL) {
if (inherits(x, "FilteredData")) {
setNames(list(x), name)
unlist(lapply(names(x), function(name) flatten_nested(x[[name]], name)))
flatten_nested(filtered_data_list)
mapping_matrix <- reactive({
state_ids_global <- vapply(slices_global(), `[[`, character(1L), "id")
mapping_smooth <- lapply(filtered_data_list, function(x) {
state_ids_local <- vapply(x$get_filter_state(), `[[`, character(1L), "id")
state_ids_allowed <- vapply(x$get_available_teal_slices()(), `[[`, character(1L), "id")
states_active <- state_ids_global %in% state_ids_local
ifelse(state_ids_global %in% state_ids_allowed, states_active, NA)
as.data.frame(mapping_smooth, row.names = state_ids_global, check.names = FALSE)
output$slices_table <- renderTable(
expr = {
mm <- mapping_matrix()
mm[] <- lapply(mm, ifelse, yes = intToUtf8(9989), no = intToUtf8(10060))
mm[] <- lapply(mm, function(x) ifelse(is.na(x), intToUtf8(128306), x))
if (!is_module_specific) colnames(mm) <- "Global Filters"
if (nrow(mm) == 0L) {
mm <- data.frame(`Filter manager` = "No filters specified.", check.names = FALSE)
rownames(mm) <- ""
mm[names(mm) != "Report previewer"]
align = paste(c("l", rep("c", sum(names(filtered_data_list) != "Report previewer"))), collapse = ""),
rownames = TRUE
modules_out <- lapply(names(filtered_data_list), function(module_name) {
filter_manager_module_srv(
id = module_name,
module_fd = filtered_data_list[[module_name]],
slices_global = slices_global
snapshot_manager_srv("snapshot_manager", slices_global, mapping_matrix, filtered_data_list)
modules_out # returned for testing purpose
moduleServer(id, function(input, output, session) {
module_fd$set_available_teal_slices(reactive(slices_global()))
slices_module <- reactive(module_fd$get_filter_state())
previous_slices <- reactiveVal(isolate(slices_module()))
slices_added <- reactiveVal(NULL)
observeEvent(slices_module(), ignoreNULL = FALSE, {
logger::log_trace("filter_manager_srv@1 detecting states deltas in module: { id }.")
added <- setdiff_teal_slices(slices_module(), slices_global())
previous_slices(slices_module())
observeEvent(slices_added(), ignoreNULL = TRUE, {
slices_module # returned for testing purpose
checkmate::assert_character(id, max.len = 1, any.missing = FALSE)
checkmate::assert_multi_class(data, c("teal_data", "teal_data_module"))
checkmate::assert(
.var.name = "title",
checkmate::check_string(title),
checkmate::check_multi_class(title, c("shiny.tag", "shiny.tag.list", "html"))
checkmate::assert(
.var.name = "header",
checkmate::check_string(header),
checkmate::check_multi_class(header, c("shiny.tag", "shiny.tag.list", "html"))
checkmate::assert(
.var.name = "footer",
checkmate::check_string(footer),
checkmate::check_multi_class(footer, c("shiny.tag", "shiny.tag.list", "html"))
ns <- NS(id)
splash_ui <- if (inherits(data, "teal_data_module")) {
data$ui(ns("teal_data_module"))
} else if (inherits(data, "teal_data")) {
div()
ui_teal(
id = ns("teal"),
splash_ui = div(splash_ui, uiOutput(ns("error"))),
title = title,
header = header,
footer = footer
checkmate::assert_character(id, max.len = 1, any.missing = FALSE)
checkmate::assert_multi_class(data, c("teal_data", "teal_data_module"))
checkmate::assert_class(modules, "teal_modules")
checkmate::assert_class(filter, "teal_slices")
moduleServer(id, function(input, output, session) {
logger::log_trace("srv_teal_with_splash initializing module with data.")
if (getOption("teal.show_js_log", default = FALSE)) {
teal_data_rv <- if (inherits(data, "teal_data_module")) {
data <- data$server(id = "teal_data_module")
if (!is.reactive(data)) {
stop("The `teal_data_module` passed to `data` must return a reactive expression.", call. = FALSE)
data
} else if (inherits(data, "teal_data")) {
reactiveVal(data)
teal_data_rv_validate <- reactive({
data <- tryCatch(teal_data_rv(), error = function(e) e)
if (inherits(data, "shiny.silent.error") && identical(data$message, "")) {
if (inherits(data, "qenv.error")) {
validate(
need(
FALSE,
paste(
"Error when executing `teal_data_module` passed to `data`:\n ",
paste(data$message, collapse = "\n"),
"\n Check your inputs or contact app developer if error persists."
if (inherits(data, "error")) {
validate(
need(
FALSE,
paste(
"Error when executing `teal_data_module` passed to `data`:\n ",
paste(data$message, collpase = "\n"),
"\n Check your inputs or contact app developer if error persists."
validate(
need(
inherits(data, "teal_data"),
paste(
"Error: `teal_data_module` passed to `data` failed to return `teal_data` object, returned",
toString(sQuote(class(data))),
"instead.",
"\n Check your inputs or contact app developer if error persists."
if (!length(teal.data::datanames(data))) {
warning("`data` object has no datanames. Default datanames are set using `teal_data`'s environment.")
is_modules_ok <- check_modules_datanames(modules, teal_data_datanames(data))
if (!isTRUE(is_modules_ok)) {
validate(need(isTRUE(is_modules_ok), sprintf("%s. Contact app developer.", is_modules_ok)))
is_filter_ok <- check_filter_datanames(filter, teal_data_datanames(data))
if (!isTRUE(is_filter_ok)) {
showNotification(
"Some filters were not applied because of incompatibility with data. Contact app developer.",
type = "warning",
duration = 10
warning(is_filter_ok)
teal_data_rv()
output$error <- renderUI({
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)
checkmate::assert_multi_class(modules, c("teal_modules", "teal_module"))
checkmate::assert_class(reporter, "Reporter")
UseMethod("srv_nested_tabs", modules)
checkmate::assert_list(datasets, types = c("list", "FilteredData"))
moduleServer(id = id, module = function(input, output, session) {
logger::log_trace("srv_nested_tabs.teal_modules initializing the module { deparse1(modules$label) }.")
labels <- vapply(modules$children, `[[`, character(1), "label")
modules_reactive <- sapply(
names(modules$children),
function(module_id) {
srv_nested_tabs(
id = module_id,
datasets = datasets[[labels[module_id]]],
modules = modules$children[[module_id]],
is_module_specific = is_module_specific,
reporter = reporter
simplify = FALSE
input_validated <- eventReactive(input$active_tab, input$active_tab, ignoreNULL = TRUE)
get_active_module <- reactive({
if (length(modules$children) == 1L) {
modules_reactive[[1]]()
modules_reactive[[input_validated()]]()
get_active_module
checkmate::assert_class(datasets, "FilteredData")
logger::log_trace("srv_nested_tabs.teal_module initializing the module: { deparse1(modules$label) }.")
moduleServer(id = id, module = function(input, output, session) {
if (!is.null(modules$datanames) && is_module_specific) {
trigger_data <- reactiveVal(1L)
trigger_module <- reactiveVal(NULL)
output$data_reactive <- renderUI({
lapply(datasets$datanames(), function(x) {
datasets$get_data(x, filtered = TRUE)
isolate(trigger_data(trigger_data() + 1))
isolate(trigger_module(TRUE))
NULL
args <- c(list(id = "module"), modules$server_args)
if (is_arg_used(modules$server, "reporter")) {
if (is_arg_used(modules$server, "datasets")) {
args <- c(args, datasets = datasets)
if (is_arg_used(modules$server, "data")) {
data <- eventReactive(trigger_data(), .datasets_to_data(modules, datasets))
args <- c(args, data = list(data))
if (is_arg_used(modules$server, "filter_panel_api")) {
filter_panel_api <- teal.slice::FilterPanelAPI$new(datasets)
args <- c(args, filter_panel_api = filter_panel_api)
observeEvent(
ignoreNULL = TRUE,
once = TRUE,
eventExpr = trigger_module(),
handlerExpr = {
module_output <- if (is_arg_used(modules$server, "id")) {
do.call(modules$server, args)
reactive(modules)
checkmate::assert_class(module, "teal_module")
checkmate::assert_class(datasets, "FilteredData")
datanames <- if (is.null(module$datanames) || identical(module$datanames, "all")) {
datasets$datanames()
include_parent_datanames(
module$datanames,
datasets$get_join_keys()
data <- sapply(datanames, function(x) datasets$get_data(x, filtered = TRUE), simplify = FALSE)
hashes <- calculate_hashes(datanames, datasets)
code <- c(
get_rcode_str_install(),
get_rcode_libraries(),
get_datasets_code(datanames, datasets, hashes)
data <- do.call(
teal.data::teal_data,
args = c(data, code = list(code), join_keys = list(datasets$get_join_keys()[datanames]))
data@verified <- attr(datasets, "verification_status")
data
sapply(datanames, function(x) rlang::hash(datasets$get_data(x, filtered = FALSE)), simplify = FALSE)
checkmate::assert_character(src, min.len = 0, max.len = 1)
params <- list(...)
params$eval <- FALSE
rblock <- RcodeBlock$new(src)
rblock$set_params(params)
self$append_content(rblock)
self$append_metadata("SRC", src)
invisible(self)
checkmate::assert_class(fs, "teal_slices")
self$append_text("Filter State", "header3")
self$append_content(TealSlicesBlock$new(fs))
invisible(self)
checkmate::assert_list(encodings)
self$append_text("Selected Options", "header3")
if (requireNamespace("yaml", quietly = TRUE)) {
self$append_text(yaml::as.yaml(encodings, handlers = list(
POSIXct = function(x) format(x, "%Y-%m-%d"),
POSIXlt = function(x) format(x, "%Y-%m-%d"),
Date = function(x) format(x, "%Y-%m-%d")
)), "verbatim")
self$append_metadata("Encodings", encodings)
invisible(self)
self$set_content(content)
self$set_style(style)
invisible(self)
checkmate::assert_class(content, "teal_slices")
if (length(content) != 0) {
states_list <- lapply(content, function(x) {
x_list <- shiny::isolate(as.list(x))
if (
inherits(x_list$choices, c("integer", "numeric", "Date", "POSIXct", "POSIXlt")) &&
length(x_list$choices) == 2 &&
length(x_list$selected) == 2
if (!is.null(x_list$arg)) {
x_list <- x_list[
c("dataname", "varname", "experiment", "arg", "expr", "selected", "range", "keep_na", "keep_inf")
names(x_list) <- c(
"Dataset name", "Variable name", "Experiment", "Filtering by", "Applied expression",
"Selected Values", "Selected range", "Include NA values", "Include Inf values"
Filter(Negate(is.null), x_list)
if (requireNamespace("yaml", quietly = TRUE)) {
super$set_content(yaml::as.yaml(states_list))
private$teal_slices <- content
invisible(self)
checkmate::assert_list(x)
checkmate::assert_names(names(x), must.include = c("teal_slices"))
self$set_content(x$teal_slices)
invisible(self)
list(teal_slices = private$teal_slices)
dots <- list(...)
if (!is_validators(dots)) stop("validate_inputs accepts validators or a list thereof")
messages <- extract_validator(dots, header)
failings <- if (!any_names(dots)) {
add_header(messages, header)
unlist(messages)
shiny::validate(shiny::need(is.null(failings), failings))
all(if (is.list(x)) unlist(lapply(x, is_validators)) else inherits(x, "InputValidator"))
x$.__enclos_env__$private$enabled
if (inherits(iv, "InputValidator")) {
add_header(gather_messages(iv), header)
if (is.null(names(iv))) names(iv) <- rep("", length(iv))
mapply(extract_validator, iv = iv, header = names(iv), SIMPLIFY = FALSE)
if (validator_enabled(iv)) {
status <- iv$validate()
failing_inputs <- Filter(Negate(is.null), status)
unique(lapply(failing_inputs, function(x) x[["message"]]))
warning("Validator is disabled and will be omitted.")
list()
ans <- unlist(messages)
if (length(ans) != 0L && isTRUE(nchar(header) > 0L)) {
ans <- c(paste0(header, "\n"), ans, "\n")
ans
any(
if (is.list(x)) {
if (!is.null(names(x)) && any(names(x) != "")) TRUE else unlist(lapply(x, any_names))
FALSE
checkmate::assert_class(tss, "teal_slices")
checkmate::assert_path_for_output(file, overwrite = TRUE, extension = "json")
cat(format(tss, trim_lines = FALSE), "\n", file = file)
checkmate::assert_file_exists(file, access = "r", extension = "json")
tss_json <- jsonlite::fromJSON(file, simplifyDataFrame = FALSE)
tss_json$slices <-
lapply(tss_json$slices, function(slice) {
for (field in c("selected", "choices")) {
if (!is.null(slice[[field]])) {
if (length(slice[[field]]) > 0) {
date_partial_regex <- "^[0-9]{4}-[0-9]{2}-[0-9]{2}"
time_stamp_regex <- paste0(date_partial_regex, "\\s[0-9]{2}:[0-9]{2}:[0-9]{2}\\sUTC$")
slice[[field]] <-
if (all(grepl(paste0(date_partial_regex, "$"), slice[[field]]))) {
as.Date(slice[[field]])
} else if (all(grepl(time_stamp_regex, slice[[field]]))) {
as.POSIXct(slice[[field]], tz = "UTC")
slice[[field]]
slice[[field]] <- character(0)
slice
tss_elements <- lapply(tss_json$slices, as.teal_slice)
do.call(teal_slices, c(tss_elements, tss_json$attributes))
checkmate::assert_character(id)
checkmate::assert_true(is.reactive(slices_global))
checkmate::assert_class(isolate(slices_global()), "teal_slices")
checkmate::assert_true(is.reactive(mapping_matrix))
checkmate::assert_data_frame(isolate(mapping_matrix()), null.ok = TRUE)
checkmate::assert_list(filtered_data_list, types = "FilteredData", any.missing = FALSE, names = "named")
moduleServer(id, function(input, output, session) {
ns <- session$ns
filter <- isolate(slices_global())
snapshot_history <- reactiveVal({
list(
"Initial application state" = as.list(filter, recursive = TRUE)
observeEvent(input$snapshot_add, {
observeEvent(input$snapshot_name_accept, {
observeEvent(input$snapshot_load, {
observeEvent(input$snaphot_file_accept, {
observeEvent(input$snapshot_reset, {
observers <- reactiveValues()
handlers <- reactiveValues()
divs <- reactiveValues()
observeEvent(snapshot_history(), {
lapply(names(snapshot_history())[-1L], function(s) {
output$snapshot_list <- renderUI({
rows <- lapply(rev(reactiveValuesToList(divs)), function(d) d)
if (length(rows) == 0L) {
div(
class = "snapshot_manager_placeholder",
"Snapshots will appear here."
checkmate::assert_string(msg, null.ok = TRUE)
checkmate::assert_data_frame(x)
if (!is.null(min_nrow)) {
if (complete) {
complete_index <- stats::complete.cases(x)
validate(need(
sum(complete_index) > 0 && nrow(x[complete_index, , drop = FALSE]) >= min_nrow,
paste(c(paste("Number of complete cases is less than:", min_nrow), msg), collapse = "\n")
validate(need(
nrow(x) >= min_nrow,
paste(
c(paste("Minimum number of records not met: >=", min_nrow, "records required."), msg),
collapse = "\n"
if (!allow_inf) {
validate(need(
all(vapply(x, function(col) !is.numeric(col) || !any(is.infinite(col)), logical(1))),
"Dataframe contains Inf values which is not allowed."
checkmate::assert_string(label)
submodules <- list(...)
if (any(vapply(submodules, is.character, FUN.VALUE = logical(1)))) {
stop(
"The only character argument to modules() must be 'label' and it must be named, ",
"change modules('lab', ...) to modules(label = 'lab', ...)"
checkmate::assert_list(submodules, min.len = 1, any.missing = FALSE, types = c("teal_module", "teal_modules"))
labels <- vapply(submodules, function(submodule) submodule$label, character(1))
names(submodules) <- make.unique(gsub("[^[:alnum:]]+", "_", labels), sep = "_")
structure(
list(
label = label,
children = submodules
class = "teal_modules"
checkmate::assert_class(modules, "teal_modules")
checkmate::assert_class(module, "teal_module")
modules$children <- c(modules$children, list(module))
labels <- vapply(modules$children, function(submodule) submodule$label, character(1))
names(modules$children) <- make.unique(gsub("[^[:alnum:]]", "_", tolower(labels)), sep = "_")
modules
if (inherits(modules, class)) {
} else if (inherits(modules, "teal_module")) {
NULL
} else if (inherits(modules, "teal_modules")) {
Filter(function(x) length(x) > 0L, lapply(modules$children, extract_module, class))
checkmate::assert_string(arg)
if (inherits(modules, "teal_modules")) {
any(unlist(lapply(modules$children, is_arg_used, arg)))
} else if (inherits(modules, "teal_module")) {
is_arg_used(modules$server, arg) || is_arg_used(modules$ui, arg)
} else if (is.function(modules)) {
isTRUE(arg %in% names(formals(modules)))
stop("is_arg_used function not implemented for this object")
checkmate::assert_string(label)
if (label == "global_filters") {
stop(
sprintf("module(label = \"%s\", ...\n ", label),
"Label 'global_filters' is reserved in teal. Please change to something else.",
call. = FALSE
if (label == "Report previewer") {
checkmate::assert_function(server)
server_formals <- names(formals(server))
if (!(
"id" %in% server_formals ||
all(c("input", "output", "session") %in% server_formals)
stop(
"\nmodule() `server` argument requires a function with following arguments:",
"\n - id - `teal` will set proper `shiny` namespace for this module.",
"\n - input, output, session (not recommended) - then `shiny::callModule` will be used to call a module.",
"\n\nFollowing arguments can be used optionaly:",
"\n - `data` - module will receive list of reactive (filtered) data specified in the `filters` argument",
"\n - `datasets` - module will receive `FilteredData`. See `help(teal.slice::FilteredData)`",
"\n - `reporter` - module will receive `Reporter`. See `help(teal.reporter::Reporter)`",
"\n - `filter_panel_api` - module will receive `FilterPanelAPI`. (See [teal.slice::FilterPanelAPI]).",
"\n - `...` server_args elements will be passed to the module named argument or to the `...`"
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
checkmate::assert_function(ui)
ui_formals <- names(formals(ui))
if (!"id" %in% ui_formals) {
stop(
"\nmodule() `ui` argument requires a function with following arguments:",
"\n - id - `teal` will set proper `shiny` namespace for this module.",
"\n\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."
if (!missing(filters)) {
if (!is.element("data", server_formals) && !is.null(datanames)) {
message(sprintf("module \"%s\" server function takes no data so \"datanames\" will be ignored", label))
datanames <- NULL
checkmate::assert_character(datanames, min.len = 1, null.ok = TRUE, any.missing = FALSE)
checkmate::assert_list(server_args, null.ok = TRUE, names = "named")
srv_extra_args <- setdiff(names(server_args), server_formals)
if (length(srv_extra_args) > 0 && !"..." %in% server_formals) {
stop(
"\nFollowing `server_args` elements have no equivalent in the formals of the server:\n",
paste(paste(" -", srv_extra_args), collapse = "\n"),
"\n\nUpdate the server arguments by including above or add `...`"
checkmate::assert_list(ui_args, null.ok = TRUE, names = "named")
ui_extra_args <- setdiff(names(ui_args), ui_formals)
if (length(ui_extra_args) > 0 && !"..." %in% ui_formals) {
stop(
"\nFollowing `ui_args` elements have no equivalent in the formals of UI:\n",
paste(paste(" -", ui_extra_args), collapse = "\n"),
"\n\nUpdate the UI arguments by including above or add `...`"
structure(
list(
label = label,
server = server, ui = ui, datanames = unique(datanames),
server_args = server_args, ui_args = ui_args
class = "teal_module"
checkmate::assert_multi_class(modules, c("teal_module", "teal_modules"))
checkmate::assert_int(depth, lower = 0)
if (inherits(modules, "teal_modules")) {
max(vapply(modules$children, modules_depth, integer(1), depth = depth + 1L))
depth
paste(c(
paste0(rep(" ", indent), "+ ", x$label),
unlist(lapply(x$children, format, indent = indent + 1, ...))
), collapse = "\n")
paste0(paste(rep(" ", indent), collapse = ""), "+ ", x$label, collapse = "")
script <- sprintf(
"Shiny.setInputValue(`%s`, Intl.DateTimeFormat().resolvedOptions().timeZone)",
ns("timezone")
shinyjs::runjs(script) # function does not return anything
return(invisible(NULL))
bs_theme <- getOption("teal.bs_theme")
if (is.null(bs_theme)) {
NULL
} else if (!inherits(bs_theme, "bs_theme")) {
warning("teal.bs_theme has to be of a bslib::bs_theme class, the default shiny bootstrap is used.")
NULL
bs_theme
parents <- character(0)
for (i in dataname) {
while (length(i) > 0) {
parent_i <- teal.data::parent(join_keys, i)
parents <- c(parent_i, parents)
i <- parent_i
unique(c(parents, dataname))
checkmate::assert_class(x, "teal_data")
checkmate::assert_character(datanames, min.chars = 1L, any.missing = FALSE)
ans <- teal.slice::init_filtered_data(
x = sapply(datanames, function(dn) x[[dn]], simplify = FALSE),
join_keys = teal.data::join_keys(x)
attr(ans, "preprocessing_code") <- teal.code::get_code(x)
attr(ans, "verification_status") <- x@verified
ans
checkmate::assert_string(title)
checkmate::assert_string(label)
checkmate::assert_string(description, null.ok = TRUE)
checkmate::assert_flag(with_filter)
checkmate::assert_class(filter_panel_api, classes = "FilterPanelAPI")
card <- teal::TealReportCard$new()
title <- if (label == "") title else label
card$set_name(title)
card$append_text(title, "header2")
if (!is.null(description)) card$append_text(description, "header3")
if (with_filter) card$append_fs(filter_panel_api$get_filter_state())
card
checkmate::assert_class(modules, "teal_modules")
checkmate::assert_character(datanames)
recursive_check_datanames <- function(modules, datanames) {
if (inherits(modules, "teal_modules")) {
sapply(modules$children, function(module) recursive_check_datanames(module, datanames = datanames))
extra_datanames <- setdiff(modules$datanames, c("all", datanames))
if (length(extra_datanames)) {
sprintf(
"- Module '%s' uses datanames not available in 'data': (%s) not in (%s)",
modules$label,
toString(dQuote(extra_datanames, q = FALSE)),
toString(dQuote(datanames, q = FALSE))
check_datanames <- unlist(recursive_check_datanames(modules, datanames))
if (length(check_datanames)) {
paste(check_datanames, collapse = "\n")
TRUE
checkmate::assert_class(filters, "teal_slices")
checkmate::assert_character(datanames)
out <- unlist(sapply(
filters, function(filter) {
dataname <- shiny::isolate(filter$dataname)
if (!dataname %in% datanames) {
sprintf(
"- Filter '%s' refers to dataname not available in 'data':\n %s not in (%s)",
shiny::isolate(filter$id),
dQuote(dataname, q = FALSE),
toString(dQuote(datanames, q = FALSE))
if (length(out)) {
paste(out, collapse = "\n")
TRUE
checkmate::assert_class(data, "teal_data")
if (length(teal.data::datanames(data))) {
teal.data::datanames(data)
ls(teal.code::get_env(data), all.names = TRUE)
checkmate::assert_class(shiny_tag, "shiny.tag")
checkmate::assert_true(shiny_tag$name == "head")
child_names <- vapply(shiny_tag$children, `[[`, character(1L), "name")
checkmate::assert_subset(c("title", "link"), child_names, .var.name = "child tags")
rel_attr <- shiny_tag$children[[which(child_names == "link")]]$attribs$rel
checkmate::assert_subset(
rel_attr,
c("icon", "shortcut icon"),
.var.name = "Link tag's rel attribute",
empty.ok = FALSE
checkmate::assert_string(title, null.ok = TRUE)
checkmate::assert_string(favicon, null.ok = TRUE)
tags$head(
tags$title(title),
tags$link(
rel = "icon",
href = favicon,
sizes = "any"
checkmate::assert_multi_class(data, c("teal_data", "teal_data_module"))
checkmate::assert_class(modules, "teal_modules")
data <- if (inherits(data, "teal_data")) {
as.list(data@env)
} else if (inherits(data, "teal_data_module")) {
body(data$server)
rlang::hash(list(data = data, modules = modules))
css_files <- list.files(
system.file("css", package = "teal", mustWork = TRUE),
pattern = pattern, full.names = TRUE
return(
shiny::singleton(
shiny::tags$head(lapply(css_files, shiny::includeCSS))
checkmate::assert_character(except, min.len = 1, any.missing = FALSE, null.ok = TRUE)
js_files <- list.files(system.file("js", package = "teal", mustWork = TRUE), pattern = pattern, full.names = TRUE)
js_files <- js_files[!(basename(js_files) %in% except)] # no-op if except is NULL
return(singleton(lapply(js_files, includeScript)))
checkmate::assert_character(files, min.len = 1, any.missing = FALSE)
lapply(files, function(file) {
shinyjs::runjs(paste0(readLines(system.file("js", file, package = "teal", mustWork = TRUE)), collapse = "\n"))
return(invisible(NULL))
tagList(
shinyjs::useShinyjs(),
include_css_files(),
include_js_files(except = "init.js"),
shinyjs::hidden(icon("gear")), # add hidden icon to load font-awesome css for icons
checkmate::assert_character(id, max.len = 1, any.missing = FALSE)
checkmate::assert_multi_class(splash_ui, c("shiny.tag", "shiny.tag.list", "html"))
if (is.character(title)) {
validate_app_title_tag(title)
checkmate::assert(
.var.name = "header",
checkmate::check_string(header),
checkmate::check_multi_class(header, c("shiny.tag", "shiny.tag.list", "html"))
if (checkmate::test_string(header)) {
checkmate::assert(
.var.name = "footer",
checkmate::check_string(footer),
checkmate::check_multi_class(footer, c("shiny.tag", "shiny.tag.list", "html"))
if (checkmate::test_string(footer)) {
ns <- NS(id)
splash_ui <- div(
id = ns("main_ui_container"),
div(splash_ui)
shiny_busy_message_panel <- conditionalPanel(
condition = "(($('html').hasClass('shiny-busy')) && (document.getElementById('shiny-notification-panel') == null))", # nolint
div(
icon("arrows-rotate", "spin fa-spin"),
"Computing ...",
class = "shinybusymessage"
res <- fluidPage(
title = title,
theme = get_teal_bs_theme(),
include_teal_css_js(),
tags$header(header),
tags$hr(class = "my-2"),
shiny_busy_message_panel,
splash_ui,
tags$hr(),
tags$footer(
div(
footer,
teal.widgets::verbatim_popup_ui(ns("sessionInfo"), "Session Info", type = "link"),
textOutput(ns("identifier"))
return(res)
stopifnot(is.reactive(teal_data_rv))
moduleServer(id, function(input, output, session) {
logger::log_trace("srv_teal initializing the module.")
output$identifier <- renderText(
paste0("Pid:", Sys.getpid(), " Token:", substr(session$token, 25, 32))
teal.widgets::verbatim_popup_srv(
"sessionInfo",
verbatim_content = utils::capture.output(utils::sessionInfo()),
title = "SessionInfo"
run_js_files(files = "init.js")
get_client_timezone(session$ns)
observeEvent(
eventExpr = input$timezone,
once = TRUE,
handlerExpr = {
reporter <- teal.reporter::Reporter$new()
if (is_arg_used(modules, "reporter") && length(extract_module(modules, "teal_module_previewer")) == 0) {
env <- environment()
datasets_reactive <- eventReactive(teal_data_rv(), {
env$progress <- shiny::Progress$new(session)
env$progress$set(0.25, message = "Setting data")
datasets_singleton <- teal_data_to_filtered_data(teal_data_rv())
filter_global <- Filter(function(x) x$id %in% attr(filter, "mapping")$global_filters, filter)
datasets_singleton$set_filter_state(filter_global)
module_datasets <- function(modules) {
if (inherits(modules, "teal_modules")) {
datasets <- lapply(modules$children, module_datasets)
labels <- vapply(modules$children, `[[`, character(1), "label")
names(datasets) <- labels
datasets
} else if (isTRUE(attr(filter, "module_specific"))) {
datanames <- if (is.null(modules$datanames) || modules$datanames == "all") {
include_parent_datanames(
teal_data_datanames(teal_data_rv()),
teal.data::join_keys(teal_data_rv())
datasets_module <- teal_data_to_filtered_data(teal_data_rv(), datanames = datanames)
slices <- Filter(x = filter, f = function(x) {
include_varnames <- attr(slices, "include_varnames")[names(attr(slices, "include_varnames")) %in% datanames]
exclude_varnames <- attr(slices, "exclude_varnames")[names(attr(slices, "exclude_varnames")) %in% datanames]
slices$include_varnames <- include_varnames
slices$exclude_varnames <- exclude_varnames
datasets_module$set_filter_state(slices)
datasets_module
datasets_singleton
module_datasets(modules)
observeEvent(datasets_reactive(), once = TRUE, {
logger::log_trace("init initializing teal app with: data ('{ class(data) }').")
if (inherits(data, "TealData")) {
checkmate::assert_multi_class(data, c("teal_data", "teal_data_module"))
checkmate::assert(
.var.name = "modules",
checkmate::check_multi_class(modules, c("teal_modules", "teal_module")),
checkmate::check_list(modules, min.len = 1, any.missing = FALSE, types = c("teal_module", "teal_modules"))
if (inherits(modules, "teal_module")) {
modules <- list(modules)
if (checkmate::test_list(modules, min.len = 1, any.missing = FALSE, types = c("teal_module", "teal_modules"))) {
modules <- do.call(teal::modules, modules)
checkmate::assert_class(filter, "teal_slices")
checkmate::assert(
.var.name = "title",
checkmate::check_string(title),
checkmate::check_multi_class(title, c("shiny.tag", "shiny.tag.list", "html"))
checkmate::assert(
.var.name = "header",
checkmate::check_string(header),
checkmate::check_multi_class(header, c("shiny.tag", "shiny.tag.list", "html"))
checkmate::assert(
.var.name = "footer",
checkmate::check_string(footer),
checkmate::check_multi_class(footer, c("shiny.tag", "shiny.tag.list", "html"))
checkmate::assert_character(id, max.len = 1, any.missing = FALSE)
teal.logger::log_system_info()
landing <- extract_module(modules, "teal_module_landing")
landing_module <- NULL
if (length(landing) == 1L) {
} else if (length(landing) > 1L) {
attr(filter, "app_id") <- create_app_id(data, modules)
filter <- as.teal_slices(as.list(filter))
if (isTRUE(attr(filter, "module_specific"))) {
if (inherits(data, "teal_data")) {
if (length(teal_data_datanames(data)) == 0) {
stop("The environment of `data` is empty.")
is_modules_ok <- check_modules_datanames(modules, teal_data_datanames(data))
if (!isTRUE(is_modules_ok)) {
logger::log_error(is_modules_ok)
checkmate::assert(is_modules_ok, .var.name = "modules")
is_filter_ok <- check_filter_datanames(filter, teal_data_datanames(data))
if (!isTRUE(is_filter_ok)) {
warning(is_filter_ok)
res <- list(
ui = ui_teal_with_splash(id = id, data = data, title = title, header = header, footer = footer),
server = function(input, output, session) {
logger::log_trace("init teal app has been initialized.")
return(res)
checkmate::assert_function(ui, args = "id", nargs = 1)
checkmate::assert_function(server, args = "id", nargs = 1)
structure(
list(ui = ui, server = server),
class = "teal_data_module"
teal_data_module(
ui = function(id) {
ns <- NS(id)
object$ui(ns("mutate_inner"))
server = function(id) {
moduleServer(id, function(input, output, session) {
teal_data_rv <- object$server("mutate_inner")
if (!is.reactive(teal_data_rv)) {
stop("The `teal_data_module` must return a reactive expression.", call. = FALSE)
td <- eventReactive(teal_data_rv(),
if (inherits(teal_data_rv(), c("teal_data", "qenv.error"))) {
eval_code(teal_data_rv(), code)
teal_data_rv()
ignoreNULL = FALSE
td
eval_code(object, code = paste(lang2calls(code), collapse = "\n"))
eval_code(object, code = paste(lang2calls(code), collapse = "\n"))
packageStartupMessage(
"\nYou are using teal version ",
read.dcf(system.file("DESCRIPTION", package = "teal"))[, "Version"]
checkmate::assert_class(modules, "teal_modules")
checkmate::assert_list(datasets, types = c("list", "FilteredData"))
checkmate::assert_class(reporter, "Reporter")
checkmate::assert_class(filter, "teal_slices")
moduleServer(id, function(input, output, session) {
logger::log_trace("srv_tabs_with_filters initializing the module.")
is_module_specific <- isTRUE(attr(filter, "module_specific"))
manager_out <- filter_manager_modal_srv("filter_manager", filtered_data_list = datasets, filter = filter)
active_module <- srv_nested_tabs(
id = "root",
datasets = datasets,
modules = modules,
reporter = reporter,
is_module_specific = is_module_specific
if (!is_module_specific) {
active_datanames <- reactive({
if (identical(active_module()$datanames, "all")) {
include_parent_datanames(
active_module()$datanames,
singleton$get_join_keys()
singleton <- unlist(datasets)[[1]]
singleton$srv_filter_panel("filter_panel", active_datanames = active_datanames)
observeEvent(
eventExpr = active_datanames(),
handlerExpr = {
script <- if (length(active_datanames()) == 0 || is.null(active_datanames())) {
"handleActiveDatasetsPresent();"
shinyjs::runjs(script)
ignoreNULL = FALSE
showNotification("Data loaded - App fully started up")
logger::log_trace("srv_tabs_with_filters initialized the module")
return(active_module)
checkmate::assert_string(label)
module(
label,
server = function(id, data) {
ui = function(id) {
datanames = datanames
shiny::isolate({
checkmate::assert_flag(allow_add)
checkmate::assert_flag(module_specific)
if (!missing(mapping)) checkmate::assert_list(mapping, types = c("character", "NULL"), names = "named")
checkmate::assert_string(app_id, null.ok = TRUE)
slices <- list(...)
all_slice_id <- vapply(slices, `[[`, character(1L), "id")
if (missing(mapping)) {
mapping <- list(global_filters = all_slice_id)
if (!module_specific) {
mapping[setdiff(names(mapping), "global_filters")] <- NULL
failed_slice_id <- setdiff(unlist(mapping), all_slice_id)
if (length(failed_slice_id)) {
stop(sprintf(
"Filters in mapping don't match any available filter.\n %s not in %s",
toString(failed_slice_id),
toString(all_slice_id)
tss <- teal.slice::teal_slices(
exclude_varnames = exclude_varnames,
include_varnames = include_varnames,
count_type = count_type,
allow_add = allow_add
attr(tss, "mapping") <- mapping
attr(tss, "module_specific") <- module_specific
attr(tss, "app_id") <- app_id
class(tss) <- c("modules_teal_slices", class(tss))
tss
checkmate::assert_list(x)
lapply(x, checkmate::assert_list, names = "named", .var.name = "list element")
attrs <- attributes(unclass(x))
ans <- lapply(x, function(x) if (is.teal_slice(x)) x else as.teal_slice(x))
do.call(teal_slices, c(ans, attrs))
checkmate::assert_class(filter, "teal_slices")
shiny::isolate({
filter_copy <- lapply(filter, function(slice) {
teal.slice::as.teal_slice(as.list(slice))
attributes(filter_copy) <- attributes(filter)
filter_copy
vapply(
utils::sessionInfo()$otherPkgs,
function(x) {
paste0("library(", x$Package, ")")
character(1)
rev() %>%
paste0(sep = "\n") %>%
paste0(collapse = "")
code_string <- getOption("teal.load_nest_code")
if (!is.null(code_string) && is.character(code_string)) {
return(code_string)
return("# Add any code to install/load your NEST environment here\n")
str_prepro <-
teal.data:::get_code_dependency(attr(datasets, "preprocessing_code"), names = datanames, check_names = FALSE)
if (length(str_prepro) == 0) {
str_prepro <- paste(str_prepro, collapse = "\n")
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")
str_filter <- teal.slice::get_filter_expr(datasets, datanames)
if (str_filter == "") {
str_filter <- character(0)
str_code <- paste(c(str_prepro, str_hash, str_filter), collapse = "\n\n")
sprintf("%s\n", str_code)
expr <- substitute(expr)
extras <- list(...)
if (!identical(as.list(expr)[[1L]], as.symbol("{"))) {
expr <- call("{", expr)
calls <- as.list(expr)[-1]
calls <- lapply(calls, function(x) do.call(substitute, list(x, env = extras)))
eval_code(object = data, code = as.expression(calls))
shiny::isolate({
checkmate::assert_flag(allow_add)
checkmate::assert_flag(module_specific)
if (!missing(mapping)) checkmate::assert_list(mapping, types = c("character", "NULL"), names = "named")
slices <- list(...)
all_slice_id <- vapply(slices, `[[`, character(1L), "id")
if (missing(mapping)) {
mapping <- list(global_filters = all_slice_id)
if (!module_specific) {
mapping[setdiff(names(mapping), "global_filters")] <- NULL
failed_slice_id <- setdiff(unlist(mapping), all_slice_id)
if (length(failed_slice_id)) {
stop(sprintf(
"Filters in mapping don't match any available filter.\n %s not in %s",
toString(failed_slice_id),
toString(all_slice_id)
tss <- teal.slice::teal_slices(
exclude_varnames = exclude_varnames,
include_varnames = include_varnames,
count_type = count_type,
allow_add = allow_add
attr(tss, "mapping") <- mapping
attr(tss, "module_specific") <- module_specific
class(tss) <- c("modules_teal_slices", class(tss))
tss
checkmate::assert_list(x)
lapply(x, checkmate::assert_list, names = "named", .var.name = "list element")
attrs <- attributes(unclass(x))
ans <- lapply(x, function(x) if (is.teal_slice(x)) x else as.teal_slice(x))
do.call(teal_slices, c(ans, attrs))
checkmate::assert_class(filter, "teal_slices")
shiny::isolate({
filter_copy <- lapply(filter, function(slice) {
teal.slice::as.teal_slice(as.list(slice))
attributes(filter_copy) <- attributes(filter)
filter_copy
if (checkmate::test_string(header)) {
if (checkmate::test_string(footer)) {
checkmate::assert(
checkmate::check_class(splash_ui, "shiny.tag"),
checkmate::check_class(splash_ui, "shiny.tag.list"),
checkmate::check_class(splash_ui, "html")
checkmate::assert(
checkmate::check_class(header, "shiny.tag"),
checkmate::check_class(header, "shiny.tag.list"),
checkmate::check_class(header, "html")
checkmate::assert(
checkmate::check_class(footer, "shiny.tag"),
checkmate::check_class(footer, "shiny.tag.list"),
checkmate::check_class(footer, "html")
ns <- NS(id)
splash_ui <- div(
id = ns("main_ui_container"),
div(splash_ui)
shiny_busy_message_panel <- conditionalPanel(
condition = "(($('html').hasClass('shiny-busy')) && (document.getElementById('shiny-notification-panel') == null))", # nolint
div(
icon("arrows-rotate", "spin fa-spin"),
"Computing ...",
class = "shinybusymessage"
res <- fluidPage(
title = title,
theme = get_teal_bs_theme(),
include_teal_css_js(),
tags$header(header),
tags$hr(class = "my-2"),
shiny_busy_message_panel,
splash_ui,
tags$hr(),
tags$footer(
div(
footer,
teal.widgets::verbatim_popup_ui(ns("sessionInfo"), "Session Info", type = "link"),
textOutput(ns("identifier"))
return(res)
stopifnot(is.reactive(raw_data))
moduleServer(id, function(input, output, session) {
logger::log_trace("srv_teal initializing the module.")
output$identifier <- renderText(
paste0("Pid:", Sys.getpid(), " Token:", substr(session$token, 25, 32))
teal.widgets::verbatim_popup_srv(
"sessionInfo",
verbatim_content = utils::capture.output(utils::sessionInfo()),
title = "SessionInfo"
run_js_files(files = "init.js") # `JavaScript` code to make the clipboard accessible
get_client_timezone(session$ns)
observeEvent(
eventExpr = input$timezone,
once = TRUE,
handlerExpr = {
env <- environment()
datasets_reactive <- reactive({
if (is.null(raw_data())) {
return(NULL)
env$progress <- shiny::Progress$new(session)
env$progress$set(0.25, message = "Setting data")
datasets_singleton <- teal.slice::init_filtered_data(raw_data())
filter_global <- Filter(function(x) x$id %in% attr(filter, "mapping")$global_filters, filter)
datasets_singleton$set_filter_state(filter_global)
module_datasets <- function(modules) {
if (inherits(modules, "teal_modules")) {
datasets <- lapply(modules$children, module_datasets)
labels <- vapply(modules$children, `[[`, character(1), "label")
names(datasets) <- labels
datasets
} else if (isTRUE(attr(filter, "module_specific"))) {
datanames <- if (is.null(modules$datanames)) raw_data()$get_datanames() else modules$datanames
data_objects <- sapply(
datanames,
function(dataname) {
dataset <- raw_data()$get_dataset(dataname)
list(
dataset = dataset$get_raw_data(),
metadata = dataset$get_metadata(),
label = dataset$get_dataset_label()
simplify = FALSE
datasets_module <- teal.slice::init_filtered_data(
data_objects,
join_keys = raw_data()$get_join_keys(),
code = raw_data()$get_code_class(),
check = raw_data()$get_check()
slices <- Filter(x = filter, f = function(x) {
include_varnames <- attr(slices, "include_varnames")[names(attr(slices, "include_varnames")) %in% datanames]
exclude_varnames <- attr(slices, "exclude_varnames")[names(attr(slices, "exclude_varnames")) %in% datanames]
slices$include_varnames <- include_varnames
slices$exclude_varnames <- exclude_varnames
datasets_module$set_filter_state(slices)
datasets_module
datasets_singleton
datasets <- module_datasets(modules)
logger::log_trace("srv_teal@4 Raw Data transferred to FilteredData.")
datasets
reporter <- teal.reporter::Reporter$new()
is_any_previewer <- function(modules) {
if (is_arg_used(modules, "reporter") && !is_any_previewer(modules)) {
observeEvent(datasets_reactive(), ignoreNULL = TRUE, once = TRUE, {
logger::log_trace("srv_teal@5 setting main ui after data was pulled")
env$progress$set(0.5, message = "Setting up main UI")
on.exit(env$progress$close())
removeUI(sprintf("#%s:first-child", session$ns("main_ui_container")))
insertUI(
selector = paste0("#", session$ns("main_ui_container")),
where = "beforeEnd",
ui = div(ui_tabs_with_filters(
session$ns("main_ui"),
modules = modules,
datasets = datasets_reactive(),
filter = filter
immediate = TRUE
active_module <- srv_tabs_with_filters(
id = "main_ui",
datasets = datasets_reactive(),
modules = modules,
reporter = reporter,
filter = filter
return(active_module)
checkmate::assert_list(
data,
any.missing = FALSE, names = "unique",
types = c("data.frame", "reactive", "MultiAssayExperiment")
checkmate::assert_class(join_keys, "JoinKeys", null.ok = TRUE)
checkmate::assert_multi_class(code, c("character", "reactive"))
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"))
for (x in names(data)) {
if (!is.reactive(data[[x]])) {
data[[x]] <- do.call(reactive, list(as.name(x)), envir = list2env(data[x]))
isolate(
checkmate::assert_multi_class(
data[[x]](), c("data.frame", "MultiAssayExperiment"),
.var.name = "data"
attr(data, "code") <- if (is.reactive(code)) code else reactive(code)
attr(data, "join_keys") <- join_keys
attr(data, "metadata") <- metadata
class(data) <- c("tdata", class(data))
data
checkmate::assert_class(data, "tdata")
list2env(lapply(data, function(x) if (is.reactive(x)) x() else x))
attr(x, "code")()
checkmate::assert_class(data, "tdata")
get_code(data)
UseMethod("get_join_keys", data)
attr(data, "join_keys")
checkmate::assert_string(dataname)
UseMethod("get_metadata", data)
metadata <- attr(data, "metadata")
if (is.null(metadata)) {
return(NULL)
metadata[[dataname]]
css_files <- list.files(
system.file("css", package = "teal", mustWork = TRUE),
pattern = pattern, full.names = TRUE
return(
shiny::singleton(
shiny::tags$head(lapply(css_files, shiny::includeCSS))
checkmate::assert_character(except, min.len = 1, any.missing = FALSE, null.ok = TRUE)
js_files <- list.files(system.file("js", package = "teal", mustWork = TRUE), pattern = pattern, full.names = TRUE)
js_files <- js_files[!(basename(js_files) %in% except)] # no-op if except is NULL
return(singleton(lapply(js_files, includeScript)))
checkmate::assert_character(files, min.len = 1, any.missing = FALSE)
lapply(files, function(file) {
shinyjs::runjs(paste0(readLines(system.file("js", file, package = "teal", mustWork = TRUE)), collapse = "\n"))
return(invisible(NULL))
tagList(
shinyjs::useShinyjs(),
include_css_files(),
include_js_files(except = "init.js"),
shinyjs::hidden(icon("gear")), # add hidden icon to load font-awesome css for icons
checkmate::assert_class(data, "TealDataAbstract")
is_pulled_data <- teal.data::is_pulled(data)
ns <- NS(id)
splash_ui <- if (is_pulled_data) {
div()
message("App was initialized with delayed data loading.")
data$get_ui(ns("startapp_module"))
ui_teal(id = ns("teal"), splash_ui = splash_ui, title = title, header = header, footer = footer)
checkmate::assert_class(data, "TealDataAbstract")
moduleServer(id, function(input, output, session) {
logger::log_trace(
"srv_teal_with_splash initializing module with data { paste(data$get_datanames(), collapse = ' ')}."
if (getOption("teal.show_js_log", default = FALSE)) {
is_pulled_data <- teal.data::is_pulled(data)
if (is_pulled_data) {
raw_data <- reactiveVal(data) # will trigger by setting it
raw_data <- data$get_server()(id = "startapp_module")
if (!is.reactive(raw_data)) {
res <- srv_teal(id = "teal", modules = modules, raw_data = raw_data, filter = filter)
logger::log_trace(
"srv_teal_with_splash initialized the module with data { paste(data$get_datanames(), collapse = ' ') }."
return(res)
checkmate::assert_character(id)
checkmate::assert_true(is.reactive(slices_global))
checkmate::assert_class(isolate(slices_global()), "teal_slices")
checkmate::assert_true(is.reactive(mapping_matrix))
checkmate::assert_data_frame(isolate(mapping_matrix()), null.ok = TRUE)
checkmate::assert_list(filtered_data_list, types = "FilteredData", any.missing = FALSE, names = "named")
moduleServer(id, function(input, output, session) {
ns <- session$ns
filter <- isolate(slices_global())
snapshot_history <- reactiveVal({
list(
"Initial application state" = as.list(filter, recursive = TRUE)
observeEvent(input$snapshot_add, {
observeEvent(input$snapshot_name_accept, {
observeEvent(input$snapshot_reset, {
observers <- reactiveValues()
handlers <- reactiveValues()
divs <- reactiveValues()
observeEvent(snapshot_history(), {
lapply(names(snapshot_history())[-1L], function(s) {
output$snapshot_list <- renderUI({
rows <- lapply(rev(reactiveValuesToList(divs)), function(d) d)
if (length(rows) == 0L) {
div(
class = "snapshot_manager_placeholder",
"Snapshots will appear here."
checkmate::assert_multi_class(modules, c("teal_modules", "teal_module"))
checkmate::assert_count(depth)
UseMethod("ui_nested_tabs", modules)
checkmate::assert_list(datasets, types = c("list", "FilteredData"))
ns <- NS(id)
do.call(
tabsetPanel,
c(
list(
id = ns("active_tab"),
type = if (modules$label == "root") "pills" else "tabs"
lapply(
names(modules$children),
function(module_id) {
module_label <- modules$children[[module_id]]$label
tabPanel(
title = module_label,
value = module_id, # when clicked this tab value changes input$<tabset panel id>
ui_nested_tabs(
id = ns(module_id),
modules = modules$children[[module_id]],
datasets = datasets[[module_label]],
depth = depth + 1L,
is_module_specific = is_module_specific
checkmate::assert_class(datasets, class = "FilteredData")
ns <- NS(id)
args <- isolate(teal.transform::resolve_delayed(modules$ui_args, datasets))
args <- c(list(id = ns("module")), args)
if (is_arg_used(modules$ui, "datasets")) {
if (is_arg_used(modules$ui, "data")) {
teal_ui <- tags$div(
id = id,
class = "teal_module",
uiOutput(ns("data_reactive"), inline = TRUE),
tagList(
if (depth >= 2L) div(style = "mt-6"),
do.call(modules$ui, args)
if (!is.null(modules$datanames) && is_module_specific) {
teal_ui
checkmate::assert_multi_class(modules, c("teal_modules", "teal_module"))
checkmate::assert_class(reporter, "Reporter")
UseMethod("srv_nested_tabs", modules)
checkmate::assert_list(datasets, types = c("list", "FilteredData"))
moduleServer(id = id, module = function(input, output, session) {
logger::log_trace("srv_nested_tabs.teal_modules initializing the module { deparse1(modules$label) }.")
labels <- vapply(modules$children, `[[`, character(1), "label")
modules_reactive <- sapply(
names(modules$children),
function(module_id) {
srv_nested_tabs(
id = module_id,
datasets = datasets[[labels[module_id]]],
modules = modules$children[[module_id]],
is_module_specific = is_module_specific,
reporter = reporter
simplify = FALSE
input_validated <- eventReactive(input$active_tab, input$active_tab, ignoreNULL = TRUE)
get_active_module <- reactive({
if (length(modules$children) == 1L) {
modules_reactive[[1]]()
modules_reactive[[input_validated()]]()
get_active_module
checkmate::assert_class(datasets, "FilteredData")
logger::log_trace("srv_nested_tabs.teal_module initializing the module: { deparse1(modules$label) }.")
moduleServer(id = id, module = function(input, output, session) {
modules$server_args <- teal.transform::resolve_delayed(modules$server_args, datasets)
if (!is.null(modules$datanames) && is_module_specific) {
trigger_data <- reactiveVal(1L)
trigger_module <- reactiveVal(NULL)
output$data_reactive <- renderUI({
lapply(datasets$datanames(), function(x) {
datasets$get_data(x, filtered = TRUE)
isolate(trigger_data(trigger_data() + 1))
isolate(trigger_module(TRUE))
NULL
args <- c(list(id = "module"), modules$server_args)
if (is_arg_used(modules$server, "reporter")) {
if (is_arg_used(modules$server, "datasets")) {
args <- c(args, datasets = datasets)
if (is_arg_used(modules$server, "data")) {
data <- .datasets_to_data(modules, datasets, trigger_data)
args <- c(args, data = list(data))
if (is_arg_used(modules$server, "filter_panel_api")) {
filter_panel_api <- teal.slice::FilterPanelAPI$new(datasets)
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."
observeEvent(
ignoreNULL = TRUE,
once = TRUE,
eventExpr = trigger_module(),
handlerExpr = {
module_output <- if (is_arg_used(modules$server, "id")) {
do.call(modules$server, args)
reactive(modules)
checkmate::assert_class(module, "teal_module")
checkmate::assert_class(datasets, "FilteredData")
checkmate::assert_class(trigger_data, "reactiveVal")
datanames <- if (is.null(module$datanames)) datasets$datanames() else module$datanames
data <- sapply(
datanames,
function(x) eventReactive(trigger_data(), datasets$get_data(x, filtered = TRUE)),
simplify = FALSE
hashes <- calculate_hashes(datanames, datasets)
metadata <- lapply(datanames, datasets$get_metadata)
names(metadata) <- datanames
new_tdata(
data,
eventReactive(
trigger_data(),
c(
get_rcode_str_install(),
get_rcode_libraries(),
get_datasets_code(datanames, datasets, hashes),
teal.slice::get_filter_expr(datasets, datanames)
datasets$get_join_keys(),
metadata
sapply(datanames, function(x) rlang::hash(datasets$get_data(x, filtered = FALSE)), simplify = FALSE)
checkmate::assert_class(modules, "teal_modules")
checkmate::assert_list(datasets, types = c("list", "FilteredData"))
checkmate::assert_class(filter, "teal_slices")
ns <- NS(id)
is_module_specific <- isTRUE(attr(filter, "module_specific"))
teal_ui <- ui_nested_tabs(ns("root"), modules = modules, datasets, is_module_specific = is_module_specific)
filter_panel_btns <- tags$li(
class = "flex-grow",
tags$button(
class = "btn action-button filter_hamburger", # see sidebar.css for style filter_hamburger
href = "javascript:void(0)",
onclick = "toggleFilterPanel();", # see sidebar.js
title = "Toggle filter panels",
icon("fas fa-bars")
filter_manager_modal_ui(ns("filter_manager"))
teal_ui$children[[1]] <- tagAppendChild(teal_ui$children[[1]], filter_panel_btns)
if (!is_module_specific) {
tabset_bar <- teal_ui$children[[1]]
teal_modules <- teal_ui$children[[2]]
filter_ui <- unlist(datasets)[[1]]$ui_filter_panel(ns("filter_panel"))
list(
tabset_bar,
tags$hr(class = "my-2"),
fluidRow(
column(width = 9, teal_modules, class = "teal_primary_col"),
column(width = 3, filter_ui, class = "teal_secondary_col")
checkmate::assert_class(modules, "teal_modules")
checkmate::assert_list(datasets, types = c("list", "FilteredData"))
checkmate::assert_class(reporter, "Reporter")
checkmate::assert_class(filter, "teal_slices")
moduleServer(id, function(input, output, session) {
logger::log_trace("srv_tabs_with_filters initializing the module.")
is_module_specific <- isTRUE(attr(filter, "module_specific"))
manager_out <- filter_manager_modal_srv("filter_manager", filtered_data_list = datasets, filter = filter)
active_module <- srv_nested_tabs(
id = "root",
datasets = datasets,
modules = modules,
reporter = reporter,
is_module_specific = is_module_specific
if (!is_module_specific) {
active_datanames <- reactive(active_module()$datanames)
singleton <- unlist(datasets)[[1]]
singleton$srv_filter_panel("filter_panel", active_datanames = active_datanames)
observeEvent(
eventExpr = active_datanames(),
handlerExpr = {
script <- if (length(active_datanames()) == 0 || is.null(active_datanames())) {
"handleNoActiveDatasets();"
"handleActiveDatasetsPresent();"
shinyjs::runjs(script)
ignoreNULL = FALSE
showNotification("Data loaded - App fully started up")
logger::log_trace("srv_tabs_with_filters initialized the module")
return(active_module)
checkmate::assert_string(label)
submodules <- list(...)
if (any(vapply(submodules, is.character, FUN.VALUE = logical(1)))) {
stop(
"The only character argument to modules() must be 'label' and it must be named, ",
"change modules('lab', ...) to modules(label = 'lab', ...)"
checkmate::assert_list(submodules, min.len = 1, any.missing = FALSE, types = c("teal_module", "teal_modules"))
labels <- vapply(submodules, function(submodule) submodule$label, character(1))
names(submodules) <- make.unique(gsub("[^[:alnum:]]+", "_", labels), sep = "_")
structure(
list(
label = label,
children = submodules
class = "teal_modules"
checkmate::assert_class(modules, "teal_modules")
checkmate::assert_class(module, "teal_module")
modules$children <- c(modules$children, list(module))
labels <- vapply(modules$children, function(submodule) submodule$label, character(1))
names(modules$children) <- make.unique(gsub("[^[:alnum:]]", "_", tolower(labels)), sep = "_")
modules
checkmate::assert_string(arg)
if (inherits(modules, "teal_modules")) {
any(unlist(lapply(modules$children, is_arg_used, arg)))
} else if (inherits(modules, "teal_module")) {
is_arg_used(modules$server, arg) || is_arg_used(modules$ui, arg)
} else if (is.function(modules)) {
isTRUE(arg %in% names(formals(modules)))
stop("is_arg_used function not implemented for this object")
moduleServer(id, function(input, output, session) {}) # nolint
tags$p(paste0("This module has no UI (id: ", id, " )"))
checkmate::assert_string(label)
checkmate::assert_function(server)
checkmate::assert_function(ui)
checkmate::assert_character(datanames, min.len = 1, null.ok = TRUE, any.missing = FALSE)
checkmate::assert_list(server_args, null.ok = TRUE, names = "named")
checkmate::assert_list(ui_args, null.ok = TRUE, names = "named")
if (!missing(filters)) {
if (label == "global_filters") {
stop(
sprintf("module(label = \"%s\", ...\n ", label),
"Label 'global_filters' is reserved in teal. Please change to something else.",
call. = FALSE
if (label == "Report previewer") {
server_formals <- names(formals(server))
if (!(
"id" %in% server_formals ||
all(c("input", "output", "session") %in% server_formals)
stop(
"\nmodule() `server` argument requires a function with following arguments:",
"\n - id - teal will set proper shiny namespace for this module.",
"\n - input, output, session (not recommended) - then shiny::callModule will be used to call a module.",
"\n\nFollowing arguments can be used optionaly:",
"\n - `data` - module will receive list of reactive (filtered) data specified in the `filters` argument",
"\n - `datasets` - module will receive `FilteredData`. See `help(teal.slice::FilteredData)`",
"\n - `reporter` - module will receive `Reporter`. See `help(teal.reporter::Reporter)`",
"\n - `filter_panel_api` - module will receive `FilterPanelAPI`. (See [teal.slice::FilterPanelAPI]).",
"\n - `...` server_args elements will be passed to the module named argument or to the `...`"
if (!is.element("data", server_formals) && !is.null(datanames)) {
message(sprintf("module \"%s\" server function takes no data so \"datanames\" will be ignored", label))
datanames <- NULL
srv_extra_args <- setdiff(names(server_args), server_formals)
if (length(srv_extra_args) > 0 && !"..." %in% server_formals) {
stop(
"\nFollowing `server_args` elements have no equivalent in the formals of the `server`:\n",
paste(paste(" -", srv_extra_args), collapse = "\n"),
"\n\nUpdate the `server` arguments by including above or add `...`"
ui_formals <- names(formals(ui))
if (!"id" %in% ui_formals) {
stop(
"\nmodule() `ui` argument requires a function with following arguments:",
"\n - id - teal will set proper shiny namespace for this module.",
"\n\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 - `...` ui_args elements will be passed to the module argument of the same name or to the `...`"
ui_extra_args <- setdiff(names(ui_args), ui_formals)
if (length(ui_extra_args) > 0 && !"..." %in% ui_formals) {
stop(
"\nFollowing `ui_args` elements have no equivalent in the formals of `ui`:\n",
paste(paste(" -", ui_extra_args), collapse = "\n"),
"\n\nUpdate the `ui` arguments by including above or add `...`"
structure(
list(
label = label,
server = server, ui = ui, datanames = datanames,
server_args = server_args, ui_args = ui_args
class = "teal_module"
checkmate::assert(
checkmate::check_class(modules, "teal_module"),
checkmate::check_class(modules, "teal_modules")
checkmate::assert_int(depth, lower = 0)
if (inherits(modules, "teal_modules")) {
max(vapply(modules$children, modules_depth, integer(1), depth = depth + 1L))
depth
ns <- NS(id)
tags$button(
id = ns("show"),
class = "btn action-button filter_manager_button",
title = "Show filters manager modal",
icon("gear")
moduleServer(id, function(input, output, session) {
observeEvent(input$show, {
filter_manager_srv("filter_manager", filtered_data_list, filter)
moduleServer(id, function(input, output, session) {
logger::log_trace("filter_manager_srv initializing for: { paste(names(filtered_data_list), collapse = ', ')}.")
is_module_specific <- isTRUE(attr(filter, "module_specific"))
slices_global <- reactiveVal(filter)
filtered_data_list <-
if (!is_module_specific) {
list(global_filters = unlist(filtered_data_list)[[1]])
flatten_nested <- function(x, name = NULL) {
if (inherits(x, "FilteredData")) {
setNames(list(x), name)
unlist(lapply(names(x), function(name) flatten_nested(x[[name]], name)))
flatten_nested(filtered_data_list)
mapping_matrix <- reactive({
state_ids_global <- vapply(slices_global(), `[[`, character(1L), "id")
mapping_smooth <- lapply(filtered_data_list, function(x) {
state_ids_local <- vapply(x$get_filter_state(), `[[`, character(1L), "id")
state_ids_allowed <- vapply(x$get_available_teal_slices()(), `[[`, character(1L), "id")
states_active <- state_ids_global %in% state_ids_local
ifelse(state_ids_global %in% state_ids_allowed, states_active, NA)
as.data.frame(mapping_smooth, row.names = state_ids_global, check.names = FALSE)
output$slices_table <- renderTable(
expr = {
mm <- mapping_matrix()
mm[] <- lapply(mm, ifelse, yes = intToUtf8(9989), no = intToUtf8(10060))
mm[] <- lapply(mm, function(x) ifelse(is.na(x), intToUtf8(128306), x))
if (!is_module_specific) colnames(mm) <- "Global Filters"
if (nrow(mm) == 0L) {
mm <- data.frame(`Filter manager` = "No filters specified.", check.names = FALSE)
rownames(mm) <- ""
mm[names(mm) != "Report previewer"]
align = paste(c("l", rep("c", sum(names(filtered_data_list) != "Report previewer"))), collapse = ""),
rownames = TRUE
modules_out <- lapply(names(filtered_data_list), function(module_name) {
filter_manager_module_srv(
id = module_name,
module_fd = filtered_data_list[[module_name]],
slices_global = slices_global
snapshot_manager_srv("snapshot_manager", slices_global, mapping_matrix, filtered_data_list)
modules_out # returned for testing purpose
moduleServer(id, function(input, output, session) {
module_fd$set_available_teal_slices(reactive(slices_global()))
slices_module <- reactive(module_fd$get_filter_state())
previous_slices <- reactiveVal(isolate(slices_module()))
slices_added <- reactiveVal(NULL)
observeEvent(slices_module(), ignoreNULL = FALSE, {
logger::log_trace("filter_manager_srv@1 detecting states deltas in module: { id }.")
added <- setdiff_teal_slices(slices_module(), slices_global())
previous_slices(slices_module())
observeEvent(slices_added(), ignoreNULL = TRUE, {
slices_module # returned for testing purpose
logger::log_trace("init initializing teal app with: data ({ class(data)[1] }).")
data <- teal.data::to_relational_data(data = data)
checkmate::assert_class(data, "TealData")
checkmate::assert_multi_class(modules, c("teal_module", "list", "teal_modules"))
checkmate::assert_string(title, null.ok = TRUE)
checkmate::assert(
checkmate::check_class(filter, "teal_slices"),
checkmate::check_list(filter, names = "named")
checkmate::assert_multi_class(header, c("shiny.tag", "character"))
checkmate::assert_multi_class(footer, c("shiny.tag", "character"))
checkmate::assert_character(id, max.len = 1, any.missing = FALSE)
teal.logger::log_system_info()
if (inherits(modules, "teal_module")) {
modules <- list(modules)
if (inherits(modules, "list")) {
modules <- do.call(teal::modules, modules)
datanames <- teal.data::get_dataname(data)
join_keys <- data$get_join_keys()
resolve_modules_datanames <- function(modules) {
if (inherits(modules, "teal_modules")) {
modules$children <- sapply(modules$children, resolve_modules_datanames, simplify = FALSE)
modules
modules$datanames <- if (identical(modules$datanames, "all")) {
datanames
} else if (is.character(modules$datanames)) {
datanames_adjusted <- intersect(modules$datanames, datanames)
include_parent_datanames(dataname = datanames_adjusted, join_keys = join_keys)
modules
modules <- resolve_modules_datanames(modules = modules)
if (!inherits(filter, "teal_slices")) {
checkmate::assert_subset(names(filter), choices = datanames)
filter <- list_to_teal_slices(filter)
filter <- as.teal_slices(as.list(filter))
for (i in seq_along(filter)) {
dataname_i <- shiny::isolate(filter[[i]]$dataname)
if (!dataname_i %in% datanames) {
if (isTRUE(attr(filter, "module_specific"))) {
res <- list(
ui = ui_teal_with_splash(id = id, data = data, title = title, header = header, footer = footer),
server = function(input, output, session) {
logger::log_trace("init teal app has been initialized.")
return(res)
dots <- list(...)
if (!is_validators(dots)) stop("validate_inputs accepts validators or a list thereof")
messages <- extract_validator(dots, header)
failings <- if (!any_names(dots)) {
add_header(messages, header)
unlist(messages)
shiny::validate(shiny::need(is.null(failings), failings))
all(if (is.list(x)) unlist(lapply(x, is_validators)) else inherits(x, "InputValidator"))
x$.__enclos_env__$private$enabled
if (inherits(iv, "InputValidator")) {
add_header(gather_messages(iv), header)
if (is.null(names(iv))) names(iv) <- rep("", length(iv))
mapply(extract_validator, iv = iv, header = names(iv), SIMPLIFY = FALSE)
if (validator_enabled(iv)) {
status <- iv$validate()
failing_inputs <- Filter(Negate(is.null), status)
unique(lapply(failing_inputs, function(x) x[["message"]]))
logger::log_warn("Validator is disabled and will be omitted.")
list()
ans <- unlist(messages)
if (length(ans) != 0L && isTRUE(nchar(header) > 0L)) {
ans <- c(paste0(header, "\n"), ans, "\n")
ans
any(
if (is.list(x)) {
if (!is.null(names(x)) && any(names(x) != "")) TRUE else unlist(lapply(x, any_names))
FALSE
checkmate::assert_string(label)
module(
label,
server = function(id, data) {
ui = function(id, data) {
datanames = datanames
checkmate::assert_subset(datanames, c("ADSL", "ADTTE"))
mods <- modules(
label = "d1",
modules(
label = "d2",
modules(
label = "d3",
example_module(label = "aaa1", datanames = datanames),
example_module(label = "aaa2", datanames = datanames),
example_module(label = "aaa3", datanames = datanames)
example_module(label = "bbb", datanames = datanames)
example_module(label = "ccc", datanames = datanames)
return(mods)
vapply(
utils::sessionInfo()$otherPkgs,
function(x) {
paste0("library(", x$Package, ")")
character(1)
rev() %>%
paste0(sep = "\n") %>%
paste0(collapse = "")
code_string <- getOption("teal.load_nest_code")
if (!is.null(code_string) && is.character(code_string)) {
return(code_string)
return("# Add any code to install/load your NEST environment here\n")
str_code <- datasets$get_code(datanames)
if (length(str_code) == 0 || (length(str_code) == 1 && str_code == "")) {
} else if (length(str_code) > 0) {
str_code <- paste0(str_code, "\n\n")
if (!datasets$get_check()) {
check_note_string <- paste0(
c(
"message(paste(\"Reproducibility of data import and preprocessing was not explicitly checked\",",
" \" ('check = FALSE' is set). Contact app developer if this is an issue.\n\"))"
collapse = "\n"
str_code <- paste0(str_code, "\n\n", check_note_string)
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"
c(str_code, str_hash)
checkmate::assert_string(label)
checkmate::assert_list(server_args, names = "named")
checkmate::assert_true(all(names(server_args) %in% names(formals(teal.reporter::reporter_previewer_srv))))
srv <- function(id, reporter, ...) {
ui <- function(id, ...) {
module <- module(
label = "temporary label",
server = srv, ui = ui,
server_args = server_args, ui_args = list(), datanames = NULL
class(module) <- c("teal_module_previewer", class(module))
module$label <- label
module
checkmate::assert_character(src, min.len = 0, max.len = 1)
params <- list(...)
params$eval <- FALSE
rblock <- RcodeBlock$new(src)
rblock$set_params(params)
self$append_content(rblock)
self$append_metadata("SRC", src)
invisible(self)
checkmate::assert_class(fs, "teal_slices")
self$append_text("Filter State", "header3")
self$append_content(TealSlicesBlock$new(fs))
invisible(self)
checkmate::assert_list(encodings)
self$append_text("Selected Options", "header3")
if (requireNamespace("yaml", quietly = TRUE)) {
self$append_text(yaml::as.yaml(encodings, handlers = list(
POSIXct = function(x) format(x, "%Y-%m-%d"),
POSIXlt = function(x) format(x, "%Y-%m-%d"),
Date = function(x) format(x, "%Y-%m-%d")
)), "verbatim")
self$append_metadata("Encodings", encodings)
invisible(self)
self$set_content(content)
self$set_style(style)
invisible(self)
checkmate::assert_class(content, "teal_slices")
if (length(content) != 0) {
states_list <- lapply(content, function(x) {
x_list <- shiny::isolate(as.list(x))
if (
inherits(x_list$choices, c("integer", "numeric", "Date", "POSIXct", "POSIXlt")) &&
length(x_list$choices) == 2 &&
length(x_list$selected) == 2
if (!is.null(x_list$arg)) {
x_list <- x_list[
c("dataname", "varname", "experiment", "arg", "expr", "selected", "range", "keep_na", "keep_inf")
names(x_list) <- c(
"Dataset name", "Variable name", "Experiment", "Filtering by", "Applied expression",
"Selected Values", "Selected range", "Include NA values", "Include Inf values"
Filter(Negate(is.null), x_list)
if (requireNamespace("yaml", quietly = TRUE)) {
super$set_content(yaml::as.yaml(states_list))
private$teal_slices <- content
invisible(self)
checkmate::assert_list(x)
checkmate::assert_names(names(x), must.include = c("teal_slices"))
self$set_content(x$teal_slices)
invisible(self)
list(teal_slices = private$teal_slices)
stopifnot(
"Please provide a character vector in msg argument of validate_has_data." = is.character(msg) || is.null(msg)
validate(need(!is.null(x) && is.data.frame(x), "No data left."))
if (!is.null(min_nrow)) {
if (complete) {
complete_index <- stats::complete.cases(x)
validate(need(
sum(complete_index) > 0 && nrow(x[complete_index, , drop = FALSE]) >= min_nrow,
paste(c(paste("Number of complete cases is less than:", min_nrow), msg), collapse = "\n")
validate(need(
nrow(x) >= min_nrow,
paste(
c(paste("Minimum number of records not met: >=", min_nrow, "records required."), msg),
collapse = "\n"
if (!allow_inf) {
validate(need(
all(vapply(x, function(col) !is.numeric(col) || !any(is.infinite(col)), logical(1))),
"Dataframe contains Inf values which is not allowed."
script <- sprintf(
"Shiny.setInputValue(`%s`, Intl.DateTimeFormat().resolvedOptions().timeZone)",
ns("timezone")
shinyjs::runjs(script) # function does not return anything
return(invisible(NULL))
bs_theme <- getOption("teal.bs_theme")
if (is.null(bs_theme)) {
NULL
} else if (!inherits(bs_theme, "bs_theme")) {
warning("teal.bs_theme has to be of a bslib::bs_theme class, the default shiny bootstrap is used.")
NULL
bs_theme
parents <- character(0)
for (i in dataname) {
while (length(i) > 0) {
parent_i <- join_keys$get_parent(i)
parents <- c(parent_i, parents)
i <- parent_i
return(unique(c(parents, dataname)))
checkmate::assert_string(title)
checkmate::assert_string(label)
checkmate::assert_string(description, null.ok = TRUE)
checkmate::assert_flag(with_filter)
checkmate::assert_class(filter_panel_api, classes = "FilterPanelAPI")
card <- teal::TealReportCard$new()
title <- if (label == "") title else label
card$set_name(title)
card$append_text(title, "header2")
if (!is.null(description)) card$append_text(description, "header3")
if (with_filter) card$append_fs(filter_panel_api$get_filter_state())
card
packageStartupMessage(
"\nYou are using teal version ",
read.dcf(system.file("DESCRIPTION", package = "teal"))[, "Version"]
checkmate::assert_character(id, max.len = 1, any.missing = FALSE)
checkmate::assert_multi_class(data, c("teal_data", "teal_data_module"))
checkmate::assert(
.var.name = "title",
checkmate::check_string(title),
checkmate::check_multi_class(title, c("shiny.tag", "shiny.tag.list", "html"))
checkmate::assert(
.var.name = "header",
checkmate::check_string(header),
checkmate::check_multi_class(header, c("shiny.tag", "shiny.tag.list", "html"))
checkmate::assert(
.var.name = "footer",
checkmate::check_string(footer),
checkmate::check_multi_class(footer, c("shiny.tag", "shiny.tag.list", "html"))
ns <- NS(id)
splash_ui <- if (inherits(data, "teal_data_module")) {
} else if (inherits(data, "teal_data")) {
tags$div()
ui_teal(
id = ns("teal"),
splash_ui = tags$div(splash_ui, uiOutput(ns("error"))),
title = title,
header = header,
footer = footer
checkmate::assert_character(id, max.len = 1, any.missing = FALSE)
checkmate::assert_multi_class(data, c("teal_data", "teal_data_module"))
checkmate::assert_class(modules, "teal_modules")
checkmate::assert_class(filter, "teal_slices")
moduleServer(id, function(input, output, session) {
logger::log_trace("srv_teal_with_splash initializing module with data.")
if (getOption("teal.show_js_log", default = FALSE)) {
teal_data_rv <- if (inherits(data, "teal_data_module")) {
data <- data$server(id = "teal_data_module")
if (!is.reactive(data)) {
stop("The `teal_data_module` passed to `data` must return a reactive expression.", call. = FALSE)
data
} else if (inherits(data, "teal_data")) {
reactiveVal(data)
teal_data_rv_validate <- reactive({
data <- tryCatch(teal_data_rv(), error = function(e) e)
if (inherits(data, "shiny.silent.error") && identical(data$message, "")) {
if (inherits(data, "qenv.error")) {
validate(
need(
FALSE,
paste(
"Error when executing `teal_data_module` passed to `data`:\n ",
paste(data$message, collapse = "\n"),
"\n Check your inputs or contact app developer if error persists."
if (inherits(data, "error")) {
validate(
need(
FALSE,
paste(
"Error when executing `teal_data_module` passed to `data`:\n ",
paste(data$message, collpase = "\n"),
"\n Check your inputs or contact app developer if error persists."
validate(
need(
inherits(data, "teal_data"),
paste(
"Error: `teal_data_module` passed to `data` failed to return `teal_data` object, returned",
toString(sQuote(class(data))),
"instead.",
"\n Check your inputs or contact app developer if error persists."
if (!length(teal.data::datanames(data))) {
warning("`data` object has no datanames. Default datanames are set using `teal_data`'s environment.")
is_modules_ok <- check_modules_datanames(modules, teal_data_datanames(data))
if (!isTRUE(is_modules_ok)) {
validate(need(isTRUE(is_modules_ok), sprintf("%s. Contact app developer.", is_modules_ok)))
is_filter_ok <- check_filter_datanames(filter, teal_data_datanames(data))
if (!isTRUE(is_filter_ok)) {
showNotification(
"Some filters were not applied because of incompatibility with data. Contact app developer.",
type = "warning",
duration = 10
warning(is_filter_ok)
teal_data_rv()
output$error <- renderUI({
teal_data_rv_validate()
NULL
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.")
res
ns <- NS(id)
tags$div(
class = "manager_content",
tags$div(
class = "manager_table_row",
tags$span(tags$b("Snapshot manager")),
actionLink(ns("snapshot_add"), label = NULL, icon = icon("camera"), title = "add snapshot"),
actionLink(ns("snapshot_load"), label = NULL, icon = icon("upload"), title = "upload snapshot"),
actionLink(ns("snapshot_reset"), label = NULL, icon = icon("undo"), title = "reset initial state"),
NULL
uiOutput(ns("snapshot_list"))
checkmate::assert_character(id)
checkmate::assert_true(is.reactive(slices_global))
checkmate::assert_class(isolate(slices_global()), "teal_slices")
checkmate::assert_true(is.reactive(mapping_matrix))
checkmate::assert_data_frame(isolate(mapping_matrix()), null.ok = TRUE)
checkmate::assert_list(datasets, types = "FilteredData", any.missing = FALSE, names = "named")
moduleServer(id, function(input, output, session) {
logger::log_trace("snapshot_manager_srv initializing")
ns <- session$ns
filter <- isolate(slices_global())
snapshot_history <- reactiveVal({
list(
"Initial application state" = as.list(filter, recursive = TRUE)
observeEvent(input$snapshot_add, {
logger::log_trace("snapshot_manager_srv: snapshot_add button clicked")
showModal(
modalDialog(
textInput(ns("snapshot_name"), "Name the snapshot", width = "100%", placeholder = "Meaningful, unique name"),
footer = tagList(
actionButton(ns("snapshot_name_accept"), "Accept", icon = icon("thumbs-up")),
modalButton(label = "Cancel", icon = icon("thumbs-down"))
size = "s"
observeEvent(input$snapshot_name_accept, {
logger::log_trace("snapshot_manager_srv: snapshot_name_accept button clicked")
snapshot_name <- trimws(input$snapshot_name)
if (identical(snapshot_name, "")) {
} else if (is.element(make.names(snapshot_name), make.names(names(snapshot_history())))) {
logger::log_trace("snapshot_manager_srv: snapshot name accepted, adding snapshot")
snapshot <- as.list(slices_global(), recursive = TRUE)
attr(snapshot, "mapping") <- matrix_to_mapping(mapping_matrix())
snapshot_update <- c(snapshot_history(), list(snapshot))
names(snapshot_update)[length(snapshot_update)] <- snapshot_name
snapshot_history(snapshot_update)
removeModal()
shinyjs::click(id = "teal-main_ui-wunder_bar-show_snapshot_manager", asis = TRUE)
observeEvent(input$snapshot_load, {
logger::log_trace("snapshot_manager_srv: snapshot_load button clicked")
showModal(
modalDialog(
fileInput(ns("snapshot_file"), "Choose snapshot file", accept = ".json", width = "100%"),
textInput(
ns("snapshot_name"),
"Name the snapshot (optional)",
width = "100%",
placeholder = "Meaningful, unique name"
footer = tagList(
actionButton(ns("snaphot_file_accept"), "Accept", icon = icon("thumbs-up")),
modalButton(label = "Cancel", icon = icon("thumbs-down"))
observeEvent(input$snaphot_file_accept, {
logger::log_trace("snapshot_manager_srv: snapshot_file_accept button clicked")
snapshot_name <- trimws(input$snapshot_name)
if (identical(snapshot_name, "")) {
logger::log_trace("snapshot_manager_srv: no snapshot name provided, naming after file")
snapshot_name <- tools::file_path_sans_ext(input$snapshot_file$name)
if (is.element(make.names(snapshot_name), make.names(names(snapshot_history())))) {
logger::log_trace("snapshot_manager_srv: snapshot name accepted, loading snapshot")
snapshot_state <- try(slices_restore(input$snapshot_file$datapath))
if (!inherits(snapshot_state, "modules_teal_slices")) {
} else if (!identical(attr(snapshot_state, "app_id"), attr(slices_global(), "app_id"))) {
logger::log_trace("snapshot_manager_srv: snapshot loaded, adding to history")
snapshot <- as.list(snapshot_state, recursive = TRUE)
snapshot_update <- c(snapshot_history(), list(snapshot))
names(snapshot_update)[length(snapshot_update)] <- snapshot_name
snapshot_history(snapshot_update)
logger::log_trace("snapshot_manager_srv: restoring snapshot")
mapping_unfolded <- unfold_mapping(attr(snapshot_state, "mapping"), names(datasets))
mapply(
function(filtered_data, filter_ids) {
filtered_data$clear_filter_states(force = TRUE)
slices <- Filter(function(x) x$id %in% filter_ids, snapshot_state)
filtered_data$set_filter_state(slices)
filtered_data = datasets,
filter_ids = mapping_unfolded
slices_global(snapshot_state)
removeModal()
observeEvent(input$snapshot_reset, {
logger::log_trace("snapshot_manager_srv: snapshot_reset button clicked, restoring snapshot")
s <- "Initial application state"
snapshot <- snapshot_history()[[s]]
snapshot_state <- as.teal_slices(snapshot)
mapping_unfolded <- unfold_mapping(attr(snapshot_state, "mapping"), names(datasets))
mapply(
function(filtered_data, filter_ids) {
filtered_data$clear_filter_states(force = TRUE)
slices <- Filter(function(x) x$id %in% filter_ids, snapshot_state)
filtered_data$set_filter_state(slices)
filtered_data = datasets,
filter_ids = mapping_unfolded
slices_global(snapshot_state)
removeModal()
observers <- reactiveValues()
handlers <- reactiveValues()
divs <- reactiveValues()
observeEvent(snapshot_history(), {
logger::log_trace("snapshot_manager_srv: snapshot history modified, updating snapshot list")
lapply(names(snapshot_history())[-1L], function(s) {
id_pickme <- sprintf("pickme_%s", make.names(s))
id_saveme <- sprintf("saveme_%s", make.names(s))
id_rowme <- sprintf("rowme_%s", make.names(s))
if (!is.element(id_pickme, names(observers))) {
observers[[id_pickme]] <- observeEvent(input[[id_pickme]], {
if (!is.element(id_saveme, names(handlers))) {
output[[id_saveme]] <- downloadHandler(
filename = function() {
sprintf("teal_snapshot_%s_%s.json", s, Sys.Date())
content = function(file) {
snapshot <- snapshot_history()[[s]]
snapshot_state <- as.teal_slices(snapshot)
slices_store(tss = snapshot_state, file = file)
handlers[[id_saveme]] <- id_saveme
if (!is.element(id_rowme, names(divs))) {
divs[[id_rowme]] <- tags$div(
class = "manager_table_row",
tags$span(tags$h5(s)),
actionLink(inputId = ns(id_pickme), label = icon("circle-check"), title = "select"),
downloadLink(outputId = ns(id_saveme), label = icon("save"), title = "save to file")
output$snapshot_list <- renderUI({
rows <- lapply(rev(reactiveValuesToList(divs)), function(d) d)
if (length(rows) == 0L) {
tags$div(
class = "manager_placeholder",
"Snapshots will appear here."
rows
snapshot_history
module_names <- structure(module_names, names = module_names)
lapply(module_names, function(x) c(mapping[[x]], mapping[["global_filters"]]))
mapping_matrix[] <- lapply(mapping_matrix, function(x) x | is.na(x))
global <- vapply(as.data.frame(t(mapping_matrix)), all, logical(1L))
global_filters <- names(global[global])
local_filters <- mapping_matrix[!rownames(mapping_matrix) %in% global_filters, ]
mapping <- c(lapply(local_filters, function(x) rownames(local_filters)[x]), list(global_filters = global_filters))
Filter(function(x) length(x) != 0L, mapping)
private$data <- data
private$modules <- modules
private$filter <- filter
app <- init(
data = data,
modules = modules,
filter = filter,
title = title,
header = header,
footer = footer
suppressWarnings(
super$initialize(
shinyApp(app$ui, app$server),
name = "teal",
variant = platform_variant(),
private$set_active_ns()
do.call(
self$set_inputs,
c(setNames(list(value), input_id), list(...))
invisible(self)
if (identical(private$ns$filter_panel, character(0))) {
private$ns$filter_panel
checkmate::assert_string(id, null.ok = TRUE)
full_id <- "wunder_bar"
if (!is.null(id)) full_id <- shiny::NS(full_id, id = id)
shiny::NS(private$base_ns, id = full_id)
displayed_datasets_index <- unlist(
self$get_js(
sprintf(
"Array.from(
document.querySelectorAll(\"#%s-active-filter_active_vars_contents > span\")
).map((el) => window.getComputedStyle(el).display != \"none\");",
self$active_filters_ns()
available_datasets <- self$get_text(
sprintf(
"#%s-active-filter_active_vars_contents .filter_panel_dataname",
self$active_filters_ns()
available_datasets[displayed_datasets_index]
checkmate::check_string(dataset_name, null.ok = TRUE)
datasets <- self$get_active_filter_vars()
checkmate::assert_subset(dataset_name, datasets)
active_filters <- lapply(
datasets,
function(x) {
var_names <- self$get_text(
sprintf(
"#%s-active-%s-filters .filter-card-varname",
self$active_filters_ns(),
x
gsub(pattern = "\\s", replacement = "")
structure(
lapply(var_names, private$get_active_filter_selection, dataset_name = x),
names = var_names
names(active_filters) <- datasets
if (is.null(dataset_name)) {
active_filters[[dataset_name]]
checkmate::check_string(dataset_name)
checkmate::check_string(var_name)
self$set_input(
sprintf(
"%s-add-%s-filter-var_to_add",
self$active_filters_ns(),
dataset_name
var_name
invisible(self)
checkmate::check_string(dataset_name)
checkmate::check_string(var_name)
checkmate::check_string(input)
input_id_prefix <- sprintf(
"%s-active-%s-filter-%s_%s-inputs",
self$active_filters_ns(),
dataset_name,
dataset_name,
var_name
supported_suffix <- c("selection", "selection_manual")
slices_suffix <- supported_suffix[
match(
TRUE,
vapply(
supported_suffix,
function(suffix) {
!is.null(self$get_html(sprintf("#%s-%s", input_id_prefix, suffix)))
logical(1)
slices_input_id <- sprintf(
"%s-active-%s-filter-%s_%s-inputs-%s",
self$active_filters_ns(),
dataset_name,
dataset_name,
var_name,
slices_suffix
if (identical(slices_suffix, "selection_manual")) {
} else if (identical(slices_suffix, "selection")) {
self$set_input(slices_input_id, input)
invisible(self)
self$click(self$wunder_bar_ns("show_snapshot_manager"))
self$wait_for_idle()
invisible(self)
all_inputs <- self$get_values()$input
active_tab_inputs <- all_inputs[grepl("-active_tab$", names(all_inputs))]
tab_ns <- lapply(names(active_tab_inputs), function(name) {
gsub(
pattern = "-active_tab$",
replacement = sprintf("-%s", active_tab_inputs[[name]]),
name
unlist()
active_ns <- tab_ns[1]
if (length(tab_ns) > 1) {
private$ns$module <- sprintf("%s-%s", active_ns, "module")
component <- "filter_panel"
component_id <- sprintf("%s-%s", private$base_ns, component)
if (!is.null(self$get_html(sprintf("#%s", component_id)))) {
private$ns[[component]] <- component_id
checkmate::check_string(dataset_name)
checkmate::check_string(var_name)
input_id_prefix <- sprintf(
"%s-active-%s-filter-%s_%s-inputs",
self$active_filters_ns(),
dataset_name,
dataset_name,
var_name
supported_suffix <- c("selection", "selection_manual")
for (suffix in supported_suffix) {
if (!is.null(self$get_html(sprintf("#%s-%s", input_id_prefix, suffix)))) {
return(self$get_value(input = sprintf("%s-%s", input_id_prefix, suffix)))
NULL # If there are not any supported filters
logger::log_trace("init initializing teal app with: data ('{ class(data) }').")
if (inherits(data, "TealData")) {
checkmate::assert_multi_class(data, c("teal_data", "teal_data_module"))
checkmate::assert(
.var.name = "modules",
checkmate::check_multi_class(modules, c("teal_modules", "teal_module")),
checkmate::check_list(modules, min.len = 1, any.missing = FALSE, types = c("teal_module", "teal_modules"))
if (inherits(modules, "teal_module")) {
modules <- list(modules)
if (checkmate::test_list(modules, min.len = 1, any.missing = FALSE, types = c("teal_module", "teal_modules"))) {
modules <- do.call(teal::modules, modules)
checkmate::assert_class(filter, "teal_slices")
checkmate::assert(
.var.name = "title",
checkmate::check_string(title),
checkmate::check_multi_class(title, c("shiny.tag", "shiny.tag.list", "html"))
checkmate::assert(
.var.name = "header",
checkmate::check_string(header),
checkmate::check_multi_class(header, c("shiny.tag", "shiny.tag.list", "html"))
checkmate::assert(
.var.name = "footer",
checkmate::check_string(footer),
checkmate::check_multi_class(footer, c("shiny.tag", "shiny.tag.list", "html"))
checkmate::assert_character(id, max.len = 1, any.missing = FALSE)
teal.logger::log_system_info()
landing <- extract_module(modules, "teal_module_landing")
landing_module <- NULL
if (length(landing) == 1L) {
} else if (length(landing) > 1L) {
attr(filter, "app_id") <- create_app_id(data, modules)
filter <- as.teal_slices(as.list(filter))
if (isTRUE(attr(filter, "module_specific"))) {
if (inherits(data, "teal_data")) {
if (length(teal_data_datanames(data)) == 0) {
stop("The environment of `data` is empty.")
is_modules_ok <- check_modules_datanames(modules, teal_data_datanames(data))
if (!isTRUE(is_modules_ok)) {
logger::log_error(is_modules_ok)
checkmate::assert(is_modules_ok, .var.name = "modules")
is_filter_ok <- check_filter_datanames(filter, teal_data_datanames(data))
if (!isTRUE(is_filter_ok)) {
warning(is_filter_ok)
res <- list(
ui = function(request) ui_teal_with_splash(id = id, data = data, title = title, header = header, footer = footer),
server = function(input, output, session) {
if (!is.null(landing_module)) {
srv_teal_with_splash(id = id, data = data, modules = modules, filter = deep_copy_filter(filter))
logger::log_trace("init teal app has been initialized.")
res
lifecycle::deprecate_soft(
when = "0.15.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",
types = c("data.frame", "reactive", "MultiAssayExperiment")
checkmate::assert_class(join_keys, "join_keys", null.ok = TRUE)
checkmate::assert_multi_class(code, c("character", "reactive"))
checkmate::assert_list(metadata, names = "unique", null.ok = TRUE)
checkmate::assert_subset(names(metadata), names(data))
if (is.reactive(code)) {
isolate(checkmate::assert_class(code(), "character", .var.name = "code"))
for (x in names(data)) {
if (!is.reactive(data[[x]])) {
data[[x]] <- do.call(reactive, list(as.name(x)), envir = list2env(data[x]))
attr(data, "code") <- if (is.reactive(code)) code else reactive(code)
attr(data, "join_keys") <- join_keys
attr(data, "metadata") <- metadata
class(data) <- c("tdata", class(data))
data
checkmate::assert_class(data, "tdata")
list2env(lapply(data, function(x) if (is.reactive(x)) x() else x))
checkmate::assert_class(data, "tdata")
attr(data, "code")()
attr(data, "join_keys")
checkmate::assert_string(dataname)
UseMethod("get_metadata", data)
metadata <- attr(data, "metadata")
if (is.null(metadata)) {
return(NULL)
metadata[[dataname]]
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)
checkmate::assert_string(label)
if (label == "global_filters") {
stop(
sprintf("module(label = \"%s\", ...\n ", label),
"Label 'global_filters' is reserved in teal. Please change to something else.",
call. = FALSE
if (label == "Report previewer") {
checkmate::assert_function(server)
server_formals <- names(formals(server))
if (!(
"id" %in% server_formals ||
all(c("input", "output", "session") %in% server_formals)
stop(
"\nmodule() `server` argument requires a function with following arguments:",
"\n - id - `teal` will set proper `shiny` namespace for this module.",
"\n - input, output, session (not recommended) - then `shiny::callModule` will be used to call a module.",
"\n\nFollowing arguments can be used optionaly:",
"\n - `data` - module will receive list of reactive (filtered) data specified in the `filters` argument",
"\n - `datasets` - module will receive `FilteredData`. See `help(teal.slice::FilteredData)`",
"\n - `reporter` - module will receive `Reporter`. See `help(teal.reporter::Reporter)`",
"\n - `filter_panel_api` - module will receive `FilterPanelAPI`. (See [teal.slice::FilterPanelAPI]).",
"\n - `...` server_args elements will be passed to the module named argument or to the `...`"
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
checkmate::assert_function(ui)
ui_formals <- names(formals(ui))
if (!"id" %in% ui_formals) {
stop(
"\nmodule() `ui` argument requires a function with following arguments:",
"\n - id - `teal` will set proper `shiny` namespace for this module.",
"\n\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."
if (!missing(filters)) {
if (!is.element("data", server_formals) && !is.null(datanames)) {
message(sprintf("module \"%s\" server function takes no data so \"datanames\" will be ignored", label))
datanames <- NULL
checkmate::assert_character(datanames, min.len = 1, null.ok = TRUE, any.missing = FALSE)
checkmate::assert_list(server_args, null.ok = TRUE, names = "named")
srv_extra_args <- setdiff(names(server_args), server_formals)
if (length(srv_extra_args) > 0 && !"..." %in% server_formals) {
stop(
"\nFollowing `server_args` elements have no equivalent in the formals of the server:\n",
paste(paste(" -", srv_extra_args), collapse = "\n"),
"\n\nUpdate the server arguments by including above or add `...`"
checkmate::assert_list(ui_args, null.ok = TRUE, names = "named")
ui_extra_args <- setdiff(names(ui_args), ui_formals)
if (length(ui_extra_args) > 0 && !"..." %in% ui_formals) {
stop(
"\nFollowing `ui_args` elements have no equivalent in the formals of UI:\n",
paste(paste(" -", ui_extra_args), collapse = "\n"),
"\n\nUpdate the UI arguments by including above or add `...`"
structure(
list(
label = label,
server = server, ui = ui, datanames = unique(datanames),
server_args = server_args, ui_args = ui_args
class = "teal_module"
checkmate::assert_string(label)
submodules <- list(...)
if (any(vapply(submodules, is.character, FUN.VALUE = logical(1)))) {
stop(
"The only character argument to modules() must be 'label' and it must be named, ",
"change modules('lab', ...) to modules(label = 'lab', ...)"
checkmate::assert_list(submodules, min.len = 1, any.missing = FALSE, types = c("teal_module", "teal_modules"))
labels <- vapply(submodules, function(submodule) submodule$label, character(1))
names(submodules) <- get_unique_labels(labels)
structure(
list(
label = label,
children = submodules
class = "teal_modules"
paste0(paste(rep(" ", indent), collapse = ""), "+ ", x$label, "\n", collapse = "")
paste(
c(
paste0(rep(" ", indent), "+ ", x$label, "\n"),
unlist(lapply(x$children, format, indent = indent + 1, ...))
collapse = ""
checkmate::assert_class(modules, "teal_modules")
checkmate::assert_class(module, "teal_module")
modules$children <- c(modules$children, list(module))
labels <- vapply(modules$children, function(submodule) submodule$label, character(1))
names(modules$children) <- get_unique_labels(labels)
modules
if (inherits(modules, class)) {
} else if (inherits(modules, "teal_module")) {
NULL
} else if (inherits(modules, "teal_modules")) {
Filter(function(x) length(x) > 0L, lapply(modules$children, extract_module, class))
checkmate::assert_string(arg)
if (inherits(modules, "teal_modules")) {
any(unlist(lapply(modules$children, is_arg_used, arg)))
} else if (inherits(modules, "teal_module")) {
is_arg_used(modules$server, arg) || is_arg_used(modules$ui, arg)
} else if (is.function(modules)) {
isTRUE(arg %in% names(formals(modules)))
stop("is_arg_used function not implemented for this object")
checkmate::assert_multi_class(modules, c("teal_module", "teal_modules"))
checkmate::assert_int(depth, lower = 0)
if (inherits(modules, "teal_modules")) {
max(vapply(modules$children, modules_depth, integer(1), depth = depth + 1L))
depth
script <- sprintf(
"Shiny.setInputValue(`%s`, Intl.DateTimeFormat().resolvedOptions().timeZone)",
ns("timezone")
shinyjs::runjs(script) # function does not return anything
invisible(NULL)
bs_theme <- getOption("teal.bs_theme")
if (is.null(bs_theme)) {
NULL
} else if (!inherits(bs_theme, "bs_theme")) {
warning("teal.bs_theme has to be of a bslib::bs_theme class, the default shiny bootstrap is used.")
NULL
bs_theme
parents <- character(0)
for (i in dataname) {
while (length(i) > 0) {
parent_i <- teal.data::parent(join_keys, i)
parents <- c(parent_i, parents)
i <- parent_i
unique(c(parents, dataname))
checkmate::assert_class(x, "teal_data")
checkmate::assert_character(datanames, min.chars = 1L, any.missing = FALSE)
ans <- teal.slice::init_filtered_data(
x = sapply(datanames, function(dn) x[[dn]], simplify = FALSE),
join_keys = teal.data::join_keys(x)
attr(ans, "preprocessing_code") <- teal.data::get_code(x, datanames = datanames, check_names = FALSE)
attr(ans, "verification_status") <- x@verified
ans
checkmate::assert_string(title)
checkmate::assert_string(label)
checkmate::assert_string(description, null.ok = TRUE)
checkmate::assert_flag(with_filter)
checkmate::assert_class(filter_panel_api, classes = "FilterPanelAPI")
card <- teal::TealReportCard$new()
title <- if (label == "") title else label
card$set_name(title)
card$append_text(title, "header2")
if (!is.null(description)) card$append_text(description, "header3")
if (with_filter) card$append_fs(filter_panel_api$get_filter_state())
card
checkmate::assert_class(modules, "teal_modules")
checkmate::assert_character(datanames)
recursive_check_datanames <- function(modules, datanames) {
if (inherits(modules, "teal_modules")) {
sapply(modules$children, function(module) recursive_check_datanames(module, datanames = datanames))
extra_datanames <- setdiff(modules$datanames, c("all", datanames))
if (length(extra_datanames)) {
sprintf(
"- Module '%s' uses datanames not available in 'data': (%s) not in (%s)",
modules$label,
toString(dQuote(extra_datanames, q = FALSE)),
toString(dQuote(datanames, q = FALSE))
check_datanames <- unlist(recursive_check_datanames(modules, datanames))
if (length(check_datanames)) {
paste(check_datanames, collapse = "\n")
TRUE
checkmate::assert_class(filters, "teal_slices")
checkmate::assert_character(datanames)
out <- unlist(sapply(
filters, function(filter) {
dataname <- shiny::isolate(filter$dataname)
if (!dataname %in% datanames) {
sprintf(
"- Filter '%s' refers to dataname not available in 'data':\n %s not in (%s)",
shiny::isolate(filter$id),
dQuote(dataname, q = FALSE),
toString(dQuote(datanames, q = FALSE))
if (length(out)) {
paste(out, collapse = "\n")
TRUE
checkmate::assert_class(data, "teal_data")
checkmate::assert_multi_class(modules, c("teal_modules", "teal_module"))
checkmate::assert_class(filters, "modules_teal_slices")
checkmate::assert_r6(filtered_data_singleton, "FilteredData")
if (!isTRUE(attr(filters, "module_specific"))) {
slices <- shiny::isolate({
Filter(function(x) x$id %in% attr(filters, "mapping")$global_filters, filters)
filtered_data_singleton$set_filter_state(slices)
return(modules_structure(modules, filtered_data_singleton))
if (inherits(modules, "teal_module")) {
datanames <-
if (is.null(modules$datanames) || identical(modules$datanames, "all")) {
include_parent_datanames(
teal_data_datanames(data),
teal.data::join_keys(data)
include_parent_datanames(
modules$datanames,
teal.data::join_keys(data)
slices <- shiny::isolate({
Filter(x = filters, f = function(x) {
x$dataname %in% datanames &&
(x$id %in% attr(filters, "mapping")$global_filters ||
x$id %in% unique(unlist(attr(filters, "mapping")[modules$label]))) # nolint: indentation_linter.
slices$include_varnames <- attr(slices, "include_varnames")[names(attr(slices, "include_varnames")) %in% datanames]
slices$exclude_varnames <- attr(slices, "exclude_varnames")[names(attr(slices, "exclude_varnames")) %in% datanames]
filtered_data <- teal_data_to_filtered_data(data, datanames)
filtered_data$set_filter_state(slices)
return(filtered_data)
} else if (inherits(modules, "teal_modules")) {
ans <- lapply(
modules$children,
modules_datasets,
data = data,
filters = filters,
filtered_data_singleton = filtered_data_singleton
names(ans) <- vapply(modules$children, `[[`, character(1), "label")
return(ans)
if (inherits(modules, "teal_module")) {
return(value)
stats::setNames(
lapply(modules$children, modules_structure, value),
vapply(modules$children, `[[`, character(1), "label")
checkmate::assert_class(data, "teal_data")
if (length(teal.data::datanames(data))) {
teal.data::datanames(data)
ls(teal.code::get_env(data), all.names = TRUE)
checkmate::assert_class(shiny_tag, "shiny.tag")
checkmate::assert_true(shiny_tag$name == "head")
child_names <- vapply(shiny_tag$children, `[[`, character(1L), "name")
checkmate::assert_subset(c("title", "link"), child_names, .var.name = "child tags")
rel_attr <- shiny_tag$children[[which(child_names == "link")]]$attribs$rel
checkmate::assert_subset(
rel_attr,
c("icon", "shortcut icon"),
.var.name = "Link tag's rel attribute",
empty.ok = FALSE
checkmate::assert_string(title, null.ok = TRUE)
checkmate::assert_string(favicon, null.ok = TRUE)
tags$head(
tags$title(title),
tags$link(
rel = "icon",
href = favicon,
sizes = "any"
checkmate::assert_multi_class(data, c("teal_data", "teal_data_module"))
checkmate::assert_class(modules, "teal_modules")
data <- if (inherits(data, "teal_data")) {
as.list(data@env)
} else if (inherits(data, "teal_data_module")) {
deparse1(body(data$server))
modules <- lapply(modules, defunction)
rlang::hash(list(data = data, modules = modules))
if (is.list(x)) {
lapply(x, defunction)
} else if (is.function(x)) {
deparse1(body(x))
x
make.unique(gsub("[^[:alnum:]]", "_", tolower(labels)), sep = "_")
checkmate::assert_character(src, min.len = 0, max.len = 1)
params <- list(...)
params$eval <- FALSE
rblock <- RcodeBlock$new(src)
rblock$set_params(params)
self$append_content(rblock)
self$append_metadata("SRC", src)
invisible(self)
checkmate::assert_class(fs, "teal_slices")
self$append_text("Filter State", "header3")
if (length(fs)) {
self$append_content(TealSlicesBlock$new(fs))
self$append_text("No filters specified.")
invisible(self)
checkmate::assert_list(encodings)
self$append_text("Selected Options", "header3")
if (requireNamespace("yaml", quietly = TRUE)) {
self$append_text(yaml::as.yaml(encodings, handlers = list(
POSIXct = function(x) format(x, "%Y-%m-%d"),
POSIXlt = function(x) format(x, "%Y-%m-%d"),
Date = function(x) format(x, "%Y-%m-%d")
)), "verbatim")
self$append_metadata("Encodings", encodings)
invisible(self)
self$set_content(content)
self$set_style(style)
invisible(self)
checkmate::assert_class(content, "teal_slices")
if (length(content) != 0) {
states_list <- lapply(content, function(x) {
x_list <- shiny::isolate(as.list(x))
if (
inherits(x_list$choices, c("integer", "numeric", "Date", "POSIXct", "POSIXlt")) &&
length(x_list$choices) == 2 &&
length(x_list$selected) == 2
if (!is.null(x_list$arg)) {
x_list <- x_list[
c("dataname", "varname", "experiment", "arg", "expr", "selected", "range", "keep_na", "keep_inf")
names(x_list) <- c(
"Dataset name", "Variable name", "Experiment", "Filtering by", "Applied expression",
"Selected Values", "Selected range", "Include NA values", "Include Inf values"
Filter(Negate(is.null), x_list)
if (requireNamespace("yaml", quietly = TRUE)) {
super$set_content(yaml::as.yaml(states_list))
private$teal_slices <- content
invisible(self)
checkmate::assert_list(x)
checkmate::assert_names(names(x), must.include = c("teal_slices"))
self$set_content(x$teal_slices)
invisible(self)
list(teal_slices = private$teal_slices)
css_files <- list.files(
system.file("css", package = "teal", mustWork = TRUE),
pattern = pattern, full.names = TRUE
singleton(
tags$head(lapply(css_files, includeCSS))
checkmate::assert_character(except, min.len = 1, any.missing = FALSE, null.ok = TRUE)
js_files <- list.files(system.file("js", package = "teal", mustWork = TRUE), pattern = pattern, full.names = TRUE)
js_files <- js_files[!(basename(js_files) %in% except)] # no-op if except is NULL
singleton(lapply(js_files, includeScript))
checkmate::assert_character(files, min.len = 1, any.missing = FALSE)
lapply(files, function(file) {
shinyjs::runjs(paste0(readLines(system.file("js", file, package = "teal", mustWork = TRUE)), collapse = "\n"))
invisible(NULL)
tagList(
shinyjs::useShinyjs(),
include_css_files(),
include_js_files(except = "init.js"),
shinyjs::hidden(icon("gear")), # add hidden icon to load font-awesome css for icons
shiny::isolate({
checkmate::assert_flag(allow_add)
checkmate::assert_flag(module_specific)
if (!missing(mapping)) checkmate::assert_list(mapping, types = c("character", "NULL"), names = "named")
checkmate::assert_string(app_id, null.ok = TRUE)
slices <- list(...)
all_slice_id <- vapply(slices, `[[`, character(1L), "id")
if (missing(mapping)) {
mapping <- list(global_filters = all_slice_id)
if (!module_specific) {
mapping[setdiff(names(mapping), "global_filters")] <- NULL
failed_slice_id <- setdiff(unlist(mapping), all_slice_id)
if (length(failed_slice_id)) {
stop(sprintf(
"Filters in mapping don't match any available filter.\n %s not in %s",
toString(failed_slice_id),
toString(all_slice_id)
tss <- teal.slice::teal_slices(
exclude_varnames = exclude_varnames,
include_varnames = include_varnames,
count_type = count_type,
allow_add = allow_add
attr(tss, "mapping") <- mapping
attr(tss, "module_specific") <- module_specific
attr(tss, "app_id") <- app_id
class(tss) <- c("modules_teal_slices", class(tss))
tss
checkmate::assert_list(x)
lapply(x, checkmate::assert_list, names = "named", .var.name = "list element")
attrs <- attributes(unclass(x))
ans <- lapply(x, function(x) if (is.teal_slice(x)) x else as.teal_slice(x))
do.call(teal_slices, c(ans, attrs))
x <- list(...)
checkmate::assert_true(all(vapply(x, is.teal_slices, logical(1L))), .var.name = "all arguments are teal_slices")
all_attributes <- lapply(x, attributes)
all_attributes <- coalesce_r(all_attributes)
all_attributes <- all_attributes[names(all_attributes) != "class"]
do.call(
teal_slices,
c(
unique(unlist(x, recursive = FALSE)),
all_attributes
checkmate::assert_class(filter, "teal_slices")
shiny::isolate({
filter_copy <- lapply(filter, function(slice) {
teal.slice::as.teal_slice(as.list(slice))
attributes(filter_copy) <- attributes(filter)
filter_copy
checkmate::assert_character(id)
checkmate::assert_true(is.reactive(slices_global))
checkmate::assert_class(isolate(slices_global()), "teal_slices")
checkmate::assert_true(is.reactive(mapping_matrix))
checkmate::assert_data_frame(isolate(mapping_matrix()), null.ok = TRUE)
checkmate::assert_list(datasets, types = "FilteredData", any.missing = FALSE, names = "named")
checkmate::assert_true(is.reactive(snapshot_history))
checkmate::assert_list(isolate(snapshot_history()), names = "unique")
moduleServer(id, function(input, output, session) {
logger::log_trace("bookmark_manager_srv initializing")
app_session <- .subset2(shiny::getDefaultReactiveDomain(), "parent")
observe({
inputs <- reactiveValuesToList(app_session$input)
ids_buttons <- names(Filter(function(x) inherits(x, "shinyActionButtonValue"), inputs))
id_bookmark_name <- grep("bookmark_name", names(inputs), value = TRUE, fixed = TRUE)
setBookmarkExclude(union(ids_buttons, id_bookmark_name), session = app_session)
app_session$onBookmark(function(state) {
app_session$onBookmarked(function(url) {
app_session$onRestored(function(state) {
ns <- session$ns
bookmark_history <- reactiveVal({
list()
observeEvent(input$bookmark_add, {
observeEvent(input$bookmark_accept, {
divs <- reactiveValues()
observeEvent(bookmark_history(), {
logger::log_trace("bookmark_manager_srv: bookmark history changed, updating bookmark list")
lapply(names(bookmark_history()), function(s) {
output$bookmark_list <- renderUI({
rows <- lapply(rev(reactiveValuesToList(divs)), function(d) d)
if (length(rows) == 0L) {
div(
class = "manager_placeholder",
"Bookmarks will appear here."
bookmark_history
packageStartupMessage(
"\nYou are using teal version ",
read.dcf(system.file("DESCRIPTION", package = "teal"))[, "Version"]
vapply(
utils::sessionInfo()$otherPkgs,
function(x) {
paste0("library(", x$Package, ")")
character(1)
rev() %>%
paste0(sep = "\n") %>%
paste0(collapse = "")
code_string <- getOption("teal.load_nest_code")
if (is.character(code_string)) {
code_string
"# Add any code to install/load your NEST environment here\n"
str_prepro <- attr(datasets, "preprocessing_code")
if (length(str_prepro) == 0) {
str_prepro <- paste(str_prepro, collapse = "\n")
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")
str_filter <- teal.slice::get_filter_expr(datasets, datanames)
if (str_filter == "") {
str_filter <- character(0)
str_code <- paste(c(str_prepro, str_hash, str_filter), collapse = "\n\n")
sprintf("%s\n", str_code)
checkmate::assert_multi_class(modules, c("teal_modules", "teal_module"))
checkmate::assert_count(depth)
UseMethod("ui_nested_tabs", modules)
checkmate::assert_list(datasets, types = c("list", "FilteredData"))
ns <- NS(id)
do.call(
tabsetPanel,
c(
list(
id = ns("active_tab"),
type = if (modules$label == "root") "pills" else "tabs"
lapply(
names(modules$children),
function(module_id) {
module_label <- modules$children[[module_id]]$label
tabPanel(
title = module_label,
value = module_id, # when clicked this tab value changes input$<tabset panel id>
ui_nested_tabs(
id = ns(module_id),
modules = modules$children[[module_id]],
datasets = datasets[[module_label]],
depth = depth + 1L,
is_module_specific = is_module_specific
checkmate::assert_class(datasets, classes = "FilteredData")
ns <- NS(id)
args <- c(list(id = ns("module")), modules$ui_args)
teal_ui <- tags$div(
id = id,
class = "teal_module",
uiOutput(ns("data_reactive"), inline = TRUE),
tagList(
if (depth >= 2L) tags$div(style = "mt-6"),
do.call(modules$ui, args)
if (!is.null(modules$datanames) && is_module_specific) {
teal_ui
checkmate::assert_multi_class(modules, c("teal_modules", "teal_module"))
checkmate::assert_class(reporter, "Reporter")
UseMethod("srv_nested_tabs", modules)
checkmate::assert_list(datasets, types = c("list", "FilteredData"))
moduleServer(id = id, module = function(input, output, session) {
logger::log_trace("srv_nested_tabs.teal_modules initializing the module { deparse1(modules$label) }.")
labels <- vapply(modules$children, `[[`, character(1), "label")
modules_reactive <- sapply(
names(modules$children),
function(module_id) {
srv_nested_tabs(
id = module_id,
datasets = datasets[[labels[module_id]]],
modules = modules$children[[module_id]],
is_module_specific = is_module_specific,
reporter = reporter
simplify = FALSE
input_validated <- eventReactive(input$active_tab, input$active_tab, ignoreNULL = TRUE)
get_active_module <- reactive({
if (length(modules$children) == 1L) {
modules_reactive[[1]]()
modules_reactive[[input_validated()]]()
get_active_module
checkmate::assert_class(datasets, "FilteredData")
logger::log_trace("srv_nested_tabs.teal_module initializing the module: { deparse1(modules$label) }.")
moduleServer(id = id, module = function(input, output, session) {
if (!is.null(modules$datanames) && is_module_specific) {
trigger_data <- reactiveVal(1L)
trigger_module <- reactiveVal(NULL)
output$data_reactive <- renderUI({
lapply(datasets$datanames(), function(x) {
datasets$get_data(x, filtered = TRUE)
isolate(trigger_data(trigger_data() + 1))
isolate(trigger_module(TRUE))
NULL
args <- c(list(id = "module"), modules$server_args)
if (is_arg_used(modules$server, "reporter")) {
if (is_arg_used(modules$server, "datasets")) {
args <- c(args, datasets = datasets)
if (is_arg_used(modules$server, "data")) {
data <- eventReactive(trigger_data(), .datasets_to_data(modules, datasets))
args <- c(args, data = list(data))
if (is_arg_used(modules$server, "filter_panel_api")) {
filter_panel_api <- teal.slice::FilterPanelAPI$new(datasets)
args <- c(args, filter_panel_api = filter_panel_api)
call_module <- function() {
if (is_arg_used(modules$server, "id")) {
do.call(modules$server, args)
if (isTRUE(session$restoreContext$active)) {
} else if (id == "report_previewer") {
observeEvent(
ignoreNULL = TRUE,
once = TRUE,
eventExpr = trigger_module(),
handlerExpr = call_module()
reactive(modules)
checkmate::assert_class(module, "teal_module")
checkmate::assert_class(datasets, "FilteredData")
datanames <- if (is.null(module$datanames) || identical(module$datanames, "all")) {
datasets$datanames()
include_parent_datanames(
module$datanames,
datasets$get_join_keys()
data <- sapply(datanames, function(x) datasets$get_data(x, filtered = TRUE), simplify = FALSE)
hashes <- calculate_hashes(datanames, datasets)
code <- c(
get_rcode_str_install(),
get_rcode_libraries(),
get_datasets_code(datanames, datasets, hashes)
data <- do.call(
teal.data::teal_data,
args = c(data, code = list(code), join_keys = list(datasets$get_join_keys()[datanames]))
data@verified <- attr(datasets, "verification_status")
data
sapply(datanames, function(x) rlang::hash(datasets$get_data(x, filtered = FALSE)), simplify = FALSE)
checkmate::assert_class(modules, "teal_modules")
checkmate::assert_list(datasets, types = c("list", "FilteredData"))
checkmate::assert_class(filter, "teal_slices")
ns <- NS(id)
is_module_specific <- isTRUE(attr(filter, "module_specific"))
teal_ui <- ui_nested_tabs(ns("root"), modules = modules, datasets, is_module_specific = is_module_specific)
filter_panel_btns <- tags$li(
class = "flex-grow",
tags$button(
class = "btn action-button filter_hamburger", # see sidebar.css for style filter_hamburger
href = "javascript:void(0)",
onclick = "toggleFilterPanel();", # see sidebar.js
title = "Toggle filter panel",
icon("fas fa-bars")
wunder_bar_ui(ns("wunder_bar"))
teal_ui$children[[1]] <- tagAppendChild(teal_ui$children[[1]], filter_panel_btns)
if (!is_module_specific) {
tabset_bar <- teal_ui$children[[1]]
teal_modules <- teal_ui$children[[2]]
filter_ui <- unlist(datasets)[[1]]$ui_filter_panel(ns("filter_panel"))
list(
tabset_bar,
tags$hr(class = "my-2"),
fluidRow(
column(width = 9, teal_modules, class = "teal_primary_col"),
column(width = 3, filter_ui, class = "teal_secondary_col")
checkmate::assert_class(modules, "teal_modules")
checkmate::assert_list(datasets, types = c("list", "FilteredData"))
checkmate::assert_class(reporter, "Reporter")
checkmate::assert_class(filter, "teal_slices")
moduleServer(id, function(input, output, session) {
logger::log_trace("srv_tabs_with_filters initializing the module.")
is_module_specific <- isTRUE(attr(filter, "module_specific"))
wunder_bar_out <- wunder_bar_srv("wunder_bar", datasets, filter)
active_module <- srv_nested_tabs(
id = "root",
datasets = datasets,
modules = modules,
reporter = reporter,
is_module_specific = is_module_specific
if (!is_module_specific) {
active_datanames <- reactive({
if (identical(active_module()$datanames, "all")) {
singleton$datanames()
include_parent_datanames(
active_module()$datanames,
singleton$get_join_keys()
singleton <- unlist(datasets)[[1]]
singleton$srv_filter_panel("filter_panel", active_datanames = active_datanames)
observeEvent(
eventExpr = active_datanames(),
handlerExpr = {
script <- if (length(active_datanames()) == 0 || is.null(active_datanames())) {
"handleActiveDatasetsPresent();"
shinyjs::runjs(script)
ignoreNULL = FALSE
showNotification("Data loaded - App fully started up")
logger::log_trace("srv_tabs_with_filters initialized the module")
active_module
moduleServer(id, function(input, output, session) {
logger::log_trace("filter_manager_srv initializing for: { paste(names(datasets), collapse = ', ')}.")
is_module_specific <- isTRUE(attr(filter, "module_specific"))
slices_global <- reactiveVal(filter)
datasets_flat <-
if (!is_module_specific) {
flatten_datasets(unlist(datasets)[[1]])
flatten_datasets(datasets)
mapping_matrix <- reactive({
state_ids_global <- vapply(slices_global(), `[[`, character(1L), "id")
mapping_smooth <- lapply(datasets_flat, function(x) {
state_ids_local <- vapply(x$get_filter_state(), `[[`, character(1L), "id")
state_ids_allowed <- vapply(x$get_available_teal_slices()(), `[[`, character(1L), "id")
states_active <- state_ids_global %in% state_ids_local
ifelse(state_ids_global %in% state_ids_allowed, states_active, NA)
as.data.frame(mapping_smooth, row.names = state_ids_global, check.names = FALSE)
output$slices_table <- renderTable(
expr = {
mm <- mapping_matrix()
mm[] <- lapply(mm, ifelse, yes = intToUtf8(9989), no = intToUtf8(10060))
mm[] <- lapply(mm, function(x) ifelse(is.na(x), intToUtf8(128306), x))
if (nrow(mm) == 0L) {
mm <- data.frame(`Filter manager` = "No filters specified.", check.names = FALSE)
rownames(mm) <- ""
mm[names(mm) != "Report previewer"]
align = paste(c("l", rep("c", sum(names(datasets_flat) != "Report previewer"))), collapse = ""),
rownames = TRUE
modules_out <- lapply(names(datasets_flat), function(module_name) {
filter_manager_module_srv(
id = module_name,
module_fd = datasets_flat[[module_name]],
slices_global = slices_global
list(
slices_global = slices_global,
mapping_matrix = mapping_matrix,
datasets_flat = datasets_flat,
modules_out = modules_out # returned for testing purpose
moduleServer(id, function(input, output, session) {
module_fd$set_available_teal_slices(reactive(slices_global()))
slices_module <- reactive(module_fd$get_filter_state())
previous_slices <- reactiveVal(isolate(slices_module()))
slices_added <- reactiveVal(NULL)
observeEvent(slices_module(), ignoreNULL = FALSE, {
logger::log_trace("filter_manager_srv@1 detecting states deltas in module: { id }.")
added <- setdiff_teal_slices(slices_module(), slices_global())
if (length(added)) slices_added(added)
previous_slices(slices_module())
observeEvent(slices_added(), ignoreNULL = TRUE, {
logger::log_trace("filter_manager_srv@2 added filter in module: { id }.")
global_ids <- vapply(slices_global(), `[[`, character(1L), "id")
lapply(
slices_added(),
function(slice) {
if (slice$id %in% global_ids) {
slices_global_new <- c(slices_global(), slices_added())
slices_global(slices_global_new)
slices_added(NULL)
slices_module # returned for testing purpose
if (inherits(x, "FilteredData")) {
setNames(list(x), name)
unlist(lapply(names(x), function(name) flatten_datasets(x[[name]], name)))
checkmate::assert_character(id, max.len = 1, any.missing = FALSE)
checkmate::assert_multi_class(splash_ui, c("shiny.tag", "shiny.tag.list", "html"))
if (is.character(title)) {
validate_app_title_tag(title)
checkmate::assert(
.var.name = "header",
checkmate::check_string(header),
checkmate::check_multi_class(header, c("shiny.tag", "shiny.tag.list", "html"))
if (checkmate::test_string(header)) {
checkmate::assert(
.var.name = "footer",
checkmate::check_string(footer),
checkmate::check_multi_class(footer, c("shiny.tag", "shiny.tag.list", "html"))
if (checkmate::test_string(footer)) {
ns <- NS(id)
splash_ui <- tags$div(
id = ns("main_ui_container"),
tags$div(splash_ui)
shiny_busy_message_panel <- conditionalPanel(
condition = "(($('html').hasClass('shiny-busy')) && (document.getElementById('shiny-notification-panel') == null))", # nolint: line_length.
tags$div(
icon("arrows-rotate", "spin fa-spin"),
"Computing ...",
class = "shinybusymessage"
fluidPage(
title = title,
theme = get_teal_bs_theme(),
include_teal_css_js(),
tags$header(header),
tags$hr(class = "my-2"),
shiny_busy_message_panel,
splash_ui,
tags$hr(),
tags$footer(
tags$div(
footer,
teal.widgets::verbatim_popup_ui(ns("sessionInfo"), "Session Info", type = "link"),
textOutput(ns("identifier"))
stopifnot(is.reactive(teal_data_rv))
moduleServer(id, function(input, output, session) {
logger::log_trace("srv_teal initializing the module.")
output$identifier <- renderText(
paste0("Pid:", Sys.getpid(), " Token:", substr(session$token, 25, 32))
teal.widgets::verbatim_popup_srv(
"sessionInfo",
verbatim_content = utils::capture.output(utils::sessionInfo()),
title = "SessionInfo"
run_js_files(files = "init.js")
get_client_timezone(session$ns)
observeEvent(
eventExpr = input$timezone,
once = TRUE,
handlerExpr = {
session$userData$timezone <- input$timezone
logger::log_trace("srv_teal@1 Timezone set to client's timezone: { input$timezone }.")
reporter <- teal.reporter::Reporter$new()
if (is_arg_used(modules, "reporter") && length(extract_module(modules, "teal_module_previewer")) == 0) {
env <- environment()
datasets_reactive <- eventReactive(teal_data_rv(), {
env$progress <- shiny::Progress$new(session)
env$progress$set(0.25, message = "Setting data")
modules_datasets(teal_data_rv(), modules, filter, teal_data_to_filtered_data(teal_data_rv()))
observeEvent(datasets_reactive(), once = TRUE, {
logger::log_trace("srv_teal@5 setting main ui after data was pulled")
on.exit(env$progress$close())
env$progress$set(0.5, message = "Setting up main UI")
datasets <- datasets_reactive()
removeUI(sprintf("#%s > div:nth-child(1)", session$ns("main_ui_container")))
insertUI(
selector = paste0("#", session$ns("main_ui_container")),
where = "beforeEnd",
ui = tags$div(ui_tabs_with_filters(
session$ns("main_ui"),
modules = modules,
datasets = datasets,
filter = filter
immediate = TRUE
srv_tabs_with_filters(
id = "main_ui",
datasets = datasets,
modules = modules,
reporter = reporter,
filter = filter
ns <- NS(id)
rev( # Reversing order because buttons show up in UI from right to left.
tagList(
tags$button(
id = ns("show_filter_manager"),
class = "btn action-button wunder_bar_button",
title = "View filter mapping",
suppressMessages(icon("solid fa-grip"))
tags$button(
id = ns("show_snapshot_manager"),
class = "btn action-button wunder_bar_button",
title = "Manage filter state snapshots",
icon("camera")
tags$button(
id = ns("show_bookmark_manager"),
class = "btn action-button wunder_bar_button",
title = "Manage bookmarks",
suppressMessages(icon("solid fa-bookmark"))
moduleServer(id, function(input, output, session) {
logger::log_trace("wunder_bar_srv initializing")
ns <- session$ns
observeEvent(input$show_filter_manager, {
observeEvent(input$show_snapshot_manager, {
logger::log_trace("wunder_bar_srv@1 show_snapshot_manager button has been clicked.")
showModal(
modalDialog(
snapshot_manager_ui(ns("snapshot_manager")),
size = "m",
footer = NULL,
easyClose = TRUE
observeEvent(input$show_bookmark_manager, {
filter_manager_results <- filter_manager_srv(
id = "filter_manager",
datasets = datasets,
filter = filter
snapshot_history <- snapshot_manager_srv(
id = "snapshot_manager",
slices_global = filter_manager_results$slices_global,
mapping_matrix = filter_manager_results$mapping_matrix,
datasets = filter_manager_results$datasets_flat
bookmark_history <- bookmark_manager_srv(
id = "bookmark_manager",
slices_global = filter_manager_results$slices_global,
mapping_matrix = filter_manager_results$mapping_matrix,
datasets = filter_manager_results$datasets_flat,
snapshot_history = snapshot_history
checkmate::assert_class(tss, "teal_slices")
checkmate::assert_path_for_output(file, overwrite = TRUE, extension = "json")
cat(format(tss, trim_lines = FALSE), "\n", file = file)
checkmate::assert_file_exists(file, access = "r", extension = "json")
tss_json <- jsonlite::fromJSON(file, simplifyDataFrame = FALSE)
tss_json$slices <-
lapply(tss_json$slices, function(slice) {
for (field in c("selected", "choices")) {
if (!is.null(slice[[field]])) {
if (length(slice[[field]]) > 0) {
date_partial_regex <- "^[0-9]{4}-[0-9]{2}-[0-9]{2}"
time_stamp_regex <- paste0(date_partial_regex, "\\s[0-9]{2}:[0-9]{2}:[0-9]{2}\\sUTC$")
slice[[field]] <-
if (all(grepl(paste0(date_partial_regex, "$"), slice[[field]]))) {
as.Date(slice[[field]])
} else if (all(grepl(time_stamp_regex, slice[[field]]))) {
as.POSIXct(slice[[field]], tz = "UTC")
slice[[field]]
slice[[field]] <- character(0)
slice
tss_elements <- lapply(tss_json$slices, as.teal_slice)
do.call(teal_slices, c(tss_elements, tss_json$attributes))
teal_data_module(
ui = function(id) {
ns <- NS(id)
object$ui(ns("mutate_inner"))
server = function(id) {
moduleServer(id, function(input, output, session) {
teal_data_rv <- object$server("mutate_inner")
if (!is.reactive(teal_data_rv)) {
stop("The `teal_data_module` must return a reactive expression.", call. = FALSE)
td <- eventReactive(teal_data_rv(),
if (inherits(teal_data_rv(), c("teal_data", "qenv.error"))) {
eval_code(teal_data_rv(), code)
teal_data_rv()
ignoreNULL = FALSE
td
eval_code(object, code = paste(lang2calls(code), collapse = "\n"))
eval_code(object, code = paste(lang2calls(code), collapse = "\n"))
checkmate::assert_string(msg, null.ok = TRUE)
checkmate::assert_data_frame(x)
if (!is.null(min_nrow)) {
if (complete) {
complete_index <- stats::complete.cases(x)
validate(need(
sum(complete_index) > 0 && nrow(x[complete_index, , drop = FALSE]) >= min_nrow,
paste(c(paste("Number of complete cases is less than:", min_nrow), msg), collapse = "\n")
validate(need(
nrow(x) >= min_nrow,
paste(
c(paste("Minimum number of records not met: >=", min_nrow, "records required."), msg),
collapse = "\n"
if (!allow_inf) {
validate(need(
all(vapply(x, function(col) !is.numeric(col) || !any(is.infinite(col)), logical(1))),
"Dataframe contains Inf values which is not allowed."
checkmate::assert_string(label)
checkmate::assert_list(server_args, names = "named")
checkmate::assert_true(all(names(server_args) %in% names(formals(teal.reporter::reporter_previewer_srv))))
message("Initializing reporter_previewer_module")
srv <- function(id, reporter, ...) {
ui <- function(id, ...) {
module <- module(
label = "temporary label",
server = srv, ui = ui,
server_args = server_args, ui_args = list(), datanames = NULL
class(module) <- c("teal_module_previewer", class(module))
module$label <- label
module
dots <- list(...)
if (!is_validators(dots)) stop("validate_inputs accepts validators or a list thereof")
messages <- extract_validator(dots, header)
failings <- if (!any_names(dots)) {
add_header(messages, header)
unlist(messages)
shiny::validate(shiny::need(is.null(failings), failings))
all(if (is.list(x)) unlist(lapply(x, is_validators)) else inherits(x, "InputValidator"))
x$.__enclos_env__$private$enabled
if (inherits(iv, "InputValidator")) {
add_header(gather_messages(iv), header)
if (is.null(names(iv))) names(iv) <- rep("", length(iv))
mapply(extract_validator, iv = iv, header = names(iv), SIMPLIFY = FALSE)
if (validator_enabled(iv)) {
status <- iv$validate()
failing_inputs <- Filter(Negate(is.null), status)
unique(lapply(failing_inputs, function(x) x[["message"]]))
warning("Validator is disabled and will be omitted.")
list()
ans <- unlist(messages)
if (length(ans) != 0L && isTRUE(nchar(header) > 0L)) {
ans <- c(paste0(header, "\n"), ans, "\n")
ans
any(
if (is.list(x)) {
if (!is.null(names(x)) && any(names(x) != "")) TRUE else unlist(lapply(x, any_names))
FALSE
checkmate::assert_string(label)
module(
label,
server = function(id, data) {
checkmate::assert_class(data(), "teal_data")
moduleServer(id, function(input, output, session) {
updateSelectInput(
inputId = "dataname",
choices = isolate(teal.data::datanames(data())),
selected = restoreInput(session$ns("dataname"), NULL)
output$text <- renderPrint({
req(input$dataname)
data()[[input$dataname]]
teal.widgets::verbatim_popup_srv(
id = "rcode",
verbatim_content = reactive(teal.code::get_code(data())),
title = "Example Code"
ui = function(id) {
ns <- NS(id)
teal.widgets::standard_layout(
output = verbatimTextOutput(ns("text")),
encoding = tags$div(
selectInput(ns("dataname"), "Choose a dataset", choices = NULL),
teal.widgets::verbatim_popup_ui(ns("rcode"), "Show R code")
datanames = datanames
expr <- substitute(expr)
extras <- list(...)
if (!identical(as.list(expr)[[1L]], as.symbol("{"))) {
expr <- call("{", expr)
calls <- as.list(expr)[-1]
calls <- lapply(calls, function(x) do.call(substitute, list(x, env = extras)))
eval_code(object = data, code = as.expression(calls))
checkmate::assert_function(ui, args = "id", nargs = 1)
checkmate::assert_function(server, args = "id", nargs = 1)
structure(
list(ui = ui, server = server),
class = "teal_data_module"
checkmate::assert_string(id)
checkmate::assert_class(slices_global, ".slicesGlobal")
moduleServer(id, function(input, output, session) {
setBookmarkExclude(c("show_filter_manager"))
observeEvent(input$show_filter_manager, {
srv_filter_manager("filter_manager", slices_global = slices_global)
checkmate::assert_string(id)
checkmate::assert_class(slices_global, ".slicesGlobal")
moduleServer(id, function(input, output, session) {
logger::log_debug("filter_manager_srv initializing.")
session$onBookmark(function(state) {
bookmarked_slices <- restoreValue(session$ns("filter_state_on_bookmark"), NULL)
if (!is.null(bookmarked_slices)) {
mapping_table <- reactive({
module_labels <- setdiff(
names(attr(slices_global$all_slices(), "mapping")),
"Report previewer"
isolate({
mm <- as.data.frame(
sapply(
module_labels,
simplify = FALSE,
function(module_label) {
available_slices <- slices_global$module_slices_api[[module_label]]$get_available_teal_slices()
global_ids <- sapply(slices_global$all_slices(), `[[`, "id", simplify = FALSE)
module_ids <- sapply(slices_global$slices_get(module_label), `[[`, "id", simplify = FALSE)
allowed_ids <- vapply(available_slices, `[[`, character(1L), "id")
active_ids <- global_ids %in% module_ids
setNames(nm = global_ids, ifelse(global_ids %in% allowed_ids, active_ids, NA))
check.names = FALSE
colnames(mm)[colnames(mm) == "global_filters"] <- "Global filters"
mm
output$slices_table <- renderTable(
expr = {
logger::log_debug("filter_manager_srv@1 rendering slices_table.")
mm <- mapping_table()
mm[] <- lapply(mm, ifelse, yes = intToUtf8(9989), no = intToUtf8(10060))
mm[] <- lapply(mm, function(x) ifelse(is.na(x), intToUtf8(128306), x))
if (nrow(mm) == 0L) {
mm <- data.frame(`Filter manager` = "No filters specified.", check.names = FALSE)
rownames(mm) <- ""
mm
rownames = TRUE
mapping_table # for testing purpose
checkmate::assert_string(id)
checkmate::assert_class(module_fd, "reactive")
checkmate::assert_class(slices_global, ".slicesGlobal")
moduleServer(id, function(input, output, session) {
logger::log_debug("srv_module_filter_manager initializing for module: { id }.")
slices_global_module <- reactive({
slices_global$slices_get(module_label = id)
slices_module <- reactive(req(module_fd())$get_filter_state())
module_fd_previous <- reactiveVal(NULL)
obs1 <- observeEvent(module_fd(), priority = 1, {
logger::log_debug("srv_module_filter_manager@1 setting initial slices for module: { id }.")
slices <- slices_global_module()
if (!is.null(module_fd_previous())) module_fd_previous()$finalize()
module_fd_previous(module_fd())
module_fd()$set_filter_state(slices)
module_fd()$set_available_teal_slices(slices_global$all_slices)
slices_global$module_slices_api_set(
id,
list(
get_available_teal_slices = module_fd()$get_available_teal_slices(),
set_filter_state = module_fd()$set_filter_state, # for testing purpose
get_filter_state = module_fd()$get_filter_state # for testing purpose
obs2 <- observeEvent(slices_module(), priority = 0, {
this_slices <- slices_module()
slices_global$slices_append(this_slices) # append new slices to the all_slices list
mapping_elem <- setNames(nm = id, list(vapply(this_slices, `[[`, character(1L), "id")))
slices_global$slices_active(mapping_elem)
obs3 <- observeEvent(slices_global_module(), {
global_vs_module <- setdiff_teal_slices(slices_global_module(), slices_module())
module_vs_global <- setdiff_teal_slices(slices_module(), slices_global_module())
if (length(global_vs_module) || length(module_vs_global)) {
logger::log_debug("srv_module_filter_manager@3 (N.B.) global state has changed for a module:{ id }.")
module_fd()$clear_filter_states()
module_fd()$set_filter_state(slices_global_module())
slices_module # returned for testing purpose
shiny::isolate({
checkmate::assert_class(slices, "teal_slices")
if (isTRUE(attr(slices, "module_specific"))) {
old_mapping <- attr(slices, "mapping")
new_mapping <- sapply(module_labels, simplify = FALSE, function(module_label) {
unique(unlist(old_mapping[c(module_label, "global_filters")]))
attr(slices, "mapping") <- new_mapping
.self$all_slices <<- shiny::reactiveVal(slices)
.self$module_slices_api <<- shiny::reactiveValues()
.self$slices_append(slices)
.self$slices_active(attr(slices, "mapping"))
invisible(.self)
isTRUE(attr(.self$all_slices(), "module_specific"))
shiny::isolate({
if (!.self$is_module_specific()) {
module_label <- "global_filters"
if (!identical(.self$module_slices_api[[module_label]], functions_list)) {
.self$module_slices_api[[module_label]] <- functions_list
invisible(.self)
shiny::isolate({
if (.self$is_module_specific()) {
new_mapping <- modifyList(attr(.self$all_slices(), "mapping"), mapping_elem)
new_mapping <- setNames(nm = "global_filters", list(unique(unlist(mapping_elem))))
if (!identical(new_mapping, attr(.self$all_slices(), "mapping"))) {
mapping_modules <- toString(names(new_mapping))
logger::log_debug(".slicesGlobal@slices_active: changing mapping for module(s): { mapping_modules }.")
new_slices <- .self$all_slices()
attr(new_slices, "mapping") <- new_mapping
.self$all_slices(new_slices)
invisible(.self)
shiny::isolate({
if (!is.teal_slices(slices)) {
new_slices <- setdiff_teal_slices(slices, .self$all_slices())
old_mapping <- attr(.self$all_slices(), "mapping")
if (length(new_slices)) {
new_ids <- vapply(new_slices, `[[`, character(1L), "id")
logger::log_debug(".slicesGlobal@slices_append: appending new slice(s): { new_ids }.")
slices_ids <- vapply(.self$all_slices(), `[[`, character(1L), "id")
lapply(new_slices, function(slice) {
if (slice$id %in% slices_ids) {
slice$id <- utils::tail(make.unique(c(slices_ids, slice$id), sep = "_"), 1)
new_slices_all <- c(.self$all_slices(), new_slices)
attr(new_slices_all, "mapping") <- old_mapping
.self$all_slices(new_slices_all)
invisible(.self)
if (missing(module_label)) {
module_ids <- unlist(attr(.self$all_slices(), "mapping")[c(module_label, "global_filters")])
Filter(
function(slice) slice$id %in% module_ids,
.self$all_slices()
shiny::isolate({
if (!is.teal_slices(slices)) {
.self$all_slices(slices)
invisible(.self)
checkmate::assert_character(id, max.len = 1, any.missing = FALSE)
checkmate::assert_multi_class(data, c("teal_data", "teal_data_module", "reactive", "reactiveVal"))
checkmate::assert_class(modules, "teal_modules")
checkmate::assert_class(filter, "teal_slices")
moduleServer(id, function(input, output, session) {
logger::log_debug("srv_teal initializing.")
output$identifier <- renderText(
paste0("Pid:", Sys.getpid(), " Token:", substr(session$token, 25, 32))
teal.widgets::verbatim_popup_srv(
"sessionInfo",
verbatim_content = utils::capture.output(utils::sessionInfo()),
title = "SessionInfo"
output$lockFile <- teal_lockfile_downloadhandler()
run_js_files(files = "init.js")
get_client_timezone(session$ns)
observeEvent(
eventExpr = input$timezone,
once = TRUE,
handlerExpr = {
data_rv <- srv_init_data("data", data = data, modules = modules, filter = filter)
datasets_rv <- if (!isTRUE(attr(filter, "module_specific"))) {
eventReactive(data_rv(), {
if (!inherits(data_rv(), "teal_data")) {
logger::log_debug("srv_teal@1 initializing FilteredData")
teal_data_to_filtered_data(data_rv())
module_labels <- unlist(module_labels(modules), use.names = FALSE)
slices_global <- methods::new(".slicesGlobal", filter, module_labels)
modules_output <- srv_teal_module(
id = "teal_modules",
data_rv = data_rv,
datasets = datasets_rv,
modules = modules,
slices_global = slices_global
mapping_table <- srv_filter_manager_panel("filter_manager_panel", slices_global = slices_global)
snapshots <- srv_snapshot_manager_panel("snapshot_manager_panel", slices_global = slices_global)
srv_bookmark_panel("bookmark_manager", modules)
if (inherits(data, "teal_data_module")) {
setBookmarkExclude(c("teal_modules-active_tab"))
invisible(NULL)
shiny::isolate({
checkmate::assert_flag(allow_add)
checkmate::assert_flag(module_specific)
if (!missing(mapping)) checkmate::assert_list(mapping, types = c("character", "NULL"), names = "named")
checkmate::assert_string(app_id, null.ok = TRUE)
slices <- list(...)
all_slice_id <- vapply(slices, `[[`, character(1L), "id")
if (missing(mapping)) {
mapping <- if (length(all_slice_id)) {
list(global_filters = all_slice_id)
list()
if (!module_specific) {
mapping[setdiff(names(mapping), "global_filters")] <- NULL
failed_slice_id <- setdiff(unlist(mapping), all_slice_id)
if (length(failed_slice_id)) {
stop(sprintf(
"Filters in mapping don't match any available filter.\n %s not in %s",
toString(failed_slice_id),
toString(all_slice_id)
tss <- teal.slice::teal_slices(
exclude_varnames = exclude_varnames,
include_varnames = include_varnames,
count_type = count_type,
allow_add = allow_add
attr(tss, "mapping") <- mapping
attr(tss, "module_specific") <- module_specific
attr(tss, "app_id") <- app_id
class(tss) <- c("modules_teal_slices", class(tss))
tss
checkmate::assert_list(x)
lapply(x, checkmate::assert_list, names = "named", .var.name = "list element")
attrs <- attributes(unclass(x))
ans <- lapply(x, function(x) if (is.teal_slice(x)) x else as.teal_slice(x))
do.call(teal_slices, c(ans, attrs))
x <- list(...)
checkmate::assert_true(all(vapply(x, is.teal_slices, logical(1L))), .var.name = "all arguments are teal_slices")
all_attributes <- lapply(x, attributes)
all_attributes <- coalesce_r(all_attributes)
all_attributes <- all_attributes[names(all_attributes) != "class"]
do.call(
teal_slices,
c(
unique(unlist(x, recursive = FALSE)),
all_attributes
checkmate::assert_class(filter, "teal_slices")
shiny::isolate({
filter_copy <- lapply(filter, function(slice) {
teal.slice::as.teal_slice(as.list(slice))
attributes(filter_copy) <- attributes(filter)
filter_copy
checkmate::assert_string(label)
if (label == "global_filters") {
stop(
sprintf("module(label = \"%s\", ...\n ", label),
"Label 'global_filters' is reserved in teal. Please change to something else.",
call. = FALSE
if (label == "Report previewer") {
checkmate::assert_function(server)
server_formals <- names(formals(server))
if (!(
"id" %in% server_formals ||
all(c("input", "output", "session") %in% server_formals)
stop(
"\nmodule() `server` argument requires a function with following arguments:",
"\n - id - `teal` will set proper `shiny` namespace for this module.",
"\n - input, output, session (not recommended) - then `shiny::callModule` will be used to call a module.",
"\n\nFollowing arguments can be used optionaly:",
"\n - `data` - module will receive list of reactive (filtered) data specified in the `filters` argument",
"\n - `datasets` - module will receive `FilteredData`. See `help(teal.slice::FilteredData)`",
"\n - `reporter` - module will receive `Reporter`. See `help(teal.reporter::Reporter)`",
"\n - `filter_panel_api` - module will receive `FilterPanelAPI`. (See [teal.slice::FilterPanelAPI]).",
"\n - `...` server_args elements will be passed to the module named argument or to the `...`"
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
checkmate::assert_function(ui)
ui_formals <- names(formals(ui))
if (!"id" %in% ui_formals) {
stop(
"\nmodule() `ui` argument requires a function with following arguments:",
"\n - id - `teal` will set proper `shiny` namespace for this module.",
"\n\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."
if (!missing(filters)) {
if (!is.element("data", server_formals) && !is.null(datanames)) {
message(sprintf("module \"%s\" server function takes no data so \"datanames\" will be ignored", label))
datanames <- NULL
checkmate::assert_character(datanames, min.len = 1, null.ok = TRUE, any.missing = FALSE)
checkmate::assert_list(server_args, null.ok = TRUE, names = "named")
srv_extra_args <- setdiff(names(server_args), server_formals)
if (length(srv_extra_args) > 0 && !"..." %in% server_formals) {
stop(
"\nFollowing `server_args` elements have no equivalent in the formals of the server:\n",
paste(paste(" -", srv_extra_args), collapse = "\n"),
"\n\nUpdate the server arguments by including above or add `...`"
checkmate::assert_list(ui_args, null.ok = TRUE, names = "named")
ui_extra_args <- setdiff(names(ui_args), ui_formals)
if (length(ui_extra_args) > 0 && !"..." %in% ui_formals) {
stop(
"\nFollowing `ui_args` elements have no equivalent in the formals of UI:\n",
paste(paste(" -", ui_extra_args), collapse = "\n"),
"\n\nUpdate the UI arguments by including above or add `...`"
checkmate::assert_list(transformers, types = "teal_data_module")
structure(
list(
label = label,
server = server,
ui = ui,
datanames = unique(datanames),
server_args = server_args,
ui_args = ui_args,
transformers = transformers
class = "teal_module"
checkmate::assert_string(label)
submodules <- list(...)
if (any(vapply(submodules, is.character, FUN.VALUE = logical(1)))) {
stop(
"The only character argument to modules() must be 'label' and it must be named, ",
"change modules('lab', ...) to modules(label = 'lab', ...)"
checkmate::assert_list(submodules, min.len = 1, any.missing = FALSE, types = c("teal_module", "teal_modules"))
labels <- vapply(submodules, function(submodule) submodule$label, character(1))
names(submodules) <- get_unique_labels(labels)
structure(
list(
label = label,
children = submodules
class = "teal_modules"
paste0(paste(rep(" ", indent), collapse = ""), "+ ", x$label, "\n", collapse = "")
paste(
c(
paste0(rep(" ", indent), "+ ", x$label, "\n"),
unlist(lapply(x$children, format, indent = indent + 1, ...))
collapse = ""
checkmate::assert_class(modules, "teal_modules")
checkmate::assert_class(module, "teal_module")
modules$children <- c(modules$children, list(module))
labels <- vapply(modules$children, function(submodule) submodule$label, character(1))
names(modules$children) <- get_unique_labels(labels)
modules
if (inherits(modules, class)) {
} else if (inherits(modules, "teal_module")) {
NULL
} else if (inherits(modules, "teal_modules")) {
Filter(function(x) length(x) > 0L, lapply(modules$children, extract_module, class))
checkmate::assert_string(arg)
if (inherits(modules, "teal_modules")) {
any(unlist(lapply(modules$children, is_arg_used, arg)))
} else if (inherits(modules, "teal_module")) {
is_arg_used(modules$server, arg) || is_arg_used(modules$ui, arg)
} else if (is.function(modules)) {
isTRUE(arg %in% names(formals(modules)))
stop("is_arg_used function not implemented for this object")
checkmate::assert_multi_class(modules, c("teal_module", "teal_modules"))
checkmate::assert_int(depth, lower = 0)
if (inherits(modules, "teal_modules")) {
max(vapply(modules$children, modules_depth, integer(1), depth = depth + 1L))
depth
if (inherits(modules, "teal_modules")) {
lapply(modules$children, module_labels)
modules$label
checkmate::assert_multi_class(modules, c("teal_modules", "teal_module"))
if (inherits(modules, "teal_modules")) {
setNames(
lapply(modules$children, modules_bookmarkable),
vapply(modules$children, `[[`, "label", FUN.VALUE = character(1))
attr(modules, "teal_bookmarkable", exact = TRUE)
checkmate::check_class(teal_data, "reactive")
moduleServer(
id = id,
function(input, output, session) {
logger::log_debug("srv_data_summary initializing")
summary_table <- reactive({
req(inherits(teal_data(), "teal_data"))
if (length(teal.data::datanames(teal_data())) == 0) {
filter_overview <- get_filter_overview(teal_data)
names(filter_overview)[[1]] <- "Data Name"
filter_overview$Obs <- ifelse(
!is.na(filter_overview$obs),
sprintf("%s/%s", filter_overview$obs_filtered, filter_overview$obs),
ifelse(!is.na(filter_overview$obs_filtered), sprintf("%s", filter_overview$obs_filtered), "")
filter_overview$Subjects <- ifelse(
!is.na(filter_overview$subjects),
sprintf("%s/%s", filter_overview$subjects_filtered, filter_overview$subjects),
filter_overview <- filter_overview[, colnames(filter_overview) %in% c("Data Name", "Obs", "Subjects")]
Filter(function(col) !all(col == ""), filter_overview)
output$table <- renderUI({
summary_table_out <- try(summary_table(), silent = TRUE)
if (inherits(summary_table_out, "try-error")) {
if (!inherits(attr(summary_table_out, "condition"), "shiny.silent.error")) {
} else if (is.null(summary_table_out)) {
body_html <- apply(
summary_table_out,
1,
function(x) {
tags$tr(
tagList(
tags$td(
if (all(x[-1] == "")) {
icon(
name = "fas fa-exclamation-triangle",
title = "Unsupported dataset",
`data-container` = "body",
`data-toggle` = "popover",
`data-content` = "object not supported by the data_summary module"
x[1]
lapply(x[-1], tags$td)
header_labels <- names(summary_table())
header_html <- tags$tr(tagList(lapply(header_labels, tags$td)))
table_html <- tags$table(
class = "table custom-table",
tags$thead(header_html),
tags$tbody(body_html)
table_html
summary_table # testing purpose
datanames <- teal.data::datanames(teal_data())
joinkeys <- teal.data::join_keys(teal_data())
filtered_data_objs <- sapply(
datanames, function(name) teal.code::get_env(teal_data())[[name]],
simplify = FALSE
unfiltered_data_objs <- sapply(
datanames, function(name) teal.code::get_env(teal_data())[[paste0(name, "._raw_")]],
simplify = FALSE
rows <- lapply(
datanames,
function(dataname) {
parent <- teal.data::parent(joinkeys, dataname)
subject_keys <- if (length(parent) > 0) {
names(joinkeys[dataname, parent])
joinkeys[dataname, dataname]
get_object_filter_overview(
filtered_data = filtered_data_objs[[dataname]],
unfiltered_data = unfiltered_data_objs[[dataname]],
dataname = dataname,
subject_keys = subject_keys
unssuported_idx <- vapply(rows, function(x) all(is.na(x[-1])), logical(1)) # this is mainly for vectors
do.call(rbind, c(rows[!unssuported_idx], rows[unssuported_idx]))
if (inherits(filtered_data, c("data.frame", "DataFrame", "array", "Matrix", "SummarizedExperiment"))) {
get_object_filter_overview_array(filtered_data, unfiltered_data, dataname, subject_keys)
} else if (inherits(filtered_data, "MultiAssayExperiment")) {
data.frame(
dataname = dataname,
obs = NA,
obs_filtered = NA,
subjects = NA,
subjects_filtered = NA
if (length(subject_keys) == 0) {
data.frame(
dataname = dataname,
obs = ifelse(!is.null(nrow(unfiltered_data)), nrow(unfiltered_data), NA),
obs_filtered = nrow(filtered_data),
subjects = NA,
subjects_filtered = NA
data.frame(
dataname = dataname,
obs = ifelse(!is.null(nrow(unfiltered_data)), nrow(unfiltered_data), NA),
obs_filtered = nrow(filtered_data),
subjects = nrow(unique(unfiltered_data[subject_keys])),
subjects_filtered = nrow(unique(filtered_data[subject_keys]))
lifecycle::deprecate_soft(
when = "0.15.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",
types = c("data.frame", "reactive", "MultiAssayExperiment")
checkmate::assert_class(join_keys, "join_keys", null.ok = TRUE)
checkmate::assert_multi_class(code, c("character", "reactive"))
checkmate::assert_list(metadata, names = "unique", null.ok = TRUE)
checkmate::assert_subset(names(metadata), names(data))
if (is.reactive(code)) {
isolate(checkmate::assert_class(code(), "character", .var.name = "code"))
for (x in names(data)) {
if (!is.reactive(data[[x]])) {
data[[x]] <- do.call(reactive, list(as.name(x)), envir = list2env(data[x]))
attr(data, "code") <- if (is.reactive(code)) code else reactive(code)
attr(data, "join_keys") <- join_keys
attr(data, "metadata") <- metadata
class(data) <- c("tdata", class(data))
data
checkmate::assert_class(data, "tdata")
list2env(lapply(data, function(x) if (is.reactive(x)) x() else x))
checkmate::assert_class(data, "tdata")
attr(data, "code")()
attr(data, "join_keys")
checkmate::assert_string(dataname)
UseMethod("get_metadata", data)
metadata <- attr(data, "metadata")
if (is.null(metadata)) {
return(NULL)
metadata[[dataname]]
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)
logger::log_debug("init initializing teal app with: data ('{ class(data) }').")
if (inherits(data, "TealData")) {
checkmate::assert_multi_class(data, c("teal_data", "teal_data_module"))
checkmate::assert_class(landing_popup, "teal_module_landing", null.ok = TRUE)
checkmate::assert(
.var.name = "modules",
checkmate::check_multi_class(modules, c("teal_modules", "teal_module")),
checkmate::check_list(modules, min.len = 1, any.missing = FALSE, types = c("teal_module", "teal_modules"))
if (inherits(modules, "teal_module")) {
modules <- list(modules)
if (checkmate::test_list(modules, min.len = 1, any.missing = FALSE, types = c("teal_module", "teal_modules"))) {
modules <- do.call(teal::modules, modules)
checkmate::assert_class(filter, "teal_slices")
checkmate::assert(
.var.name = "title",
checkmate::check_string(title),
checkmate::check_multi_class(title, c("shiny.tag", "shiny.tag.list", "html"))
checkmate::assert(
.var.name = "header",
checkmate::check_string(header),
checkmate::check_multi_class(header, c("shiny.tag", "shiny.tag.list", "html"))
checkmate::assert(
.var.name = "footer",
checkmate::check_string(footer),
checkmate::check_multi_class(footer, c("shiny.tag", "shiny.tag.list", "html"))
checkmate::assert_character(id, max.len = 1, any.missing = FALSE)
teal.logger::log_system_info()
teal_lockfile()
landing <- extract_module(modules, "teal_module_landing")
if (length(landing) == 1L) {
} else if (length(landing) > 1L) {
if (is.null(attr(filter, "app_id", exact = TRUE))) attr(filter, "app_id") <- create_app_id(data, modules)
filter <- as.teal_slices(as.list(filter))
if (isTRUE(attr(filter, "module_specific"))) {
if (inherits(data, "teal_data")) {
if (length(.teal_data_datanames(data)) == 0) {
stop("The environment of `data` is empty.")
if (!length(teal.data::datanames(data))) {
is_modules_ok <- check_modules_datanames(modules, .teal_data_datanames(data))
if (!isTRUE(is_modules_ok)) {
is_filter_ok <- check_filter_datanames(filter, .teal_data_datanames(data))
if (!isTRUE(is_filter_ok)) {
warning(is_filter_ok)
reporter <- teal.reporter::Reporter$new()$set_id(attr(filter, "app_id"))
if (is_arg_used(modules, "reporter") && length(extract_module(modules, "teal_module_previewer")) == 0) {
ns <- NS(id)
res <- list(
ui = function(request) {
server = function(input, output, session) {
logger::log_debug("init teal app has been initialized.")
res
checkmate::assert_character(id, max.len = 1, any.missing = FALSE)
checkmate::assert_multi_class(data, c("teal_data", "teal_data_module", "reactive", "reactiveVal"))
checkmate::assert_class(modules, "teal_modules")
checkmate::assert_class(filter, "teal_slices")
moduleServer(id, function(input, output, session) {
logger::log_debug("srv_data initializing.")
if (getOption("teal.show_js_log", default = FALSE)) {
data_validated <- if (inherits(data, "teal_data_module")) {
srv_teal_data(
"teal_data_module",
data = reactive(req(FALSE)), # to .fallback_on_failure to shiny.silent.error
data_module = data,
modules = modules,
validate_shiny_silent_error = FALSE
} else if (inherits(data, "teal_data")) {
reactiveVal(data)
} else if (inherits(data, c("reactive", "reactiveVal"))) {
.fallback_on_failure(this = data, that = reactive(req(FALSE)), label = "Reactive data")
if (inherits(data, "teal_data_module")) {
shinyjs::disable(selector = sprintf(".teal-body:has('#%s') .nav li a", session$ns("content")))
observeEvent(data_validated(), {
showNotification("Data loaded successfully.", duration = 5)
shinyjs::enable(selector = sprintf(".teal-body:has('#%s') .nav li a", session$ns("content")))
if (isTRUE(attr(data, "once"))) {
shinyjs::hide(
selector = sprintf(
".teal-body:has('#%s') a[data-value='teal_data_module']",
session$ns("content")
shinyjs::runjs(
sprintf(
"document.querySelector('.teal-body:has(#%s) .nav li:nth-child(2) a').click();",
session$ns("content")
is_filter_ok <- check_filter_datanames(filter, .teal_data_datanames(data_validated()))
if (!isTRUE(is_filter_ok)) {
showNotification(
"Some filters were not applied because of incompatibility with data. Contact app developer.",
type = "warning",
duration = 10
warning(is_filter_ok)
observeEvent(data_validated(), once = TRUE, {
app_session <- .subset2(shiny::getDefaultReactiveDomain(), "parent")
setBookmarkExclude(
session$ns(
grep(
pattern = "teal_data_module-",
x = names(reactiveValuesToList(input)),
value = TRUE
session = app_session
reactive(.add_signature_to_data(data_validated()))
hashes <- .get_hashes_code(data)
tdata <- do.call(
teal.data::teal_data,
c(
list(code = trimws(c(teal.code::get_code(data), hashes), which = "right")),
list(join_keys = teal.data::join_keys(data)),
sapply(
ls(teal.code::get_env(data)),
teal.code::get_var,
object = data,
simplify = FALSE
tdata@verified <- data@verified
teal.data::datanames(tdata) <- teal.data::datanames(data)
tdata
vapply(
datanames,
function(dataname, datasets) {
hash <- rlang::hash(data[[dataname]])
sprintf(
"stopifnot(%s == %s) # @linksto %s",
deparse1(bquote(rlang::hash(.(as.name(dataname))))),
deparse1(hash),
dataname
character(1L),
USE.NAMES = TRUE
checkmate::assert_string(id)
checkmate::assert_class(data_rv, "reactive")
checkmate::assert_multi_class(modules, c("teal_modules", "teal_module"))
checkmate::assert_class(datasets, "reactive", null.ok = TRUE)
checkmate::assert_class(slices_global, ".slicesGlobal")
checkmate::assert_class(reporter, "Reporter")
UseMethod("srv_teal_module", modules)
moduleServer(id = id, module = function(input, output, session) {
logger::log_debug("srv_teal_module.teal_modules initializing the module { deparse1(modules$label) }.")
modules_output <- sapply(
names(modules$children),
function(module_id) {
srv_teal_module(
id = module_id,
data_rv = data_rv,
modules = modules$children[[module_id]],
datasets = datasets,
slices_global = slices_global,
reporter = reporter,
is_active = reactive(is_active() && input$active_tab == module_id)
simplify = FALSE
modules_output
logger::log_debug("srv_teal_module.teal_module initializing the module: { deparse1(modules$label) }.")
moduleServer(id = id, module = function(input, output, session) {
active_datanames <- reactive(.resolve_module_datanames(data = data_rv(), modules = modules))
if (is.null(datasets)) {
datasets <- eventReactive(data_rv(), {
if (!inherits(data_rv(), "teal_data")) {
logger::log_debug("srv_teal_module@1 initializing module-specific FilteredData")
teal_data_to_filtered_data(data_rv(), datanames = active_datanames())
srv_module_filter_manager(modules$label, module_fd = datasets, slices_global = slices_global)
filtered_teal_data <- srv_filter_data(
"filter_panel",
datasets = datasets,
active_datanames = active_datanames,
data_rv = data_rv,
is_active = is_active
transformed_teal_data <- srv_transform_data(
"data_transform",
data = filtered_teal_data,
transforms = modules$transformers,
modules = modules
module_teal_data <- reactive({
all_teal_data <- transformed_teal_data()
module_datanames <- .resolve_module_datanames(data = all_teal_data, modules = modules)
.subset_teal_data(all_teal_data, module_datanames)
module_teal_data_validated <- srv_validate_reactive_teal_data(
"validate_datanames",
data = module_teal_data,
modules = modules
summary_table <- srv_data_summary("data_summary", module_teal_data)
module_out <- reactiveVal(NULL)
if (!inherits(modules, "teal_module_previewer")) {
obs_module <- observeEvent(
ignoreNULL = TRUE,
once = TRUE,
eventExpr = module_teal_data_validated(),
handlerExpr = {
module_out(.call_teal_module(modules, datasets, module_teal_data_validated, reporter))
if ("report" %in% names(module_out)) {
module_out
args <- c(list(id = "module"), modules$server_args)
if (is_arg_used(modules$server, "reporter")) {
args <- c(args, list(reporter = reporter))
if (is_arg_used(modules$server, "datasets")) {
args <- c(args, datasets = datasets())
warning("datasets argument is not reactive and therefore it won't be updated when data is refreshed.")
if (is_arg_used(modules$server, "data")) {
args <- c(args, data = list(filtered_teal_data))
if (is_arg_used(modules$server, "filter_panel_api")) {
args <- c(args, filter_panel_api = teal.slice::FilterPanelAPI$new(datasets()))
if (is_arg_used(modules$server, "id")) {
do.call(modules$server, args)
stopifnot("data_rv must be teal_data object." = inherits(data, "teal_data"))
if (is.null(modules$datanames) || identical(modules$datanames, "all")) {
.teal_data_datanames(data)
intersect(
include_parent_datanames(modules$datanames, teal.data::join_keys(data)),
.teal_data_ls(data)
checkmate::assert_string(label)
ans <- module(
label,
server = function(id, data) {
checkmate::assert_class(isolate(data()), "teal_data")
moduleServer(id, function(input, output, session) {
datanames_rv <- reactive({
teal.data::datanames(req(data()))
observeEvent(datanames_rv(), {
selected <- isolate(input$dataname)
updateSelectInput(
session = session,
inputId = "dataname",
choices = datanames_rv(),
selected = selected
output$text <- renderPrint({
req(input$dataname)
teal.widgets::verbatim_popup_srv(
id = "rcode",
verbatim_content = reactive(teal.code::get_code(data())),
title = "Example Code"
ui = function(id) {
datanames = datanames,
transformers = transformers
attr(ans, "teal_bookmarkable") <- TRUE
ans
script <- sprintf(
"Shiny.setInputValue(`%s`, Intl.DateTimeFormat().resolvedOptions().timeZone)",
ns("timezone")
shinyjs::runjs(script) # function does not return anything
invisible(NULL)
bs_theme <- getOption("teal.bs_theme")
if (is.null(bs_theme)) {
NULL
} else if (!inherits(bs_theme, "bs_theme")) {
warning("teal.bs_theme has to be of a bslib::bs_theme class, the default shiny bootstrap is used.")
NULL
bs_theme
parents <- character(0)
for (i in dataname) {
while (length(i) > 0) {
parent_i <- teal.data::parent(join_keys, i)
parents <- c(parent_i, parents)
i <- parent_i
unique(c(parents, dataname))
checkmate::assert_class(x, "teal_data")
checkmate::assert_character(datanames, min.chars = 1L, any.missing = FALSE)
teal.slice::init_filtered_data(
x = sapply(datanames, function(dn) x[[dn]], simplify = FALSE),
join_keys = teal.data::join_keys(x)
checkmate::assert_string(title)
checkmate::assert_string(label)
checkmate::assert_string(description, null.ok = TRUE)
checkmate::assert_flag(with_filter)
checkmate::assert_class(filter_panel_api, classes = "FilterPanelAPI")
card <- teal::TealReportCard$new()
title <- if (label == "") title else label
card$set_name(title)
card$append_text(title, "header2")
if (!is.null(description)) card$append_text(description, "header3")
if (with_filter) card$append_fs(filter_panel_api$get_filter_state())
card
checkmate::assert_multi_class(modules, c("teal_modules", "teal_module"))
checkmate::assert_character(datanames)
recursive_check_datanames <- function(modules, datanames) {
if (inherits(modules, "teal_modules")) {
sapply(modules$children, function(module) recursive_check_datanames(module, datanames = datanames))
extra_datanames <- setdiff(modules$datanames, c("all", datanames))
if (length(extra_datanames)) {
sprintf(
"Module '%s' uses datanames not available in 'data': (%s) not in (%s)",
modules$label,
toString(dQuote(extra_datanames, q = FALSE)),
toString(dQuote(datanames, q = FALSE))
check_datanames <- unlist(recursive_check_datanames(modules, datanames))
if (length(check_datanames)) {
paste(check_datanames, collapse = "\n")
TRUE
checkmate::assert_class(filters, "teal_slices")
checkmate::assert_character(datanames)
out <- unlist(sapply(
filters, function(filter) {
dataname <- shiny::isolate(filter$dataname)
if (!dataname %in% datanames) {
sprintf(
"- Filter '%s' refers to dataname not available in 'data':\n %s not in (%s)",
shiny::isolate(filter$id),
dQuote(dataname, q = FALSE),
toString(dQuote(datanames, q = FALSE))
if (length(out)) {
paste(out, collapse = "\n")
TRUE
checkmate::assert_class(shiny_tag, "shiny.tag")
checkmate::assert_true(shiny_tag$name == "head")
child_names <- vapply(shiny_tag$children, `[[`, character(1L), "name")
checkmate::assert_subset(c("title", "link"), child_names, .var.name = "child tags")
rel_attr <- shiny_tag$children[[which(child_names == "link")]]$attribs$rel
checkmate::assert_subset(
rel_attr,
c("icon", "shortcut icon"),
.var.name = "Link tag's rel attribute",
empty.ok = FALSE
checkmate::assert_string(title, null.ok = TRUE)
checkmate::assert_string(favicon, null.ok = TRUE)
tags$head(
tags$title(title),
tags$link(
rel = "icon",
href = favicon,
sizes = "any"
checkmate::assert_multi_class(data, c("teal_data", "teal_data_module"))
checkmate::assert_class(modules, "teal_modules")
data <- if (inherits(data, "teal_data")) {
as.list(teal.code::get_env(data))
} else if (inherits(data, "teal_data_module")) {
deparse1(body(data$server))
modules <- lapply(modules, defunction)
rlang::hash(list(data = data, modules = modules))
if (is.list(x)) {
lapply(x, defunction)
} else if (is.function(x)) {
deparse1(body(x))
x
make.unique(gsub("[^[:alnum:]]", "_", tolower(labels)), sep = "_")
checkmate::assert_string(string)
gsub(
"(?:(?:\\x{001b}\\[)|\\x{009b})(?:(?:[0-9]{1,3})?(?:(?:;[0-9]{0,3})*)?[A-M|f-m])|\\x{001b}[A-M]",
string,
perl = TRUE,
useBytes = TRUE
checkmate::assert_class(data, "teal_data")
data@code <- c(data@code, code)
data@id <- c(data@id, max(data@id) + 1L + seq_along(code))
data@messages <- c(data@messages, rep("", length(code)))
data@warnings <- c(data@warnings, rep("", length(code)))
methods::validObject(data)
data
checkmate::assert_class(data, "teal_data")
checkmate::assert_class(objects, "list")
new_env <- list2env(objects, parent = .GlobalEnv)
rlang::env_coalesce(new_env, data@env)
data@env <- new_env
data
checkmate::assert_class(data, "teal_data")
checkmate::assert_class(datanames, "character")
datanames_corrected <- intersect(datanames, ls(data@env))
dataname_corrected_with_raw <- intersect(c(datanames, sprintf("%s._raw_", datanames)), ls(data@env))
if (!length(datanames)) {
new_data <- do.call(
teal.data::teal_data,
args = c(
mget(x = dataname_corrected_with_raw, envir = data@env),
list(
code = gsub(
"warning('Code was not verified for reproducibility.')\n",
teal.data::get_code(data, datanames = dataname_corrected_with_raw),
fixed = TRUE
join_keys = teal.data::join_keys(data)[datanames_corrected]
new_data@verified <- data@verified
teal.data::datanames(new_data) <- datanames_corrected
new_data
checkmate::assert_class(data, "teal_data")
datanames <- teal.data::datanames(data)
if (length(datanames)) {
datanames
.teal_data_ls(data)
grep("._raw_", ls(teal.code::get_env(data), all.names = TRUE), value = TRUE, invert = TRUE)
checkmate::assert_class(datasets, "reactive")
moduleServer(id, function(input, output, session) {
output$panel <- renderUI({
req(inherits(datasets(), "FilteredData"))
isolate({
logger::log_debug("srv_filter_panel rendering filter panel.")
if (length(active_datanames())) {
datasets()$srv_active("filters", active_datanames = active_datanames)
datasets()$ui_active(session$ns("filters"), active_datanames = active_datanames)
trigger_data <- .observe_active_filter_changed(datasets, is_active, active_datanames, data_rv)
eventReactive(trigger_data(), {
.make_filtered_teal_data(modules, data = data_rv(), datasets = datasets(), datanames = active_datanames())
data <- eval_code(data, sprintf("%1$s._raw_ <- %1$s", datanames))
filtered_code <- teal.slice::get_filter_expr(datasets = datasets, datanames = datanames)
filtered_teal_data <- .append_evaluated_code(data, filtered_code)
filtered_datasets <- sapply(datanames, function(x) datasets$get_data(x, filtered = TRUE), simplify = FALSE)
filtered_teal_data <- .append_modified_data(filtered_teal_data, filtered_datasets)
filtered_teal_data
previous_signature <- reactiveVal(NULL)
filter_changed <- reactive({
req(inherits(datasets(), "FilteredData"))
new_signature <- c(
teal.data::get_code(data_rv()),
teal.slice::get_filter_expr(datasets = datasets(), datanames = active_datanames())
if (!identical(previous_signature(), new_signature)) {
previous_signature(new_signature)
TRUE
FALSE
trigger_data <- reactiveVal(NULL)
observe({
if (isTRUE(is_active() && filter_changed())) {
isolate({
if (is.null(trigger_data())) {
trigger_data(0)
trigger_data(trigger_data() + 1)
trigger_data
moduleServer(id, function(input, output, session) {
logger::log_debug("srv_snapshot_manager_panel initializing")
setBookmarkExclude(c("show_snapshot_manager"))
observeEvent(input$show_snapshot_manager, {
srv_snapshot_manager("module", slices_global = slices_global)
checkmate::assert_character(id)
moduleServer(id, function(input, output, session) {
logger::log_debug("srv_snapshot_manager initializing")
setBookmarkExclude(c(
"snapshot_add", "snapshot_load", "snapshot_reset",
"snapshot_name_accept", "snaphot_file_accept",
"snapshot_name", "snapshot_file"
session$onBookmark(function(state) {
ns <- session$ns
snapshot_history <- reactiveVal({
restoreValue(
ns("snapshot_history"),
list("Initial application state" = shiny::isolate(as.list(slices_global$all_slices(), recursive = TRUE)))
observeEvent(input$snapshot_add, {
observeEvent(input$snapshot_name_accept, {
observeEvent(input$snapshot_load, {
observeEvent(input$snaphot_file_accept, {
observeEvent(input$snapshot_reset, {
logger::log_debug("srv_snapshot_manager: snapshot_reset button clicked, restoring snapshot")
s <- "Initial application state"
snapshot <- snapshot_history()[[s]]
snapshot_state <- as.teal_slices(snapshot)
slices_global$slices_set(snapshot_state)
removeModal()
observers <- reactiveValues()
handlers <- reactiveValues()
divs <- reactiveValues()
observeEvent(snapshot_history(), {
logger::log_debug("srv_snapshot_manager: snapshot history modified, updating snapshot list")
lapply(names(snapshot_history())[-1L], function(s) {
output$snapshot_list <- renderUI({
rows <- rev(reactiveValuesToList(divs))
if (length(rows) == 0L) {
tags$div(
class = "manager_placeholder",
"Snapshots will appear here."
snapshot_history
checkmate::assert_string(id)
checkmate::assert_class(data, "reactive")
checkmate::assert_class(data_module, "teal_data_module")
checkmate::assert_multi_class(modules, c("teal_modules", "teal_module"), null.ok = TRUE)
moduleServer(id, function(input, output, session) {
logger::log_debug("srv_teal_data initializing.")
data_out <- if (is_arg_used(data_module$server, "data")) {
data_module$server(id = "data", data = data)
data_module$server(id = "data")
data_validated <- srv_validate_reactive_teal_data(
id = "validate",
data = data_out,
modules = modules,
validate_shiny_silent_error = validate_shiny_silent_error
.fallback_on_failure(
this = data_validated,
that = data,
label = sprintf("Data element '%s' for module '%s'", id, modules$label)
moduleServer(id, function(input, output, session) {
if (!is.reactive(data)) {
stop("The `teal_data_module` passed to `data` must return a reactive expression.", call. = FALSE)
data_out_rv <- reactive(tryCatch(data(), error = function(e) e))
data_validated <- reactive({
data_out <- data_out_rv()
if (inherits(data_out, "shiny.silent.error") && identical(data_out$message, "")) {
if (!validate_shiny_silent_error) {
return(NULL)
if (inherits(data_out, c("qenv.error", "error"))) {
validate(
need(
FALSE,
paste(
"Error when executing `teal_data_module` passed to `data`:\n ",
strip_style(paste(data_out$message, collapse = "\n")),
"\n Check your inputs or contact app developer if error persists."
validate(
need(
inherits(data_out, "teal_data"),
paste(
"Error: `teal_data_module` passed to `data` failed to return `teal_data` object, returned",
strip_style(toString(sQuote(class(data_out)))),
"instead.",
"\n Check your inputs or contact app developer if error persists."
data_out
output$shiny_errors <- renderUI({
data_validated()
NULL
output$shiny_warnings <- renderUI({
if (inherits(data_out_rv(), "teal_data")) {
is_modules_ok <- check_modules_datanames(modules = modules, datanames = .teal_data_ls(data_validated()))
if (!isTRUE(is_modules_ok)) {
tags$div(is_modules_ok, class = "teal-output-warning")
data_validated
checkmate::assert_class(this, "reactive")
checkmate::assert_class(that, "reactive")
checkmate::assert_string(label)
reactive({
res <- try(this(), silent = TRUE)
if (inherits(res, "teal_data")) {
logger::log_debug("{ label } evaluated successfully.")
res
logger::log_debug("{ label } failed, falling back to previous data.")
that()
dots <- list(...)
if (!is_validators(dots)) stop("validate_inputs accepts validators or a list thereof")
messages <- extract_validator(dots, header)
failings <- if (!any_names(dots)) {
add_header(messages, header)
unlist(messages)
shiny::validate(shiny::need(is.null(failings), failings))
all(if (is.list(x)) unlist(lapply(x, is_validators)) else inherits(x, "InputValidator"))
x$.__enclos_env__$private$enabled
if (inherits(iv, "InputValidator")) {
add_header(gather_messages(iv), header)
if (is.null(names(iv))) names(iv) <- rep("", length(iv))
mapply(extract_validator, iv = iv, header = names(iv), SIMPLIFY = FALSE)
if (validator_enabled(iv)) {
status <- iv$validate()
failing_inputs <- Filter(Negate(is.null), status)
unique(lapply(failing_inputs, function(x) x[["message"]]))
warning("Validator is disabled and will be omitted.")
list()
ans <- unlist(messages)
if (length(ans) != 0L && isTRUE(nchar(header) > 0L)) {
ans <- c(paste0(header, "\n"), ans, "\n")
ans
any(
if (is.list(x)) {
if (!is.null(names(x)) && any(names(x) != "")) TRUE else unlist(lapply(x, any_names))
FALSE
lockfile_path <- "teal_app.lock"
user_lockfile <- getOption("teal.renv.lockfile", "")
if (!identical(user_lockfile, "")) {
if (!(is_in_test() || is_r_cmd_check())) {
checkmate::assert_flag(close)
checkmate::assert_string(lockfile_path, na.ok = TRUE)
promise <- promises::future_promise({
shiny::onStop(function() file.remove(lockfile_path))
renv_logs <- utils::capture.output(
renv::snapshot(
lockfile = lockfile_path,
prompt = FALSE,
force = TRUE
if (any(grepl("Lockfile written", renv_logs))) {
logger::log_debug("lockfile created with issues.")
lockfile_path
if (close) {
promises::then(promise, onFulfilled = function() {
future::plan(future::sequential)
promise
downloadHandler(
filename = function() {
content = function(file) {
contentType = "application/json"
identical(Sys.getenv("TESTTHAT"), "true")
checkmate::assert_class(tss, "teal_slices")
checkmate::assert_path_for_output(file, overwrite = TRUE, extension = "json")
cat(format(tss, trim_lines = FALSE), "\n", file = file)
checkmate::assert_file_exists(file, access = "r", extension = "json")
tss_json <- jsonlite::fromJSON(file, simplifyDataFrame = FALSE)
tss_json$slices <-
lapply(tss_json$slices, function(slice) {
for (field in c("selected", "choices")) {
if (!is.null(slice[[field]])) {
if (length(slice[[field]]) > 0) {
date_partial_regex <- "^[0-9]{4}-[0-9]{2}-[0-9]{2}"
time_stamp_regex <- paste0(date_partial_regex, "\\s[0-9]{2}:[0-9]{2}:[0-9]{2}\\sUTC$")
slice[[field]] <-
if (all(grepl(paste0(date_partial_regex, "$"), slice[[field]]))) {
as.Date(slice[[field]])
} else if (all(grepl(time_stamp_regex, slice[[field]]))) {
as.POSIXct(slice[[field]], tz = "UTC")
slice[[field]]
slice[[field]] <- character(0)
slice
tss_elements <- lapply(tss_json$slices, as.teal_slice)
do.call(teal_slices, c(tss_elements, tss_json$attributes))
checkmate::assert_string(id)
checkmate::assert_class(data, "reactive")
checkmate::assert_list(transforms, "teal_transform_module", null.ok = TRUE)
checkmate::assert_class(modules, "teal_module")
if (length(transforms) == 0L) {
return(data)
labels <- lapply(transforms, function(x) attr(x, "label"))
ids <- get_unique_labels(labels)
names(transforms) <- ids
moduleServer(id, function(input, output, session) {
logger::log_debug("srv_teal_data_modules initializing.")
Reduce(
function(previous_result, name) {
srv_teal_data(
id = name,
data = previous_result,
data_module = transforms[[name]],
modules = modules
x = names(transforms),
init = data
checkmate::assert_character(src, min.len = 0, max.len = 1)
params <- list(...)
params$eval <- FALSE
rblock <- RcodeBlock$new(src)
rblock$set_params(params)
self$append_content(rblock)
self$append_metadata("SRC", src)
invisible(self)
checkmate::assert_class(fs, "teal_slices")
self$append_text("Filter State", "header3")
if (length(fs)) {
self$append_content(TealSlicesBlock$new(fs))
self$append_text("No filters specified.")
invisible(self)
checkmate::assert_list(encodings)
self$append_text("Selected Options", "header3")
if (requireNamespace("yaml", quietly = TRUE)) {
self$append_text(yaml::as.yaml(encodings, handlers = list(
POSIXct = function(x) format(x, "%Y-%m-%d"),
POSIXlt = function(x) format(x, "%Y-%m-%d"),
Date = function(x) format(x, "%Y-%m-%d")
)), "verbatim")
self$append_metadata("Encodings", encodings)
invisible(self)
self$set_content(content)
self$set_style(style)
invisible(self)
checkmate::assert_class(content, "teal_slices")
if (length(content) != 0) {
states_list <- lapply(content, function(x) {
x_list <- shiny::isolate(as.list(x))
if (
inherits(x_list$choices, c("integer", "numeric", "Date", "POSIXct", "POSIXlt")) &&
length(x_list$choices) == 2 &&
length(x_list$selected) == 2
if (!is.null(x_list$arg)) {
x_list <- x_list[
c("dataname", "varname", "experiment", "arg", "expr", "selected", "range", "keep_na", "keep_inf")
names(x_list) <- c(
"Dataset name", "Variable name", "Experiment", "Filtering by", "Applied expression",
"Selected Values", "Selected range", "Include NA values", "Include Inf values"
Filter(Negate(is.null), x_list)
if (requireNamespace("yaml", quietly = TRUE)) {
super$set_content(yaml::as.yaml(states_list))
private$teal_slices <- content
invisible(self)
checkmate::assert_list(x)
checkmate::assert_names(names(x), must.include = c("text", "style"))
super$set_content(x$text)
super$set_style(x$style)
invisible(self)
content <- self$get_content()
list(
text = if (length(content)) content else "",
style = self$get_style()
checkmate::assert_character(id)
checkmate::assert_class(modules, "teal_modules")
moduleServer(id, function(input, output, session) {
logger::log_debug("bookmark_manager_srv initializing")
ns <- session$ns
bookmark_option <- get_bookmarking_option()
is_unbookmarkable <- need_bookmarking(modules)
setBookmarkExclude(c("do_bookmark"))
app_session <- .subset2(session, "parent")
app_session$onBookmarked(function(url) {
observeEvent(input$do_bookmark, {
invisible(NULL)
bookmark_option <- getShinyOption("bookmarkStore")
if (is.null(bookmark_option) && identical(getOption("shiny.bookmarkStore"), "server")) {
bookmark_option
unlist(rapply2(
modules_bookmarkable(modules),
Negate(isTRUE)
checkmate::assert_character("value")
session_default <- shiny::getDefaultReactiveDomain()
session_parent <- .subset2(session_default, "parent")
session <- if (is.null(session_parent)) session_default else session_parent
if (isTRUE(session$restoreContext$active) && exists(value, session$restoreContext$values, inherits = FALSE)) {
default
if (inherits(x, "list")) {
lapply(x, rapply2, f = f)
f(x)
checkmate::assert_character(files, min.len = 1, any.missing = FALSE)
lapply(files, function(file) {
shinyjs::runjs(paste0(readLines(system.file("js", file, package = "teal", mustWork = TRUE)), collapse = "\n"))
invisible(NULL)
checkmate::assert_string(msg, null.ok = TRUE)
checkmate::assert_data_frame(x)
if (!is.null(min_nrow)) {
if (complete) {
complete_index <- stats::complete.cases(x)
validate(need(
sum(complete_index) > 0 && nrow(x[complete_index, , drop = FALSE]) >= min_nrow,
paste(c(paste("Number of complete cases is less than:", min_nrow), msg), collapse = "\n")
validate(need(
nrow(x) >= min_nrow,
paste(
c(paste("Minimum number of records not met: >=", min_nrow, "records required."), msg),
collapse = "\n"
if (!allow_inf) {
validate(need(
all(vapply(x, function(col) !is.numeric(col) || !any(is.infinite(col)), logical(1))),
"Dataframe contains Inf values which is not allowed."
checkmate::assert_string(label)
checkmate::assert_list(server_args, names = "named")
checkmate::assert_true(all(names(server_args) %in% names(formals(teal.reporter::reporter_previewer_srv))))
message("Initializing reporter_previewer_module")
srv <- function(id, reporter, ...) {
ui <- function(id, ...) {
module <- module(
label = "temporary label",
server = srv, ui = ui,
server_args = server_args, ui_args = list(), datanames = NULL
class(module) <- c("teal_module_previewer", class(module))
module$label <- label
attr(module, "teal_bookmarkable") <- TRUE
module
teal_data_module(
ui = function(id) {
ns <- NS(id)
object$ui(ns("mutate_inner"))
server = function(id) {
moduleServer(id, function(input, output, session) {
teal_data_rv <- object$server("mutate_inner")
if (!is.reactive(teal_data_rv)) {
stop("The `teal_data_module` must return a reactive expression.", call. = FALSE)
td <- eventReactive(teal_data_rv(),
if (inherits(teal_data_rv(), c("teal_data", "qenv.error"))) {
eval_code(teal_data_rv(), code)
teal_data_rv()
ignoreNULL = FALSE
td
eval_code(object, code = paste(lang2calls(code), collapse = "\n"))
eval_code(object, code = paste(lang2calls(code), collapse = "\n"))
checkmate::assert_function(ui, args = "id", nargs = 1)
checkmate::assert_function(server, args = "id", nargs = 1)
structure(
list(ui = ui, server = server),
label = label,
class = "teal_data_module",
once = once
checkmate::assert_function(ui, args = "id", nargs = 1)
checkmate::assert_function(server, args = c("id", "data"), nargs = 2)
structure(
list(ui = ui, server = server),
label = label,
class = c("teal_transform_module", "teal_data_module")
libraries <- vapply(
utils::sessionInfo()$otherPkgs,
function(x) {
paste0("library(", x$Package, ")")
character(1)
paste0(paste0(rev(libraries), sep = "\n"), collapse = "")
code_string <- getOption("teal.load_nest_code")
if (is.character(code_string)) {
code_string
"# Add any code to install/load your NEST environment here\n"
expr <- substitute(expr)
extras <- list(...)
if (!identical(as.list(expr)[[1L]], as.symbol("{"))) {
expr <- call("{", expr)
calls <- as.list(expr)[-1]
calls <- lapply(calls, function(x) do.call(substitute, list(x, env = extras)))
eval_code(object = data, code = as.expression(calls))
packageStartupMessage(
"\nYou are using teal version ",
read.dcf(system.file("DESCRIPTION", package = "teal"))[, "Version"]
script <- sprintf(
"Shiny.setInputValue(`%s`, Intl.DateTimeFormat().resolvedOptions().timeZone)",
ns("timezone")
shinyjs::runjs(script) # function does not return anything
return(invisible(NULL))
bs_theme <- getOption("teal.bs_theme")
if (is.null(bs_theme)) {
NULL
} else if (!inherits(bs_theme, "bs_theme")) {
warning("teal.bs_theme has to be of a bslib::bs_theme class, the default shiny bootstrap is used.")
NULL
bs_theme
parents <- character(0)
for (i in dataname) {
while (length(i) > 0) {
parent_i <- teal.data::parent(join_keys, i)
parents <- c(parent_i, parents)
i <- parent_i
return(unique(c(parents, dataname)))
checkmate::assert_class(x, "teal_data")
checkmate::assert_character(datanames, min.len = 1L, any.missing = FALSE)
checkmate::assert_subset(datanames, teal.data::datanames(x))
ans <- teal.slice::init_filtered_data(
x = sapply(datanames, function(dn) x[[dn]], simplify = FALSE),
join_keys = teal.data::join_keys(x)
attr(ans, "preprocessing_code") <- teal.code::get_code(x)
ans
checkmate::assert_string(title)
checkmate::assert_string(label)
checkmate::assert_string(description, null.ok = TRUE)
checkmate::assert_flag(with_filter)
checkmate::assert_class(filter_panel_api, classes = "FilterPanelAPI")
card <- teal::TealReportCard$new()
title <- if (label == "") title else label
card$set_name(title)
card$append_text(title, "header2")
if (!is.null(description)) card$append_text(description, "header3")
if (with_filter) card$append_fs(filter_panel_api$get_filter_state())
card
checkmate::assert_class(modules, "teal_modules")
checkmate::assert_character(datanames)
recursive_check_datanames <- function(modules, datanames) {
if (inherits(modules, "teal_modules")) {
sapply(modules$children, function(module) recursive_check_datanames(module, datanames = datanames))
extra_datanames <- setdiff(modules$datanames, c("all", datanames))
if (length(extra_datanames)) {
sprintf(
"- Module '%s' uses datanames not available in 'data': (%s) not in (%s)",
modules$label,
toString(dQuote(extra_datanames, q = FALSE)),
toString(dQuote(datanames, q = FALSE))
check_datanames <- unlist(recursive_check_datanames(modules, datanames))
if (length(check_datanames)) {
paste(check_datanames, collapse = "\n")
TRUE
checkmate::assert_class(filters, "teal_slices")
checkmate::assert_character(datanames)
out <- unlist(sapply(
filters, function(filter) {
dataname <- shiny::isolate(filter$dataname)
if (!dataname %in% datanames) {
sprintf(
"- Filter '%s' refers to dataname not available in 'data':\n %s not in (%s)",
shiny::isolate(filter$id),
dQuote(dataname, q = FALSE),
toString(dQuote(datanames, q = FALSE))
if (length(out)) {
paste(out, collapse = "\n")
TRUE
checkmate::assert_character(id)
checkmate::assert_true(is.reactive(slices_global))
checkmate::assert_class(isolate(slices_global()), "teal_slices")
checkmate::assert_true(is.reactive(mapping_matrix))
checkmate::assert_data_frame(isolate(mapping_matrix()), null.ok = TRUE)
checkmate::assert_list(filtered_data_list, types = "FilteredData", any.missing = FALSE, names = "named")
moduleServer(id, function(input, output, session) {
ns <- session$ns
filter <- isolate(slices_global())
snapshot_history <- reactiveVal({
list(
"Initial application state" = as.list(filter, recursive = TRUE)
observeEvent(input$snapshot_add, {
observeEvent(input$snapshot_name_accept, {
observeEvent(input$snapshot_load, {
observeEvent(input$snaphot_file_accept, {
observeEvent(input$snapshot_reset, {
observers <- reactiveValues()
handlers <- reactiveValues()
divs <- reactiveValues()
observeEvent(snapshot_history(), {
lapply(names(snapshot_history())[-1L], function(s) {
output$snapshot_list <- renderUI({
rows <- lapply(rev(reactiveValuesToList(divs)), function(d) d)
if (length(rows) == 0L) {
div(
class = "snapshot_manager_placeholder",
"Snapshots will appear here."
if (checkmate::test_string(header)) {
if (checkmate::test_string(footer)) {
checkmate::assert(
checkmate::check_class(splash_ui, "shiny.tag"),
checkmate::check_class(splash_ui, "shiny.tag.list"),
checkmate::check_class(splash_ui, "html")
checkmate::assert(
checkmate::check_class(header, "shiny.tag"),
checkmate::check_class(header, "shiny.tag.list"),
checkmate::check_class(header, "html")
checkmate::assert(
checkmate::check_class(footer, "shiny.tag"),
checkmate::check_class(footer, "shiny.tag.list"),
checkmate::check_class(footer, "html")
ns <- NS(id)
splash_ui <- div(
id = ns("main_ui_container"),
div(splash_ui)
shiny_busy_message_panel <- conditionalPanel(
condition = "(($('html').hasClass('shiny-busy')) && (document.getElementById('shiny-notification-panel') == null))", # nolint
div(
icon("arrows-rotate", "spin fa-spin"),
"Computing ...",
class = "shinybusymessage"
res <- fluidPage(
title = title,
theme = get_teal_bs_theme(),
include_teal_css_js(),
tags$header(header),
tags$hr(class = "my-2"),
shiny_busy_message_panel,
splash_ui,
tags$hr(),
tags$footer(
div(
footer,
teal.widgets::verbatim_popup_ui(ns("sessionInfo"), "Session Info", type = "link"),
textOutput(ns("identifier"))
return(res)
stopifnot(is.reactive(teal_data_rv))
moduleServer(id, function(input, output, session) {
logger::log_trace("srv_teal initializing the module.")
output$identifier <- renderText(
paste0("Pid:", Sys.getpid(), " Token:", substr(session$token, 25, 32))
teal.widgets::verbatim_popup_srv(
"sessionInfo",
verbatim_content = utils::capture.output(utils::sessionInfo()),
title = "SessionInfo"
run_js_files(files = "init.js") # `JavaScript` code to make the clipboard accessible
get_client_timezone(session$ns)
observeEvent(
eventExpr = input$timezone,
once = TRUE,
handlerExpr = {
reporter <- teal.reporter::Reporter$new()
if (is_arg_used(modules, "reporter") && length(extract_module(modules, "teal_module_previewer")) == 0) {
env <- environment()
datasets_reactive <- eventReactive(teal_data_rv(), {
env$progress <- shiny::Progress$new(session)
env$progress$set(0.25, message = "Setting data")
datasets_singleton <- teal_data_to_filtered_data(teal_data_rv())
filter_global <- Filter(function(x) x$id %in% attr(filter, "mapping")$global_filters, filter)
datasets_singleton$set_filter_state(filter_global)
module_datasets <- function(modules) {
if (inherits(modules, "teal_modules")) {
datasets <- lapply(modules$children, module_datasets)
labels <- vapply(modules$children, `[[`, character(1), "label")
names(datasets) <- labels
datasets
} else if (isTRUE(attr(filter, "module_specific"))) {
datanames <- if (is.null(modules$datanames) || modules$datanames == "all") {
include_parent_datanames(
teal.data::datanames(teal_data_rv()),
teal_data_rv()@join_keys
datasets_module <- teal_data_to_filtered_data(teal_data_rv(), datanames = datanames)
slices <- Filter(x = filter, f = function(x) {
include_varnames <- attr(slices, "include_varnames")[names(attr(slices, "include_varnames")) %in% datanames]
exclude_varnames <- attr(slices, "exclude_varnames")[names(attr(slices, "exclude_varnames")) %in% datanames]
slices$include_varnames <- include_varnames
slices$exclude_varnames <- exclude_varnames
datasets_module$set_filter_state(slices)
datasets_module
datasets_singleton
module_datasets(modules)
observeEvent(datasets_reactive(), once = TRUE, {
logger::log_trace("srv_teal@5 setting main ui after data was pulled")
on.exit(env$progress$close())
env$progress$set(0.5, message = "Setting up main UI")
datasets <- datasets_reactive()
removeUI(sprintf("#%s > div:nth-child(1)", session$ns("main_ui_container")))
insertUI(
selector = paste0("#", session$ns("main_ui_container")),
where = "beforeEnd",
ui = div(ui_tabs_with_filters(
session$ns("main_ui"),
modules = modules,
datasets = datasets,
filter = filter
immediate = TRUE
active_module <- srv_tabs_with_filters(
id = "main_ui",
datasets = datasets,
modules = modules,
reporter = reporter,
filter = filter
return(active_module)
vapply(
utils::sessionInfo()$otherPkgs,
function(x) {
paste0("library(", x$Package, ")")
character(1)
rev() %>%
paste0(sep = "\n") %>%
paste0(collapse = "")
code_string <- getOption("teal.load_nest_code")
if (!is.null(code_string) && is.character(code_string)) {
return(code_string)
return("# Add any code to install/load your NEST environment here\n")
str_prepro <- teal.data:::get_code_dependency(attr(datasets, "preprocessing_code"), names = datanames)
if (length(str_prepro) == 0) {
} else if (length(str_prepro) > 0) {
str_prepro <- paste0(str_prepro, "\n\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"
str_filter <- teal.slice::get_filter_expr(datasets, datanames)
c(str_prepro, str_hash, str_filter)
checkmate::assert_string(msg, null.ok = TRUE)
checkmate::assert_data_frame(x)
if (!is.null(min_nrow)) {
if (complete) {
complete_index <- stats::complete.cases(x)
validate(need(
sum(complete_index) > 0 && nrow(x[complete_index, , drop = FALSE]) >= min_nrow,
paste(c(paste("Number of complete cases is less than:", min_nrow), msg), collapse = "\n")
validate(need(
nrow(x) >= min_nrow,
paste(
c(paste("Minimum number of records not met: >=", min_nrow, "records required."), msg),
collapse = "\n"
if (!allow_inf) {
validate(need(
all(vapply(x, function(col) !is.numeric(col) || !any(is.infinite(col)), logical(1))),
"Dataframe contains Inf values which is not allowed."
checkmate::assert_string(label)
module(
label,
server = function(id, data) {
checkmate::assert_class(data, "tdata")
moduleServer(id, function(input, output, session) {
output$text <- renderPrint(data[[input$dataname]]())
teal.widgets::verbatim_popup_srv(
id = "rcode",
verbatim_content = attr(data, "code")(),
title = "Association Plot"
ui = function(id, data) {
ns <- NS(id)
teal.widgets::standard_layout(
output = verbatimTextOutput(ns("text")),
encoding = div(
selectInput(ns("dataname"), "Choose a dataset", choices = names(data)),
teal.widgets::verbatim_popup_ui(ns("rcode"), "Show R code")
datanames = datanames
checkmate::assert_subset(datanames, c("ADSL", "ADTTE"))
mods <- modules(
label = "d1",
modules(
label = "d2",
modules(
label = "d3",
example_module(label = "aaa1", datanames = datanames),
example_module(label = "aaa2", datanames = datanames),
example_module(label = "aaa3", datanames = datanames)
example_module(label = "bbb", datanames = datanames)
example_module(label = "ccc", datanames = datanames)
return(mods)
checkmate::assert_list(
data,
any.missing = FALSE, names = "unique",
types = c("data.frame", "reactive", "MultiAssayExperiment")
checkmate::assert_class(join_keys, "join_keys", null.ok = TRUE)
checkmate::assert_multi_class(code, c("character", "reactive"))
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"))
for (x in names(data)) {
if (!is.reactive(data[[x]])) {
data[[x]] <- do.call(reactive, list(as.name(x)), envir = list2env(data[x]))
isolate(
checkmate::assert_multi_class(
data[[x]](), c("data.frame", "MultiAssayExperiment"),
.var.name = "data"
attr(data, "code") <- if (is.reactive(code)) code else reactive(code)
attr(data, "join_keys") <- join_keys
attr(data, "metadata") <- metadata
class(data) <- c("tdata", class(data))
data
checkmate::assert_class(data, "tdata")
list2env(lapply(data, function(x) if (is.reactive(x)) x() else x))
attr(x, "code")()
checkmate::assert_class(data, "tdata")
get_code(data)
attr(data, "join_keys")
checkmate::assert_string(dataname)
UseMethod("get_metadata", data)
metadata <- attr(data, "metadata")
if (is.null(metadata)) {
return(NULL)
metadata[[dataname]]
shiny::isolate({
checkmate::assert_flag(allow_add)
checkmate::assert_flag(module_specific)
if (!missing(mapping)) checkmate::assert_list(mapping, types = c("character", "NULL"), names = "named")
checkmate::assert_string(app_id, null.ok = TRUE)
slices <- list(...)
all_slice_id <- vapply(slices, `[[`, character(1L), "id")
if (missing(mapping)) {
mapping <- list(global_filters = all_slice_id)
if (!module_specific) {
mapping[setdiff(names(mapping), "global_filters")] <- NULL
failed_slice_id <- setdiff(unlist(mapping), all_slice_id)
if (length(failed_slice_id)) {
stop(sprintf(
"Filters in mapping don't match any available filter.\n %s not in %s",
toString(failed_slice_id),
toString(all_slice_id)
tss <- teal.slice::teal_slices(
exclude_varnames = exclude_varnames,
include_varnames = include_varnames,
count_type = count_type,
allow_add = allow_add
attr(tss, "mapping") <- mapping
attr(tss, "module_specific") <- module_specific
attr(tss, "app_id") <- app_id
class(tss) <- c("modules_teal_slices", class(tss))
tss
checkmate::assert_list(x)
lapply(x, checkmate::assert_list, names = "named", .var.name = "list element")
attrs <- attributes(unclass(x))
ans <- lapply(x, function(x) if (is.teal_slice(x)) x else as.teal_slice(x))
do.call(teal_slices, c(ans, attrs))
checkmate::assert_class(filter, "teal_slices")
shiny::isolate({
filter_copy <- lapply(filter, function(slice) {
teal.slice::as.teal_slice(as.list(slice))
attributes(filter_copy) <- attributes(filter)
filter_copy
ns <- NS(id)
tags$button(
id = ns("show"),
class = "btn action-button filter_manager_button",
title = "Show filters manager modal",
icon("gear")
moduleServer(id, function(input, output, session) {
observeEvent(input$show, {
filter_manager_srv("filter_manager", filtered_data_list, filter)
moduleServer(id, function(input, output, session) {
logger::log_trace("filter_manager_srv initializing for: { paste(names(filtered_data_list), collapse = ', ')}.")
is_module_specific <- isTRUE(attr(filter, "module_specific"))
slices_global <- reactiveVal(filter)
filtered_data_list <-
if (!is_module_specific) {
list(global_filters = unlist(filtered_data_list)[[1]])
flatten_nested <- function(x, name = NULL) {
if (inherits(x, "FilteredData")) {
setNames(list(x), name)
unlist(lapply(names(x), function(name) flatten_nested(x[[name]], name)))
flatten_nested(filtered_data_list)
mapping_matrix <- reactive({
state_ids_global <- vapply(slices_global(), `[[`, character(1L), "id")
mapping_smooth <- lapply(filtered_data_list, function(x) {
state_ids_local <- vapply(x$get_filter_state(), `[[`, character(1L), "id")
state_ids_allowed <- vapply(x$get_available_teal_slices()(), `[[`, character(1L), "id")
states_active <- state_ids_global %in% state_ids_local
ifelse(state_ids_global %in% state_ids_allowed, states_active, NA)
as.data.frame(mapping_smooth, row.names = state_ids_global, check.names = FALSE)
output$slices_table <- renderTable(
expr = {
mm <- mapping_matrix()
mm[] <- lapply(mm, ifelse, yes = intToUtf8(9989), no = intToUtf8(10060))
mm[] <- lapply(mm, function(x) ifelse(is.na(x), intToUtf8(128306), x))
if (!is_module_specific) colnames(mm) <- "Global Filters"
if (nrow(mm) == 0L) {
mm <- data.frame(`Filter manager` = "No filters specified.", check.names = FALSE)
rownames(mm) <- ""
mm[names(mm) != "Report previewer"]
align = paste(c("l", rep("c", sum(names(filtered_data_list) != "Report previewer"))), collapse = ""),
rownames = TRUE
modules_out <- lapply(names(filtered_data_list), function(module_name) {
filter_manager_module_srv(
id = module_name,
module_fd = filtered_data_list[[module_name]],
slices_global = slices_global
snapshot_manager_srv("snapshot_manager", slices_global, mapping_matrix, filtered_data_list)
modules_out # returned for testing purpose
moduleServer(id, function(input, output, session) {
module_fd$set_available_teal_slices(reactive(slices_global()))
slices_module <- reactive(module_fd$get_filter_state())
previous_slices <- reactiveVal(isolate(slices_module()))
slices_added <- reactiveVal(NULL)
observeEvent(slices_module(), ignoreNULL = FALSE, {
logger::log_trace("filter_manager_srv@1 detecting states deltas in module: { id }.")
added <- setdiff_teal_slices(slices_module(), slices_global())
previous_slices(slices_module())
observeEvent(slices_added(), ignoreNULL = TRUE, {
slices_module # returned for testing purpose
checkmate::assert_character(src, min.len = 0, max.len = 1)
params <- list(...)
params$eval <- FALSE
rblock <- RcodeBlock$new(src)
rblock$set_params(params)
self$append_content(rblock)
self$append_metadata("SRC", src)
invisible(self)
checkmate::assert_class(fs, "teal_slices")
self$append_text("Filter State", "header3")
self$append_content(TealSlicesBlock$new(fs))
invisible(self)
checkmate::assert_list(encodings)
self$append_text("Selected Options", "header3")
if (requireNamespace("yaml", quietly = TRUE)) {
self$append_text(yaml::as.yaml(encodings, handlers = list(
POSIXct = function(x) format(x, "%Y-%m-%d"),
POSIXlt = function(x) format(x, "%Y-%m-%d"),
Date = function(x) format(x, "%Y-%m-%d")
)), "verbatim")
self$append_metadata("Encodings", encodings)
invisible(self)
self$set_content(content)
self$set_style(style)
invisible(self)
checkmate::assert_class(content, "teal_slices")
if (length(content) != 0) {
states_list <- lapply(content, function(x) {
x_list <- shiny::isolate(as.list(x))
if (
inherits(x_list$choices, c("integer", "numeric", "Date", "POSIXct", "POSIXlt")) &&
length(x_list$choices) == 2 &&
length(x_list$selected) == 2
if (!is.null(x_list$arg)) {
x_list <- x_list[
c("dataname", "varname", "experiment", "arg", "expr", "selected", "range", "keep_na", "keep_inf")
names(x_list) <- c(
"Dataset name", "Variable name", "Experiment", "Filtering by", "Applied expression",
"Selected Values", "Selected range", "Include NA values", "Include Inf values"
Filter(Negate(is.null), x_list)
if (requireNamespace("yaml", quietly = TRUE)) {
super$set_content(yaml::as.yaml(states_list))
private$teal_slices <- content
invisible(self)
checkmate::assert_list(x)
checkmate::assert_names(names(x), must.include = c("teal_slices"))
self$set_content(x$teal_slices)
invisible(self)
list(teal_slices = private$teal_slices)
teal_data_module(
ui = function(id) {
ns <- NS(id)
object$ui(ns("mutate_inner"))
server = function(id) {
moduleServer(id, function(input, output, session) {
data <- object$server("mutate_inner")
reactive(eval_code(data(), code))
eval_code(object, code = format_expression(code))
eval_code(object, code = format_expression(code))
expr <- substitute(expr)
extras <- list(...)
if (!identical(as.list(expr)[[1L]], as.symbol("{"))) {
expr <- call("{", expr)
calls <- as.list(expr)[-1]
calls <- lapply(calls, function(x) do.call(substitute, list(x, env = extras)))
eval_code(object = data, code = as.expression(calls))
css_files <- list.files(
system.file("css", package = "teal", mustWork = TRUE),
pattern = pattern, full.names = TRUE
return(
shiny::singleton(
shiny::tags$head(lapply(css_files, shiny::includeCSS))
checkmate::assert_character(except, min.len = 1, any.missing = FALSE, null.ok = TRUE)
js_files <- list.files(system.file("js", package = "teal", mustWork = TRUE), pattern = pattern, full.names = TRUE)
js_files <- js_files[!(basename(js_files) %in% except)] # no-op if except is NULL
return(singleton(lapply(js_files, includeScript)))
checkmate::assert_character(files, min.len = 1, any.missing = FALSE)
lapply(files, function(file) {
shinyjs::runjs(paste0(readLines(system.file("js", file, package = "teal", mustWork = TRUE)), collapse = "\n"))
return(invisible(NULL))
tagList(
shinyjs::useShinyjs(),
include_css_files(),
include_js_files(except = "init.js"),
shinyjs::hidden(icon("gear")), # add hidden icon to load font-awesome css for icons
dots <- list(...)
if (!is_validators(dots)) stop("validate_inputs accepts validators or a list thereof")
messages <- extract_validator(dots, header)
failings <- if (!any_names(dots)) {
add_header(messages, header)
unlist(messages)
shiny::validate(shiny::need(is.null(failings), failings))
all(if (is.list(x)) unlist(lapply(x, is_validators)) else inherits(x, "InputValidator"))
x$.__enclos_env__$private$enabled
if (inherits(iv, "InputValidator")) {
add_header(gather_messages(iv), header)
if (is.null(names(iv))) names(iv) <- rep("", length(iv))
mapply(extract_validator, iv = iv, header = names(iv), SIMPLIFY = FALSE)
if (validator_enabled(iv)) {
status <- iv$validate()
failing_inputs <- Filter(Negate(is.null), status)
unique(lapply(failing_inputs, function(x) x[["message"]]))
logger::log_warn("Validator is disabled and will be omitted.")
list()
ans <- unlist(messages)
if (length(ans) != 0L && isTRUE(nchar(header) > 0L)) {
ans <- c(paste0(header, "\n"), ans, "\n")
ans
any(
if (is.list(x)) {
if (!is.null(names(x)) && any(names(x) != "")) TRUE else unlist(lapply(x, any_names))
FALSE
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)
checkmate::assert_multi_class(data, c("TealData", "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(
checkmate::check_class(filter, "teal_slices"),
checkmate::check_list(filter, names = "named")
checkmate::assert_multi_class(header, c("shiny.tag", "character"))
checkmate::assert_multi_class(footer, c("shiny.tag", "character"))
checkmate::assert_character(id, max.len = 1, any.missing = FALSE)
teal.logger::log_system_info()
if (inherits(modules, "teal_module")) {
modules <- list(modules)
if (inherits(modules, "list")) {
modules <- do.call(teal::modules, modules)
landing <- extract_module(modules, "teal_module_landing")
modules <- drop_module(modules, "teal_module_landing")
hashables <- mget(c("data", "modules"))
hashables$data <- if (inherits(hashables$data, "teal_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()
hashables$data$get_code()
attr(filter, "app_id") <- rlang::hash(hashables)
filter <- as.teal_slices(as.list(filter))
if (isTRUE(attr(filter, "module_specific"))) {
if (inherits(data, "teal_data")) {
if (length(teal.data::datanames(data)) == 0) {
stop("`data` object has no datanames. Specify `datanames(data)` and try again.")
is_modules_ok <- check_modules_datanames(modules, teal.data::datanames(data))
if (!isTRUE(is_modules_ok)) {
logger::log_error(is_modules_ok)
checkmate::assert(is_modules_ok, .var.name = "modules")
is_filter_ok <- check_filter_datanames(filter, teal.data::datanames(data))
if (!isTRUE(is_filter_ok)) {
logger::log_warn(is_filter_ok)
res <- list(
ui = ui_teal_with_splash(id = id, data = data, title = title, header = header, footer = footer),
server = function(input, output, session) {
logger::log_trace("init teal app has been initialized.")
return(res)
checkmate::assert_multi_class(modules, c("teal_modules", "teal_module"))
checkmate::assert_count(depth)
UseMethod("ui_nested_tabs", modules)
checkmate::assert_list(datasets, types = c("list", "FilteredData"))
ns <- NS(id)
do.call(
tabsetPanel,
c(
list(
id = ns("active_tab"),
type = if (modules$label == "root") "pills" else "tabs"
lapply(
names(modules$children),
function(module_id) {
module_label <- modules$children[[module_id]]$label
tabPanel(
title = module_label,
value = module_id, # when clicked this tab value changes input$<tabset panel id>
ui_nested_tabs(
id = ns(module_id),
modules = modules$children[[module_id]],
datasets = datasets[[module_label]],
depth = depth + 1L,
is_module_specific = is_module_specific
checkmate::assert_class(datasets, classes = "FilteredData")
ns <- NS(id)
args <- isolate(teal.transform::resolve_delayed(modules$ui_args, datasets))
args <- c(list(id = ns("module")), args)
if (is_arg_used(modules$ui, "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",
uiOutput(ns("data_reactive"), inline = TRUE),
tagList(
if (depth >= 2L) div(style = "mt-6"),
do.call(modules$ui, args)
if (!is.null(modules$datanames) && is_module_specific) {
teal_ui
checkmate::assert_multi_class(modules, c("teal_modules", "teal_module"))
checkmate::assert_class(reporter, "Reporter")
UseMethod("srv_nested_tabs", modules)
checkmate::assert_list(datasets, types = c("list", "FilteredData"))
moduleServer(id = id, module = function(input, output, session) {
logger::log_trace("srv_nested_tabs.teal_modules initializing the module { deparse1(modules$label) }.")
labels <- vapply(modules$children, `[[`, character(1), "label")
modules_reactive <- sapply(
names(modules$children),
function(module_id) {
srv_nested_tabs(
id = module_id,
datasets = datasets[[labels[module_id]]],
modules = modules$children[[module_id]],
is_module_specific = is_module_specific,
reporter = reporter
simplify = FALSE
input_validated <- eventReactive(input$active_tab, input$active_tab, ignoreNULL = TRUE)
get_active_module <- reactive({
if (length(modules$children) == 1L) {
modules_reactive[[1]]()
modules_reactive[[input_validated()]]()
get_active_module
checkmate::assert_class(datasets, "FilteredData")
logger::log_trace("srv_nested_tabs.teal_module initializing the module: { deparse1(modules$label) }.")
moduleServer(id = id, module = function(input, output, session) {
modules$server_args <- teal.transform::resolve_delayed(modules$server_args, datasets)
if (!is.null(modules$datanames) && is_module_specific) {
trigger_data <- reactiveVal(1L)
trigger_module <- reactiveVal(NULL)
output$data_reactive <- renderUI({
lapply(datasets$datanames(), function(x) {
datasets$get_data(x, filtered = TRUE)
isolate(trigger_data(trigger_data() + 1))
isolate(trigger_module(TRUE))
NULL
args <- c(list(id = "module"), modules$server_args)
if (is_arg_used(modules$server, "reporter")) {
if (is_arg_used(modules$server, "datasets")) {
args <- c(args, datasets = datasets)
if (is_arg_used(modules$server, "data")) {
data <- .datasets_to_data(modules, datasets, trigger_data)
args <- c(args, data = list(data))
if (is_arg_used(modules$server, "filter_panel_api")) {
filter_panel_api <- teal.slice::FilterPanelAPI$new(datasets)
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."
observeEvent(
ignoreNULL = TRUE,
once = TRUE,
eventExpr = trigger_module(),
handlerExpr = {
module_output <- if (is_arg_used(modules$server, "id")) {
do.call(modules$server, args)
reactive(modules)
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()
unique(module$datanames) # todo: include parents! unique shouldn't be needed here!
data <- sapply(
datanames,
function(x) eventReactive(trigger_data(), 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
sapply(datanames, function(x) rlang::hash(datasets$get_data(x, filtered = FALSE)), simplify = FALSE)
checkmate::assert_class(modules, "teal_modules")
checkmate::assert_list(datasets, types = c("list", "FilteredData"))
checkmate::assert_class(filter, "teal_slices")
ns <- NS(id)
is_module_specific <- isTRUE(attr(filter, "module_specific"))
teal_ui <- ui_nested_tabs(ns("root"), modules = modules, datasets, is_module_specific = is_module_specific)
filter_panel_btns <- tags$li(
class = "flex-grow",
tags$button(
class = "btn action-button filter_hamburger", # see sidebar.css for style filter_hamburger
href = "javascript:void(0)",
onclick = "toggleFilterPanel();", # see sidebar.js
title = "Toggle filter panels",
icon("fas fa-bars")
filter_manager_modal_ui(ns("filter_manager"))
teal_ui$children[[1]] <- tagAppendChild(teal_ui$children[[1]], filter_panel_btns)
if (!is_module_specific) {
tabset_bar <- teal_ui$children[[1]]
teal_modules <- teal_ui$children[[2]]
filter_ui <- unlist(datasets)[[1]]$ui_filter_panel(ns("filter_panel"))
list(
tabset_bar,
tags$hr(class = "my-2"),
fluidRow(
column(width = 9, teal_modules, class = "teal_primary_col"),
column(width = 3, filter_ui, class = "teal_secondary_col")
checkmate::assert_class(modules, "teal_modules")
checkmate::assert_list(datasets, types = c("list", "FilteredData"))
checkmate::assert_class(reporter, "Reporter")
checkmate::assert_class(filter, "teal_slices")
moduleServer(id, function(input, output, session) {
logger::log_trace("srv_tabs_with_filters initializing the module.")
is_module_specific <- isTRUE(attr(filter, "module_specific"))
manager_out <- filter_manager_modal_srv("filter_manager", filtered_data_list = datasets, filter = filter)
active_module <- srv_nested_tabs(
id = "root",
datasets = datasets,
modules = modules,
reporter = reporter,
is_module_specific = is_module_specific
if (!is_module_specific) {
active_datanames <- reactive({
if (identical(active_module()$datanames, "all")) {
singleton$datanames()
active_module()$datanames
singleton <- unlist(datasets)[[1]]
singleton$srv_filter_panel("filter_panel", active_datanames = active_datanames)
observeEvent(
eventExpr = active_datanames(),
handlerExpr = {
script <- if (length(active_datanames()) == 0 || is.null(active_datanames())) {
"handleActiveDatasetsPresent();"
shinyjs::runjs(script)
ignoreNULL = FALSE
showNotification("Data loaded - App fully started up")
logger::log_trace("srv_tabs_with_filters initialized the module")
return(active_module)
checkmate::assert_multi_class(data, c("TealData", "teal_data", "teal_data_module"))
ns <- NS(id)
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()
message("App was initialized with delayed data loading.")
data$get_ui(ns("startapp_module"))
ui_teal(
id = ns("teal"),
splash_ui = div(splash_ui, uiOutput(ns("error"))),
title = title,
header = header,
footer = footer
checkmate::check_multi_class(data, c("TealData", "teal_data", "teal_data_module"))
moduleServer(id, function(input, output, session) {
logger::log_trace("srv_teal_with_splash initializing module with data.")
if (getOption("teal.show_js_log", default = FALSE)) {
teal_data_rv <- if (inherits(data, "teal_data_module")) {
data <- data$server(id = "teal_data_module")
if (!is.reactive(data)) {
stop("The `teal_data_module` must return a reactive expression.", call. = FALSE)
data
} else if (inherits(data, "teal_data")) {
reactiveVal(data)
} else if (inherits(data, "TealDataAbstract") && teal.data::is_pulled(data)) {
raw_data_old <- data$get_server()(id = "startapp_module")
raw_data <- reactive({
data <- raw_data_old()
if (!is.null(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))
raw_data
teal_data_rv_validate <- reactive({
data <- tryCatch(teal_data_rv(), error = function(e) e)
if (inherits(data, "shiny.silent.error") && identical(data$message, "")) {
if (inherits(data, "qenv.error")) {
validate(
need(
FALSE,
paste(
"Error when executing `teal_data_module`:\n ",
paste(data$message, collapse = "\n"),
"\n Check your inputs or contact app developer if error persists."
if (inherits(data, "error")) {
validate(
need(
FALSE,
paste(
"Error when executing `teal_data_module`:\n ",
paste(data$message, collpase = "\n"),
"\n Check your inputs or contact app developer if error persists."
validate(
need(
inherits(data, "teal_data"),
paste(
"Error: `teal_data_module` did not return `teal_data` object",
"\n Check your inputs or contact app developer if error persists"
validate(need(teal.data::datanames(data), "Data has no datanames. Contact app developer."))
is_modules_ok <- check_modules_datanames(modules, teal.data::datanames(data))
validate(need(isTRUE(is_modules_ok), is_modules_ok))
is_filter_ok <- check_filter_datanames(filter, teal.data::datanames(data))
if (!isTRUE(is_filter_ok)) {
showNotification(
"Some filters were not applied because of incompatibility with data. Contact app developer.",
type = "warning",
duration = 10
logger::log_warn(is_filter_ok)
teal_data_rv()
output$error <- renderUI({
teal_data_rv_validate()
NULL
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)
checkmate::assert_class(tss, "teal_slices")
checkmate::assert_path_for_output(file, overwrite = TRUE, extension = "json")
cat(format(tss, trim_lines = FALSE), "\n", file = file)
checkmate::assert_file_exists(file, access = "r", extension = "json")
tss_json <- jsonlite::fromJSON(file, simplifyDataFrame = FALSE)
tss_json$slices <-
lapply(tss_json$slices, function(slice) {
for (field in c("selected", "choices")) {
if (!is.null(slice[[field]])) {
date_partial_regex <- "^[0-9]{4}-[0-9]{2}-[0-9]{2}"
time_stamp_regex <- paste0(date_partial_regex, "\\s[0-9]{2}:[0-9]{2}:[0-9]{2}\\sUTC$")
slice[[field]] <-
if (all(grepl(paste0(date_partial_regex, "$"), slice[[field]]))) {
as.Date(slice[[field]])
} else if (all(grepl(time_stamp_regex, slice[[field]]))) {
as.POSIXct(slice[[field]], tz = "UTC")
slice[[field]]
slice
tss_elements <- lapply(tss_json$slices, as.teal_slice)
do.call(teal_slices, c(tss_elements, tss_json$attributes))
checkmate::assert_string(label)
submodules <- list(...)
if (any(vapply(submodules, is.character, FUN.VALUE = logical(1)))) {
stop(
"The only character argument to modules() must be 'label' and it must be named, ",
"change modules('lab', ...) to modules(label = 'lab', ...)"
checkmate::assert_list(submodules, min.len = 1, any.missing = FALSE, types = c("teal_module", "teal_modules"))
labels <- vapply(submodules, function(submodule) submodule$label, character(1))
names(submodules) <- make.unique(gsub("[^[:alnum:]]+", "_", labels), sep = "_")
structure(
list(
label = label,
children = submodules
class = "teal_modules"
checkmate::assert_class(modules, "teal_modules")
checkmate::assert_class(module, "teal_module")
modules$children <- c(modules$children, list(module))
labels <- vapply(modules$children, function(submodule) submodule$label, character(1))
names(modules$children) <- make.unique(gsub("[^[:alnum:]]", "_", tolower(labels)), sep = "_")
modules
if (inherits(modules, class)) {
} else if (inherits(modules, "teal_module")) {
NULL
} else if (inherits(modules, "teal_modules")) {
Filter(function(x) length(x) > 0L, lapply(modules$children, extract_module, class))
if (inherits(modules, class)) {
} else if (inherits(modules, "teal_module")) {
modules
} else if (inherits(modules, "teal_modules")) {
do.call(
"modules",
c(Filter(function(x) length(x) > 0L, lapply(modules$children, drop_module, class)), label = modules$label)
checkmate::assert_string(arg)
if (inherits(modules, "teal_modules")) {
any(unlist(lapply(modules$children, is_arg_used, arg)))
} else if (inherits(modules, "teal_module")) {
is_arg_used(modules$server, arg) || is_arg_used(modules$ui, arg)
} else if (is.function(modules)) {
isTRUE(arg %in% names(formals(modules)))
stop("is_arg_used function not implemented for this object")
checkmate::assert_string(label)
checkmate::assert_function(server)
checkmate::assert_function(ui)
checkmate::assert_character(datanames, min.len = 1, null.ok = TRUE, any.missing = FALSE)
checkmate::assert_list(server_args, null.ok = TRUE, names = "named")
checkmate::assert_list(ui_args, null.ok = TRUE, names = "named")
if (!missing(filters)) {
if (label == "global_filters") {
stop(
sprintf("module(label = \"%s\", ...\n ", label),
"Label 'global_filters' is reserved in teal. Please change to something else.",
call. = FALSE
if (label == "Report previewer") {
server_formals <- names(formals(server))
if (!(
"id" %in% server_formals ||
all(c("input", "output", "session") %in% server_formals)
stop(
"\nmodule() `server` argument requires a function with following arguments:",
"\n - id - teal will set proper shiny namespace for this module.",
"\n - input, output, session (not recommended) - then shiny::callModule will be used to call a module.",
"\n\nFollowing arguments can be used optionaly:",
"\n - `data` - module will receive list of reactive (filtered) data specified in the `filters` argument",
"\n - `datasets` - module will receive `FilteredData`. See `help(teal.slice::FilteredData)`",
"\n - `reporter` - module will receive `Reporter`. See `help(teal.reporter::Reporter)`",
"\n - `filter_panel_api` - module will receive `FilterPanelAPI`. (See [teal.slice::FilterPanelAPI]).",
"\n - `...` server_args elements will be passed to the module named argument or to the `...`"
if (!is.element("data", server_formals) && !is.null(datanames)) {
message(sprintf("module \"%s\" server function takes no data so \"datanames\" will be ignored", label))
datanames <- NULL
srv_extra_args <- setdiff(names(server_args), server_formals)
if (length(srv_extra_args) > 0 && !"..." %in% server_formals) {
stop(
"\nFollowing `server_args` elements have no equivalent in the formals of the `server`:\n",
paste(paste(" -", srv_extra_args), collapse = "\n"),
"\n\nUpdate the `server` arguments by including above or add `...`"
ui_formals <- names(formals(ui))
if (!"id" %in% ui_formals) {
stop(
"\nmodule() `ui` argument requires a function with following arguments:",
"\n - id - teal will set proper shiny namespace for this module.",
"\n\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 - `...` ui_args elements will be passed to the module argument of the same name or to the `...`"
ui_extra_args <- setdiff(names(ui_args), ui_formals)
if (length(ui_extra_args) > 0 && !"..." %in% ui_formals) {
stop(
"\nFollowing `ui_args` elements have no equivalent in the formals of `ui`:\n",
paste(paste(" -", ui_extra_args), collapse = "\n"),
"\n\nUpdate the `ui` arguments by including above or add `...`"
structure(
list(
label = label,
server = server, ui = ui, datanames = unique(datanames),
server_args = server_args, ui_args = ui_args
class = "teal_module"
checkmate::assert(
checkmate::check_class(modules, "teal_module"),
checkmate::check_class(modules, "teal_modules")
checkmate::assert_int(depth, lower = 0)
if (inherits(modules, "teal_modules")) {
max(vapply(modules$children, modules_depth, integer(1), depth = depth + 1L))
depth
checkmate::assert_function(ui, args = "id", nargs = 1)
checkmate::assert_function(server, args = "id", nargs = 1)
structure(
list(ui = ui, server = server),
class = "teal_data_module"
checkmate::assert_string(label)
checkmate::assert_list(server_args, names = "named")
checkmate::assert_true(all(names(server_args) %in% names(formals(teal.reporter::reporter_previewer_srv))))
logger::log_info("Initializing reporter_previewer_module")
srv <- function(id, reporter, ...) {
ui <- function(id, ...) {
module <- module(
label = "temporary label",
server = srv, ui = ui,
server_args = server_args, ui_args = list(), datanames = NULL
class(module) <- c("teal_module_previewer", class(module))
module$label <- label
module
packageStartupMessage(
"\nYou are using teal version ",
read.dcf(system.file("DESCRIPTION", package = "teal"))[, "Version"]
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our community include:
-Examples of unacceptable behavior include:
-Examples of unacceptable behavior include:
+Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [INSERT CONTACT METHOD]. All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
Community Impact: A violation through a single incident or series of actions.
Consequence: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
Community Impact: A serious violation of community standards, including sustained inappropriate behavior.
Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
Consequence: A permanent ban from any sort of public interaction within the community.
This Code of Conduct is adapted from the Contributor Covenant, version 2.1, available at https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
Community Impact Guidelines were inspired by Mozilla’s code of conduct enforcement ladder.
For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
@@ -177,17 +212,19 @@🙏 Thank you for taking the time to contribute!
Your input is deeply valued, whether an issue, a pull request, or even feedback, regardless of size, content or scope.
Please refer the project documentation for a brief introduction. Please also see other articles within the project documentation for additional information.
A Code of Conduct governs this project. Participants and contributors are expected to follow the rules outlined therein.
We use GitHub to track issues, feature requests, and bugs. Before submitting a new issue, please check if the issue has already been reported. If the issue already exists, please upvote the existing issue 👍.
For new feature requests, please elaborate on the context and the benefit the feature will have for users, developers, or other relevant personas.
This repository uses the GitHub Flow model for collaboration. To submit a pull request:
-Create a branch
Please see the branch naming convention below. If you don’t have write access to this repository, please fork it.
Make changes
Make sure your code
-Create a pull request (PR)
In the pull request description, please link the relevant issue (if any), provide a detailed description of the change, and include any assumptions.
@@ -169,76 +199,93 @@Suppose your changes are related to a current issue in the current project; please name your branch as follows: <issue_id>_<short_description>
. Please use underscore (_
) as a delimiter for word separation. For example, 420_fix_ui_bug
would be a suitable branch name if your change is resolving and UI-related bug reported in issue number 420
in the current project.
If your change affects multiple repositories, please name your branches as follows: <issue_id>_<issue_repo>_<short description>
. For example, 69_awesomeproject_fix_spelling_error
would reference issue 69
reported in project awesomeproject
and aims to resolve one or more spelling errors in multiple (likely related) repositories.
monorepo
and staged.dependencies
-Sometimes you might need to change upstream dependent package(s) to be able to submit a meaningful change. We are using staged.dependencies
functionality to simulate a monorepo
behavior. The dependency configuration is already specified in this project’s staged_dependencies.yaml
file. You need to name the feature branches appropriately. This is the only exception from the branch naming convention described above.
Please refer to the staged.dependencies package documentation for more details.
This repository follows some unified processes and standards adopted by its maintainers to ensure software development is carried out consistently within teams and cohesively across other repositories.
This repository follows the standard tidyverse
style guide and uses lintr
for lint checks. Customized lint configurations are available in this repository’s .lintr
file.
Lightweight is the right weight. This repository follows tinyverse recommedations of limiting dependencies to minimum.
If the code is not compatible with all (!) historical versions of a given dependenct package, it is required to specify minimal version in the DESCRIPTION
file. In particular: if the development version requires (imports) the development version of another package - it is required to put abc (>= 1.2.3.9000)
.
We continuously test our packages against the newest R version along with the most recent dependencies from CRAN and BioConductor. We recommend that your working environment is also set up in the same way. You can find the details about the R version and packages used in the R CMD check
GitHub Action execution log - there is a step that prints out the R sessionInfo()
.
If you discover bugs on older R versions or with an older set of dependencies, please create the relevant bug reports.
pre-commit
pre-commit
+We highly recommend that you use the pre-commit
tool combined with R hooks for pre-commit
to execute some of the checks before committing and pushing your changes.
Pre-commit hooks are already available in this repository’s .pre-commit-config.yaml
file.
As mentioned previously, all contributions are deeply valued and appreciated. While all contribution data is available as part of the repository insights, to recognize a significant contribution and hence add the contributor to the package authors list, the following rules are enforced:
-git blame
query) ORgit blame
query) OR*Excluding auto-generated code, including but not limited to roxygen
comments or renv.lock
files.
*Excluding auto-generated code, including but not limited to roxygen
comments or renv.lock
files.
The package maintainer also reserves the right to adjust the criteria to recognize contributions.
If you have further questions regarding the contribution guidelines, please contact the package/repository maintainer.
If you believe you have found a security vulnerability in any of the repositories in this organization, please report it to us through coordinated disclosure.
Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.
Instead, please send an email to vulnerability.management[@]roche.com.
Please include as much of the information listed below as you can to help us better understand and resolve the issue:
-This information will help us triage your report more quickly.
+This information will help us triage your report more quickly.
teal
teal
+teal
appsteal
teal
+The purpose of the blueprint is to aid new developer’s comprehension of the fundamental principles of the teal
framework. We will explore crucial teal
concepts such as data flow, actors, and filter panel, among others.
The purpose of the blueprint is to aid new developer’s comprehension of the fundamental principles of the teal
framework. We will explore crucial teal
concepts such as data flow, actors, and filter panel, among others.
Features.
+ +Features.
-lifecycle::deprecate_soft(
when = "0.15.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",
types = c("data.frame", "reactive", "MultiAssayExperiment")
checkmate::assert_class(join_keys, "join_keys", null.ok = TRUE)
checkmate::assert_multi_class(code, c("character", "reactive"))
checkmate::assert_list(metadata, names = "unique", null.ok = TRUE)
checkmate::assert_subset(names(metadata), names(data))
if (is.reactive(code)) {
isolate(checkmate::assert_class(code(), "character", .var.name = "code"))
for (x in names(data)) {
if (!is.reactive(data[[x]])) {
data[[x]] <- do.call(reactive, list(as.name(x)), envir = list2env(data[x]))
attr(data, "code") <- if (is.reactive(code)) code else reactive(code)
attr(data, "join_keys") <- join_keys
attr(data, "metadata") <- metadata
class(data) <- c("tdata", class(data))
data
checkmate::assert_class(data, "tdata")
list2env(lapply(data, function(x) if (is.reactive(x)) x() else x))
checkmate::assert_class(data, "tdata")
attr(data, "code")()
attr(data, "join_keys")
checkmate::assert_string(dataname)
UseMethod("get_metadata", data)
metadata <- attr(data, "metadata")
if (is.null(metadata)) {
return(NULL)
metadata[[dataname]]
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)
packageStartupMessage(
"\nYou are using teal version ",
read.dcf(system.file("DESCRIPTION", package = "teal"))[, "Version"]
checkmate::assert_character(src, min.len = 0, max.len = 1)
params <- list(...)
params$eval <- FALSE
rblock <- RcodeBlock$new(src)
rblock$set_params(params)
self$append_content(rblock)
self$append_metadata("SRC", src)
invisible(self)
checkmate::assert_class(fs, "teal_slices")
self$append_text("Filter State", "header3")
if (length(fs)) {
self$append_content(TealSlicesBlock$new(fs))
self$append_text("No filters specified.")
invisible(self)
checkmate::assert_list(encodings)
self$append_text("Selected Options", "header3")
if (requireNamespace("yaml", quietly = TRUE)) {
self$append_text(yaml::as.yaml(encodings, handlers = list(
POSIXct = function(x) format(x, "%Y-%m-%d"),
POSIXlt = function(x) format(x, "%Y-%m-%d"),
Date = function(x) format(x, "%Y-%m-%d")
)), "verbatim")
self$append_metadata("Encodings", encodings)
invisible(self)
self$set_content(content)
self$set_style(style)
invisible(self)
checkmate::assert_class(content, "teal_slices")
if (length(content) != 0) {
states_list <- lapply(content, function(x) {
x_list <- shiny::isolate(as.list(x))
if (
inherits(x_list$choices, c("integer", "numeric", "Date", "POSIXct", "POSIXlt")) &&
length(x_list$choices) == 2 &&
length(x_list$selected) == 2
if (!is.null(x_list$arg)) {
x_list <- x_list[
c("dataname", "varname", "experiment", "arg", "expr", "selected", "range", "keep_na", "keep_inf")
names(x_list) <- c(
"Dataset name", "Variable name", "Experiment", "Filtering by", "Applied expression",
"Selected Values", "Selected range", "Include NA values", "Include Inf values"
Filter(Negate(is.null), x_list)
if (requireNamespace("yaml", quietly = TRUE)) {
super$set_content(yaml::as.yaml(states_list))
private$teal_slices <- content
invisible(self)
checkmate::assert_list(x)
checkmate::assert_names(names(x), must.include = c("teal_slices"))
self$set_content(x$teal_slices)
invisible(self)
list(teal_slices = private$teal_slices)
vapply(
utils::sessionInfo()$otherPkgs,
function(x) {
paste0("library(", x$Package, ")")
character(1)
rev() %>%
paste0(sep = "\n") %>%
paste0(collapse = "")
code_string <- getOption("teal.load_nest_code")
if (is.character(code_string)) {
code_string
"# Add any code to install/load your NEST environment here\n"
str_prepro <- attr(datasets, "preprocessing_code")
if (length(str_prepro) == 0) {
str_prepro <- paste(str_prepro, collapse = "\n")
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")
str_filter <- teal.slice::get_filter_expr(datasets, datanames)
if (str_filter == "") {
str_filter <- character(0)
str_code <- paste(c(str_prepro, str_hash, str_filter), collapse = "\n\n")
sprintf("%s\n", str_code)
checkmate::assert_string(label)
if (label == "global_filters") {
stop(
sprintf("module(label = \"%s\", ...\n ", label),
"Label 'global_filters' is reserved in teal. Please change to something else.",
call. = FALSE
if (label == "Report previewer") {
checkmate::assert_function(server)
server_formals <- names(formals(server))
if (!(
"id" %in% server_formals ||
all(c("input", "output", "session") %in% server_formals)
stop(
"\nmodule() `server` argument requires a function with following arguments:",
"\n - id - `teal` will set proper `shiny` namespace for this module.",
"\n - input, output, session (not recommended) - then `shiny::callModule` will be used to call a module.",
"\n\nFollowing arguments can be used optionaly:",
"\n - `data` - module will receive list of reactive (filtered) data specified in the `filters` argument",
"\n - `datasets` - module will receive `FilteredData`. See `help(teal.slice::FilteredData)`",
"\n - `reporter` - module will receive `Reporter`. See `help(teal.reporter::Reporter)`",
"\n - `filter_panel_api` - module will receive `FilterPanelAPI`. (See [teal.slice::FilterPanelAPI]).",
"\n - `...` server_args elements will be passed to the module named argument or to the `...`"
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
checkmate::assert_function(ui)
ui_formals <- names(formals(ui))
if (!"id" %in% ui_formals) {
stop(
"\nmodule() `ui` argument requires a function with following arguments:",
"\n - id - `teal` will set proper `shiny` namespace for this module.",
"\n\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."
if (!missing(filters)) {
if (!is.element("data", server_formals) && !is.null(datanames)) {
message(sprintf("module \"%s\" server function takes no data so \"datanames\" will be ignored", label))
datanames <- NULL
checkmate::assert_character(datanames, min.len = 1, null.ok = TRUE, any.missing = FALSE)
checkmate::assert_list(server_args, null.ok = TRUE, names = "named")
srv_extra_args <- setdiff(names(server_args), server_formals)
if (length(srv_extra_args) > 0 && !"..." %in% server_formals) {
stop(
"\nFollowing `server_args` elements have no equivalent in the formals of the server:\n",
paste(paste(" -", srv_extra_args), collapse = "\n"),
"\n\nUpdate the server arguments by including above or add `...`"
checkmate::assert_list(ui_args, null.ok = TRUE, names = "named")
ui_extra_args <- setdiff(names(ui_args), ui_formals)
if (length(ui_extra_args) > 0 && !"..." %in% ui_formals) {
stop(
"\nFollowing `ui_args` elements have no equivalent in the formals of UI:\n",
paste(paste(" -", ui_extra_args), collapse = "\n"),
"\n\nUpdate the UI arguments by including above or add `...`"
structure(
list(
label = label,
server = server, ui = ui, datanames = unique(datanames),
server_args = server_args, ui_args = ui_args
class = "teal_module"
checkmate::assert_string(label)
submodules <- list(...)
if (any(vapply(submodules, is.character, FUN.VALUE = logical(1)))) {
stop(
"The only character argument to modules() must be 'label' and it must be named, ",
"change modules('lab', ...) to modules(label = 'lab', ...)"
checkmate::assert_list(submodules, min.len = 1, any.missing = FALSE, types = c("teal_module", "teal_modules"))
labels <- vapply(submodules, function(submodule) submodule$label, character(1))
names(submodules) <- make.unique(gsub("[^[:alnum:]]+", "_", labels), sep = "_")
structure(
list(
label = label,
children = submodules
class = "teal_modules"
paste0(paste(rep(" ", indent), collapse = ""), "+ ", x$label, "\n", collapse = "")
paste(
c(
paste0(rep(" ", indent), "+ ", x$label, "\n"),
unlist(lapply(x$children, format, indent = indent + 1, ...))
collapse = ""
checkmate::assert_class(modules, "teal_modules")
checkmate::assert_class(module, "teal_module")
modules$children <- c(modules$children, list(module))
labels <- vapply(modules$children, function(submodule) submodule$label, character(1))
names(modules$children) <- make.unique(gsub("[^[:alnum:]]", "_", tolower(labels)), sep = "_")
modules
if (inherits(modules, class)) {
} else if (inherits(modules, "teal_module")) {
NULL
} else if (inherits(modules, "teal_modules")) {
Filter(function(x) length(x) > 0L, lapply(modules$children, extract_module, class))
checkmate::assert_string(arg)
if (inherits(modules, "teal_modules")) {
any(unlist(lapply(modules$children, is_arg_used, arg)))
} else if (inherits(modules, "teal_module")) {
is_arg_used(modules$server, arg) || is_arg_used(modules$ui, arg)
} else if (is.function(modules)) {
isTRUE(arg %in% names(formals(modules)))
stop("is_arg_used function not implemented for this object")
checkmate::assert_multi_class(modules, c("teal_module", "teal_modules"))
checkmate::assert_int(depth, lower = 0)
if (inherits(modules, "teal_modules")) {
max(vapply(modules$children, modules_depth, integer(1), depth = depth + 1L))
depth
moduleServer(id, function(input, output, session) {
observeEvent(input$show, {
filter_manager_srv("filter_manager", filtered_data_list, filter)
moduleServer(id, function(input, output, session) {
logger::log_trace("filter_manager_srv initializing for: { paste(names(filtered_data_list), collapse = ', ')}.")
is_module_specific <- isTRUE(attr(filter, "module_specific"))
slices_global <- reactiveVal(filter)
filtered_data_list <-
if (!is_module_specific) {
list(global_filters = unlist(filtered_data_list)[[1]])
flatten_nested <- function(x, name = NULL) {
if (inherits(x, "FilteredData")) {
setNames(list(x), name)
unlist(lapply(names(x), function(name) flatten_nested(x[[name]], name)))
flatten_nested(filtered_data_list)
mapping_matrix <- reactive({
state_ids_global <- vapply(slices_global(), `[[`, character(1L), "id")
mapping_smooth <- lapply(filtered_data_list, function(x) {
state_ids_local <- vapply(x$get_filter_state(), `[[`, character(1L), "id")
state_ids_allowed <- vapply(x$get_available_teal_slices()(), `[[`, character(1L), "id")
states_active <- state_ids_global %in% state_ids_local
ifelse(state_ids_global %in% state_ids_allowed, states_active, NA)
as.data.frame(mapping_smooth, row.names = state_ids_global, check.names = FALSE)
output$slices_table <- renderTable(
expr = {
mm <- mapping_matrix()
mm[] <- lapply(mm, ifelse, yes = intToUtf8(9989), no = intToUtf8(10060))
mm[] <- lapply(mm, function(x) ifelse(is.na(x), intToUtf8(128306), x))
if (!is_module_specific) colnames(mm) <- "Global Filters"
if (nrow(mm) == 0L) {
mm <- data.frame(`Filter manager` = "No filters specified.", check.names = FALSE)
rownames(mm) <- ""
mm[names(mm) != "Report previewer"]
align = paste(c("l", rep("c", sum(names(filtered_data_list) != "Report previewer"))), collapse = ""),
rownames = TRUE
modules_out <- lapply(names(filtered_data_list), function(module_name) {
filter_manager_module_srv(
id = module_name,
module_fd = filtered_data_list[[module_name]],
slices_global = slices_global
snapshot_manager_srv("snapshot_manager", slices_global, mapping_matrix, filtered_data_list)
modules_out # returned for testing purpose
moduleServer(id, function(input, output, session) {
module_fd$set_available_teal_slices(reactive(slices_global()))
slices_module <- reactive(module_fd$get_filter_state())
previous_slices <- reactiveVal(isolate(slices_module()))
slices_added <- reactiveVal(NULL)
observeEvent(slices_module(), ignoreNULL = FALSE, {
logger::log_trace("filter_manager_srv@1 detecting states deltas in module: { id }.")
added <- setdiff_teal_slices(slices_module(), slices_global())
previous_slices(slices_module())
observeEvent(slices_added(), ignoreNULL = TRUE, {
slices_module # returned for testing purpose
shiny::isolate({
checkmate::assert_flag(allow_add)
checkmate::assert_flag(module_specific)
if (!missing(mapping)) checkmate::assert_list(mapping, types = c("character", "NULL"), names = "named")
checkmate::assert_string(app_id, null.ok = TRUE)
slices <- list(...)
all_slice_id <- vapply(slices, `[[`, character(1L), "id")
if (missing(mapping)) {
mapping <- list(global_filters = all_slice_id)
if (!module_specific) {
mapping[setdiff(names(mapping), "global_filters")] <- NULL
failed_slice_id <- setdiff(unlist(mapping), all_slice_id)
if (length(failed_slice_id)) {
stop(sprintf(
"Filters in mapping don't match any available filter.\n %s not in %s",
toString(failed_slice_id),
toString(all_slice_id)
tss <- teal.slice::teal_slices(
exclude_varnames = exclude_varnames,
include_varnames = include_varnames,
count_type = count_type,
allow_add = allow_add
attr(tss, "mapping") <- mapping
attr(tss, "module_specific") <- module_specific
attr(tss, "app_id") <- app_id
class(tss) <- c("modules_teal_slices", class(tss))
tss
checkmate::assert_list(x)
lapply(x, checkmate::assert_list, names = "named", .var.name = "list element")
attrs <- attributes(unclass(x))
ans <- lapply(x, function(x) if (is.teal_slice(x)) x else as.teal_slice(x))
do.call(teal_slices, c(ans, attrs))
checkmate::assert_class(filter, "teal_slices")
shiny::isolate({
filter_copy <- lapply(filter, function(slice) {
teal.slice::as.teal_slice(as.list(slice))
attributes(filter_copy) <- attributes(filter)
filter_copy
dots <- list(...)
if (!is_validators(dots)) stop("validate_inputs accepts validators or a list thereof")
messages <- extract_validator(dots, header)
failings <- if (!any_names(dots)) {
add_header(messages, header)
unlist(messages)
shiny::validate(shiny::need(is.null(failings), failings))
all(if (is.list(x)) unlist(lapply(x, is_validators)) else inherits(x, "InputValidator"))
x$.__enclos_env__$private$enabled
if (inherits(iv, "InputValidator")) {
add_header(gather_messages(iv), header)
if (is.null(names(iv))) names(iv) <- rep("", length(iv))
mapply(extract_validator, iv = iv, header = names(iv), SIMPLIFY = FALSE)
if (validator_enabled(iv)) {
status <- iv$validate()
failing_inputs <- Filter(Negate(is.null), status)
unique(lapply(failing_inputs, function(x) x[["message"]]))
warning("Validator is disabled and will be omitted.")
list()
ans <- unlist(messages)
if (length(ans) != 0L && isTRUE(nchar(header) > 0L)) {
ans <- c(paste0(header, "\n"), ans, "\n")
ans
any(
if (is.list(x)) {
if (!is.null(names(x)) && any(names(x) != "")) TRUE else unlist(lapply(x, any_names))
FALSE
checkmate::assert_character(id)
checkmate::assert_true(is.reactive(slices_global))
checkmate::assert_class(isolate(slices_global()), "teal_slices")
checkmate::assert_true(is.reactive(mapping_matrix))
checkmate::assert_data_frame(isolate(mapping_matrix()), null.ok = TRUE)
checkmate::assert_list(filtered_data_list, types = "FilteredData", any.missing = FALSE, names = "named")
moduleServer(id, function(input, output, session) {
ns <- session$ns
filter <- isolate(slices_global())
snapshot_history <- reactiveVal({
list(
"Initial application state" = as.list(filter, recursive = TRUE)
observeEvent(input$snapshot_add, {
observeEvent(input$snapshot_name_accept, {
observeEvent(input$snapshot_load, {
observeEvent(input$snaphot_file_accept, {
observeEvent(input$snapshot_reset, {
observers <- reactiveValues()
handlers <- reactiveValues()
divs <- reactiveValues()
observeEvent(snapshot_history(), {
lapply(names(snapshot_history())[-1L], function(s) {
output$snapshot_list <- renderUI({
rows <- lapply(rev(reactiveValuesToList(divs)), function(d) d)
if (length(rows) == 0L) {
div(
class = "snapshot_manager_placeholder",
"Snapshots will appear here."
checkmate::assert_character(id, max.len = 1, any.missing = FALSE)
checkmate::assert_multi_class(splash_ui, c("shiny.tag", "shiny.tag.list", "html"))
if (is.character(title)) {
validate_app_title_tag(title)
checkmate::assert(
.var.name = "header",
checkmate::check_string(header),
checkmate::check_multi_class(header, c("shiny.tag", "shiny.tag.list", "html"))
if (checkmate::test_string(header)) {
checkmate::assert(
.var.name = "footer",
checkmate::check_string(footer),
checkmate::check_multi_class(footer, c("shiny.tag", "shiny.tag.list", "html"))
if (checkmate::test_string(footer)) {
ns <- NS(id)
splash_ui <- div(
id = ns("main_ui_container"),
div(splash_ui)
shiny_busy_message_panel <- conditionalPanel(
condition = "(($('html').hasClass('shiny-busy')) && (document.getElementById('shiny-notification-panel') == null))", # nolint: line_length.
div(
icon("arrows-rotate", "spin fa-spin"),
"Computing ...",
class = "shinybusymessage"
fluidPage(
title = title,
theme = get_teal_bs_theme(),
include_teal_css_js(),
tags$header(header),
tags$hr(class = "my-2"),
shiny_busy_message_panel,
splash_ui,
tags$hr(),
tags$footer(
div(
footer,
teal.widgets::verbatim_popup_ui(ns("sessionInfo"), "Session Info", type = "link"),
textOutput(ns("identifier"))
stopifnot(is.reactive(teal_data_rv))
moduleServer(id, function(input, output, session) {
logger::log_trace("srv_teal initializing the module.")
output$identifier <- renderText(
paste0("Pid:", Sys.getpid(), " Token:", substr(session$token, 25, 32))
teal.widgets::verbatim_popup_srv(
"sessionInfo",
verbatim_content = utils::capture.output(utils::sessionInfo()),
title = "SessionInfo"
run_js_files(files = "init.js")
get_client_timezone(session$ns)
observeEvent(
eventExpr = input$timezone,
once = TRUE,
handlerExpr = {
reporter <- teal.reporter::Reporter$new()
if (is_arg_used(modules, "reporter") && length(extract_module(modules, "teal_module_previewer")) == 0) {
env <- environment()
datasets_reactive <- eventReactive(teal_data_rv(), {
env$progress <- shiny::Progress$new(session)
env$progress$set(0.25, message = "Setting data")
modules_datasets(teal_data_rv(), modules, filter, teal_data_to_filtered_data(teal_data_rv()))
observeEvent(datasets_reactive(), once = TRUE, {
checkmate::assert_multi_class(modules, c("teal_modules", "teal_module"))
checkmate::assert_class(reporter, "Reporter")
UseMethod("srv_nested_tabs", modules)
checkmate::assert_list(datasets, types = c("list", "FilteredData"))
moduleServer(id = id, module = function(input, output, session) {
logger::log_trace("srv_nested_tabs.teal_modules initializing the module { deparse1(modules$label) }.")
labels <- vapply(modules$children, `[[`, character(1), "label")
modules_reactive <- sapply(
names(modules$children),
function(module_id) {
srv_nested_tabs(
id = module_id,
datasets = datasets[[labels[module_id]]],
modules = modules$children[[module_id]],
is_module_specific = is_module_specific,
reporter = reporter
simplify = FALSE
input_validated <- eventReactive(input$active_tab, input$active_tab, ignoreNULL = TRUE)
get_active_module <- reactive({
if (length(modules$children) == 1L) {
modules_reactive[[1]]()
modules_reactive[[input_validated()]]()
get_active_module
checkmate::assert_class(datasets, "FilteredData")
logger::log_trace("srv_nested_tabs.teal_module initializing the module: { deparse1(modules$label) }.")
moduleServer(id = id, module = function(input, output, session) {
if (!is.null(modules$datanames) && is_module_specific) {
trigger_data <- reactiveVal(1L)
trigger_module <- reactiveVal(NULL)
output$data_reactive <- renderUI({
lapply(datasets$datanames(), function(x) {
datasets$get_data(x, filtered = TRUE)
isolate(trigger_data(trigger_data() + 1))
isolate(trigger_module(TRUE))
NULL
args <- c(list(id = "module"), modules$server_args)
if (is_arg_used(modules$server, "reporter")) {
if (is_arg_used(modules$server, "datasets")) {
args <- c(args, datasets = datasets)
if (is_arg_used(modules$server, "data")) {
data <- eventReactive(trigger_data(), .datasets_to_data(modules, datasets))
args <- c(args, data = list(data))
if (is_arg_used(modules$server, "filter_panel_api")) {
filter_panel_api <- teal.slice::FilterPanelAPI$new(datasets)
args <- c(args, filter_panel_api = filter_panel_api)
observeEvent(
ignoreNULL = TRUE,
once = TRUE,
eventExpr = trigger_module(),
handlerExpr = {
module_output <- if (is_arg_used(modules$server, "id")) {
do.call(modules$server, args)
reactive(modules)
checkmate::assert_class(module, "teal_module")
checkmate::assert_class(datasets, "FilteredData")
datanames <- if (is.null(module$datanames) || identical(module$datanames, "all")) {
datasets$datanames()
include_parent_datanames(
module$datanames,
datasets$get_join_keys()
data <- sapply(datanames, function(x) datasets$get_data(x, filtered = TRUE), simplify = FALSE)
hashes <- calculate_hashes(datanames, datasets)
code <- c(
get_rcode_str_install(),
get_rcode_libraries(),
get_datasets_code(datanames, datasets, hashes)
data <- do.call(
teal.data::teal_data,
args = c(data, code = list(code), join_keys = list(datasets$get_join_keys()[datanames]))
data@verified <- attr(datasets, "verification_status")
data
sapply(datanames, function(x) rlang::hash(datasets$get_data(x, filtered = FALSE)), simplify = FALSE)
checkmate::assert_class(modules, "teal_modules")
checkmate::assert_list(datasets, types = c("list", "FilteredData"))
checkmate::assert_class(reporter, "Reporter")
checkmate::assert_class(filter, "teal_slices")
moduleServer(id, function(input, output, session) {
logger::log_trace("srv_tabs_with_filters initializing the module.")
is_module_specific <- isTRUE(attr(filter, "module_specific"))
manager_out <- filter_manager_modal_srv("filter_manager", filtered_data_list = datasets, filter = filter)
active_module <- srv_nested_tabs(
id = "root",
datasets = datasets,
modules = modules,
reporter = reporter,
is_module_specific = is_module_specific
if (!is_module_specific) {
active_datanames <- reactive({
if (identical(active_module()$datanames, "all")) {
include_parent_datanames(
active_module()$datanames,
singleton$get_join_keys()
singleton <- unlist(datasets)[[1]]
singleton$srv_filter_panel("filter_panel", active_datanames = active_datanames)
observeEvent(
eventExpr = active_datanames(),
handlerExpr = {
script <- if (length(active_datanames()) == 0 || is.null(active_datanames())) {
"handleActiveDatasetsPresent();"
shinyjs::runjs(script)
ignoreNULL = FALSE
showNotification("Data loaded - App fully started up")
logger::log_trace("srv_tabs_with_filters initialized the module")
active_module
checkmate::assert_character(id, max.len = 1, any.missing = FALSE)
checkmate::assert_multi_class(data, c("teal_data", "teal_data_module"))
checkmate::assert(
.var.name = "title",
checkmate::check_string(title),
checkmate::check_multi_class(title, c("shiny.tag", "shiny.tag.list", "html"))
checkmate::assert(
.var.name = "header",
checkmate::check_string(header),
checkmate::check_multi_class(header, c("shiny.tag", "shiny.tag.list", "html"))
checkmate::assert(
.var.name = "footer",
checkmate::check_string(footer),
checkmate::check_multi_class(footer, c("shiny.tag", "shiny.tag.list", "html"))
ns <- NS(id)
splash_ui <- if (inherits(data, "teal_data_module")) {
data$ui(ns("teal_data_module"))
} else if (inherits(data, "teal_data")) {
div()
ui_teal(
id = ns("teal"),
splash_ui = div(splash_ui, uiOutput(ns("error"))),
title = title,
header = header,
footer = footer
checkmate::assert_character(id, max.len = 1, any.missing = FALSE)
checkmate::assert_multi_class(data, c("teal_data", "teal_data_module"))
checkmate::assert_class(modules, "teal_modules")
checkmate::assert_class(filter, "teal_slices")
moduleServer(id, function(input, output, session) {
logger::log_trace("srv_teal_with_splash initializing module with data.")
if (getOption("teal.show_js_log", default = FALSE)) {
teal_data_rv <- if (inherits(data, "teal_data_module")) {
data <- data$server(id = "teal_data_module")
if (!is.reactive(data)) {
stop("The `teal_data_module` passed to `data` must return a reactive expression.", call. = FALSE)
data
} else if (inherits(data, "teal_data")) {
reactiveVal(data)
teal_data_rv_validate <- reactive({
data <- tryCatch(teal_data_rv(), error = function(e) e)
if (inherits(data, "shiny.silent.error") && identical(data$message, "")) {
if (inherits(data, "qenv.error")) {
validate(
need(
FALSE,
paste(
"Error when executing `teal_data_module` passed to `data`:\n ",
paste(data$message, collapse = "\n"),
"\n Check your inputs or contact app developer if error persists."
if (inherits(data, "error")) {
validate(
need(
FALSE,
paste(
"Error when executing `teal_data_module` passed to `data`:\n ",
paste(data$message, collpase = "\n"),
"\n Check your inputs or contact app developer if error persists."
validate(
need(
inherits(data, "teal_data"),
paste(
"Error: `teal_data_module` passed to `data` failed to return `teal_data` object, returned",
toString(sQuote(class(data))),
"instead.",
"\n Check your inputs or contact app developer if error persists."
if (!length(teal.data::datanames(data))) {
warning("`data` object has no datanames. Default datanames are set using `teal_data`'s environment.")
is_modules_ok <- check_modules_datanames(modules, teal_data_datanames(data))
if (!isTRUE(is_modules_ok)) {
validate(need(isTRUE(is_modules_ok), sprintf("%s. Contact app developer.", is_modules_ok)))
is_filter_ok <- check_filter_datanames(filter, teal_data_datanames(data))
if (!isTRUE(is_filter_ok)) {
showNotification(
"Some filters were not applied because of incompatibility with data. Contact app developer.",
type = "warning",
duration = 10
warning(is_filter_ok)
teal_data_rv()
output$error <- renderUI({
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.")
res
script <- sprintf(
"Shiny.setInputValue(`%s`, Intl.DateTimeFormat().resolvedOptions().timeZone)",
ns("timezone")
shinyjs::runjs(script) # function does not return anything
invisible(NULL)
bs_theme <- getOption("teal.bs_theme")
if (is.null(bs_theme)) {
NULL
} else if (!inherits(bs_theme, "bs_theme")) {
warning("teal.bs_theme has to be of a bslib::bs_theme class, the default shiny bootstrap is used.")
NULL
bs_theme
parents <- character(0)
for (i in dataname) {
while (length(i) > 0) {
parent_i <- teal.data::parent(join_keys, i)
parents <- c(parent_i, parents)
i <- parent_i
unique(c(parents, dataname))
checkmate::assert_class(x, "teal_data")
checkmate::assert_character(datanames, min.chars = 1L, any.missing = FALSE)
ans <- teal.slice::init_filtered_data(
x = sapply(datanames, function(dn) x[[dn]], simplify = FALSE),
join_keys = teal.data::join_keys(x)
attr(ans, "preprocessing_code") <- teal.data::get_code(x, datanames = datanames, check_names = FALSE)
attr(ans, "verification_status") <- x@verified
ans
checkmate::assert_string(title)
checkmate::assert_string(label)
checkmate::assert_string(description, null.ok = TRUE)
checkmate::assert_flag(with_filter)
checkmate::assert_class(filter_panel_api, classes = "FilterPanelAPI")
card <- teal::TealReportCard$new()
title <- if (label == "") title else label
card$set_name(title)
card$append_text(title, "header2")
if (!is.null(description)) card$append_text(description, "header3")
if (with_filter) card$append_fs(filter_panel_api$get_filter_state())
card
checkmate::assert_class(modules, "teal_modules")
checkmate::assert_character(datanames)
recursive_check_datanames <- function(modules, datanames) {
if (inherits(modules, "teal_modules")) {
sapply(modules$children, function(module) recursive_check_datanames(module, datanames = datanames))
extra_datanames <- setdiff(modules$datanames, c("all", datanames))
if (length(extra_datanames)) {
sprintf(
"- Module '%s' uses datanames not available in 'data': (%s) not in (%s)",
modules$label,
toString(dQuote(extra_datanames, q = FALSE)),
toString(dQuote(datanames, q = FALSE))
check_datanames <- unlist(recursive_check_datanames(modules, datanames))
if (length(check_datanames)) {
paste(check_datanames, collapse = "\n")
TRUE
checkmate::assert_class(filters, "teal_slices")
checkmate::assert_character(datanames)
out <- unlist(sapply(
filters, function(filter) {
dataname <- shiny::isolate(filter$dataname)
if (!dataname %in% datanames) {
sprintf(
"- Filter '%s' refers to dataname not available in 'data':\n %s not in (%s)",
shiny::isolate(filter$id),
dQuote(dataname, q = FALSE),
toString(dQuote(datanames, q = FALSE))
if (length(out)) {
paste(out, collapse = "\n")
TRUE
checkmate::assert_class(data, "teal_data")
checkmate::assert_multi_class(modules, c("teal_modules", "teal_module"))
checkmate::assert_class(filters, "modules_teal_slices")
checkmate::assert_r6(filtered_data_singleton, "FilteredData")
if (!isTRUE(attr(filters, "module_specific"))) {
slices <- shiny::isolate({
Filter(function(x) x$id %in% attr(filters, "mapping")$global_filters, filters)
filtered_data_singleton$set_filter_state(slices)
return(modules_structure(modules, filtered_data_singleton))
if (inherits(modules, "teal_module")) {
datanames <-
if (is.null(modules$datanames) || identical(modules$datanames, "all")) {
include_parent_datanames(
teal_data_datanames(data),
teal.data::join_keys(data)
include_parent_datanames(
modules$datanames,
teal.data::join_keys(data)
slices <- shiny::isolate({
Filter(x = filters, f = function(x) {
x$dataname %in% datanames &&
(x$id %in% attr(filters, "mapping")$global_filters ||
x$id %in% unique(unlist(attr(filters, "mapping")[modules$label]))) # nolint: indentation_linter.
slices$include_varnames <- attr(slices, "include_varnames")[names(attr(slices, "include_varnames")) %in% datanames]
slices$exclude_varnames <- attr(slices, "exclude_varnames")[names(attr(slices, "exclude_varnames")) %in% datanames]
filtered_data <- teal_data_to_filtered_data(data, datanames)
filtered_data$set_filter_state(slices)
return(filtered_data)
} else if (inherits(modules, "teal_modules")) {
ans <- lapply(
modules$children,
modules_datasets,
data = data,
filters = filters,
filtered_data_singleton = filtered_data_singleton
names(ans) <- vapply(modules$children, `[[`, character(1), "label")
return(ans)
if (inherits(modules, "teal_module")) {
return(value)
stats::setNames(
lapply(modules$children, modules_structure, value),
vapply(modules$children, `[[`, character(1), "label")
checkmate::assert_class(data, "teal_data")
if (length(teal.data::datanames(data))) {
teal.data::datanames(data)
ls(teal.code::get_env(data), all.names = TRUE)
checkmate::assert_class(shiny_tag, "shiny.tag")
checkmate::assert_true(shiny_tag$name == "head")
child_names <- vapply(shiny_tag$children, `[[`, character(1L), "name")
checkmate::assert_subset(c("title", "link"), child_names, .var.name = "child tags")
rel_attr <- shiny_tag$children[[which(child_names == "link")]]$attribs$rel
checkmate::assert_subset(
rel_attr,
c("icon", "shortcut icon"),
.var.name = "Link tag's rel attribute",
empty.ok = FALSE
checkmate::assert_string(title, null.ok = TRUE)
checkmate::assert_string(favicon, null.ok = TRUE)
tags$head(
tags$title(title),
tags$link(
rel = "icon",
href = favicon,
sizes = "any"
checkmate::assert_multi_class(data, c("teal_data", "teal_data_module"))
checkmate::assert_class(modules, "teal_modules")
data <- if (inherits(data, "teal_data")) {
as.list(data@env)
} else if (inherits(data, "teal_data_module")) {
deparse1(body(data$server))
modules <- lapply(modules, defunction)
rlang::hash(list(data = data, modules = modules))
if (is.list(x)) {
lapply(x, defunction)
} else if (is.function(x)) {
deparse1(body(x))
x
checkmate::assert_class(tss, "teal_slices")
checkmate::assert_path_for_output(file, overwrite = TRUE, extension = "json")
cat(format(tss, trim_lines = FALSE), "\n", file = file)
checkmate::assert_file_exists(file, access = "r", extension = "json")
tss_json <- jsonlite::fromJSON(file, simplifyDataFrame = FALSE)
tss_json$slices <-
lapply(tss_json$slices, function(slice) {
for (field in c("selected", "choices")) {
if (!is.null(slice[[field]])) {
if (length(slice[[field]]) > 0) {
date_partial_regex <- "^[0-9]{4}-[0-9]{2}-[0-9]{2}"
time_stamp_regex <- paste0(date_partial_regex, "\\s[0-9]{2}:[0-9]{2}:[0-9]{2}\\sUTC$")
slice[[field]] <-
if (all(grepl(paste0(date_partial_regex, "$"), slice[[field]]))) {
as.Date(slice[[field]])
} else if (all(grepl(time_stamp_regex, slice[[field]]))) {
as.POSIXct(slice[[field]], tz = "UTC")
slice[[field]]
slice[[field]] <- character(0)
slice
tss_elements <- lapply(tss_json$slices, as.teal_slice)
do.call(teal_slices, c(tss_elements, tss_json$attributes))
checkmate::assert_string(msg, null.ok = TRUE)
checkmate::assert_data_frame(x)
if (!is.null(min_nrow)) {
if (complete) {
complete_index <- stats::complete.cases(x)
validate(need(
sum(complete_index) > 0 && nrow(x[complete_index, , drop = FALSE]) >= min_nrow,
paste(c(paste("Number of complete cases is less than:", min_nrow), msg), collapse = "\n")
validate(need(
nrow(x) >= min_nrow,
paste(
c(paste("Minimum number of records not met: >=", min_nrow, "records required."), msg),
collapse = "\n"
if (!allow_inf) {
validate(need(
all(vapply(x, function(col) !is.numeric(col) || !any(is.infinite(col)), logical(1))),
"Dataframe contains Inf values which is not allowed."
logger::log_trace("init initializing teal app with: data ('{ class(data) }').")
if (inherits(data, "TealData")) {
checkmate::assert_multi_class(data, c("teal_data", "teal_data_module"))
checkmate::assert(
.var.name = "modules",
checkmate::check_multi_class(modules, c("teal_modules", "teal_module")),
checkmate::check_list(modules, min.len = 1, any.missing = FALSE, types = c("teal_module", "teal_modules"))
if (inherits(modules, "teal_module")) {
modules <- list(modules)
if (checkmate::test_list(modules, min.len = 1, any.missing = FALSE, types = c("teal_module", "teal_modules"))) {
modules <- do.call(teal::modules, modules)
checkmate::assert_class(filter, "teal_slices")
checkmate::assert(
.var.name = "title",
checkmate::check_string(title),
checkmate::check_multi_class(title, c("shiny.tag", "shiny.tag.list", "html"))
checkmate::assert(
.var.name = "header",
checkmate::check_string(header),
checkmate::check_multi_class(header, c("shiny.tag", "shiny.tag.list", "html"))
checkmate::assert(
.var.name = "footer",
checkmate::check_string(footer),
checkmate::check_multi_class(footer, c("shiny.tag", "shiny.tag.list", "html"))
checkmate::assert_character(id, max.len = 1, any.missing = FALSE)
teal.logger::log_system_info()
landing <- extract_module(modules, "teal_module_landing")
landing_module <- NULL
if (length(landing) == 1L) {
} else if (length(landing) > 1L) {
attr(filter, "app_id") <- create_app_id(data, modules)
filter <- as.teal_slices(as.list(filter))
if (isTRUE(attr(filter, "module_specific"))) {
if (inherits(data, "teal_data")) {
if (length(teal_data_datanames(data)) == 0) {
stop("The environment of `data` is empty.")
is_modules_ok <- check_modules_datanames(modules, teal_data_datanames(data))
if (!isTRUE(is_modules_ok)) {
logger::log_error(is_modules_ok)
checkmate::assert(is_modules_ok, .var.name = "modules")
is_filter_ok <- check_filter_datanames(filter, teal_data_datanames(data))
if (!isTRUE(is_filter_ok)) {
warning(is_filter_ok)
res <- list(
ui = ui_teal_with_splash(id = id, data = data, title = title, header = header, footer = footer),
server = function(input, output, session) {
logger::log_trace("init teal app has been initialized.")
res
expr <- substitute(expr)
extras <- list(...)
if (!identical(as.list(expr)[[1L]], as.symbol("{"))) {
expr <- call("{", expr)
calls <- as.list(expr)[-1]
calls <- lapply(calls, function(x) do.call(substitute, list(x, env = extras)))
eval_code(object = data, code = as.expression(calls))
css_files <- list.files(
system.file("css", package = "teal", mustWork = TRUE),
pattern = pattern, full.names = TRUE
singleton(
tags$head(lapply(css_files, includeCSS))
checkmate::assert_character(except, min.len = 1, any.missing = FALSE, null.ok = TRUE)
js_files <- list.files(system.file("js", package = "teal", mustWork = TRUE), pattern = pattern, full.names = TRUE)
js_files <- js_files[!(basename(js_files) %in% except)] # no-op if except is NULL
singleton(lapply(js_files, includeScript))
checkmate::assert_character(files, min.len = 1, any.missing = FALSE)
lapply(files, function(file) {
shinyjs::runjs(paste0(readLines(system.file("js", file, package = "teal", mustWork = TRUE)), collapse = "\n"))
invisible(NULL)
tagList(
shinyjs::useShinyjs(),
include_css_files(),
include_js_files(except = "init.js"),
shinyjs::hidden(icon("gear")), # add hidden icon to load font-awesome css for icons
checkmate::assert_string(label)
module(
label,
server = function(id, data) {
ui = function(id) {
datanames = datanames
teal_data_module(
ui = function(id) {
ns <- NS(id)
object$ui(ns("mutate_inner"))
server = function(id) {
moduleServer(id, function(input, output, session) {
teal_data_rv <- object$server("mutate_inner")
if (!is.reactive(teal_data_rv)) {
stop("The `teal_data_module` must return a reactive expression.", call. = FALSE)
td <- eventReactive(teal_data_rv(),
if (inherits(teal_data_rv(), c("teal_data", "qenv.error"))) {
eval_code(teal_data_rv(), code)
teal_data_rv()
ignoreNULL = FALSE
td
eval_code(object, code = paste(lang2calls(code), collapse = "\n"))
eval_code(object, code = paste(lang2calls(code), collapse = "\n"))
checkmate::assert_string(label)
checkmate::assert_list(server_args, names = "named")
checkmate::assert_true(all(names(server_args) %in% names(formals(teal.reporter::reporter_previewer_srv))))
logger::log_info("Initializing reporter_previewer_module")
srv <- function(id, reporter, ...) {
ui <- function(id, ...) {
module <- module(
label = "temporary label",
server = srv, ui = ui,
server_args = server_args, ui_args = list(), datanames = NULL
class(module) <- c("teal_module_previewer", class(module))
module$label <- label
module
checkmate::assert_function(ui, args = "id", nargs = 1)
checkmate::assert_function(server, args = "id", nargs = 1)
structure(
list(ui = ui, server = server),
class = "teal_data_module"
CRAN release: 2024-03-07
-datanames
of modules that crashed module-specific applications when filtering child datasets.CRAN release: 2024-03-07
+CRAN release: 2024-02-22
-module_specific
feature when a teal
module specified datanames
of length more than 1.CRAN release: 2024-02-22
+module_specific
feature when a teal
module specified datanames
of length more than 1.CRAN release: 2024-02-08
-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.CRAN release: 2024-02-08
+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.?snapshot
.as_tdata
function to facilitate migration of modules to the new teal_data
class.build_app_title
function to facilitate adding favicons to app title.data
argument in init
now accepts only teal_data
and teal_data_module
.tdata
has been deprecated and replaced with teal_data
. Support for tdata
passed to the data
argument in module(server)
will be removed in the next release.module
validation checks so that it won’t throw messages about data
argument unnecessarily.module
validation checks so that it won’t throw messages about data
argument unnecessarily.teal_slices
and made modifications to init
to enable tagging teal_slices
with an app id to safely upload snapshots from disk.FilteredData
no longer stores pre-processing code in specific slots. Code is now attached as attribute. Adjusted appropriately.module_specific
in teal::teal_slices
documentation.module_specific
in teal::teal_slices
documentation.?snapshot
.reporter_previewer_module
to customize default values through srv_args
.reporter_previewer_module
in a list of modules to override default one.filter
argument in teal::init
requires teal_slices
object now. Details in documentation of teal::init
.filter
argument in teal::init
requires teal_slices
object now. Details in documentation of teal::init
.filters
argument in module
and replaced it with datanames
. Details in documentation of teal::module
datasets
argument in modules
has been deprecated and will be removed in a future release. Please use data
argument instead. data
is of type tdata
; see “Creating custom modules” vignettes and function documentation of teal::new_tdata
for further details.datasets
argument in modules
has been deprecated and will be removed in a future release. Please use data
argument instead. data
is of type tdata
; see “Creating custom modules” vignettes and function documentation of teal::new_tdata
for further details.chunks
in teal.code
, the teal
framework now uses their replacement (qenv
) instead. The documentation in teal
has been updated to reflect this and custom modules written with chunks
should be updated to use qenv
.chunks
in teal.code
, get_rcode
, get_rcode_srv
, and get_rcode_ui
have been removed.validate_inputs
function that transfers input validation messages to app output.validate_inputs
function that transfers input validation messages to app output.modules
argument of init
accepts teal_module
type of object. There is no need to wrap up a single module in modules()
or list()
.module_nested_tabs
so that only active modules are calculated in a teal
app.scda.2022
.scda.2022
.teal
applications.rlang
instead of digest
package to calculate the hash (which has been moved from teal.data
and teal.slice
). There is now an explicit hashing check in the reproducible code output.root_modules
, default_filter
, bookmarkableShinyApp
, as well as deprecated logging mechanism, including the functions log_app_usage
and .log
.teal
app with bslib::run_with_themer
.srv_nested_tabs
documentation.header
and footer
arguments in init
to empty text.bslib
bootstrap themes in teal::init
apps, please read more in the new teal-bs-themes
vignette.bslib
bootstrap themes in teal::init
apps, please read more in the new teal-bs-themes
vignette.filterable
attributes for the per-dataset lists in the filter
argument of init
.filterable
attributes for the per-dataset lists in the filter
argument of init
.teal_module
to have data
argument which receives a list of reactive filter data with "code"
and "join_keys"
attributes.teal_module
to have filter_panel_api
argument which receives a FilterPanelAPI
object.module_teal
to reflect changes in teal.slice
.teal_module
to no longer receive datasets
object in the ...
argument. In order to use datasets
in the teal_module
please specify datasets
explicitly.teal_module
to no longer receive datasets
object in the ...
argument. In order to use datasets
in the teal_module
please specify datasets
explicitly.merge_expression
argument in get_rcode_srv
function and removed it in get_rcode
function.session
argument in get_rcode
function.reporter_previewer_module
to wrap the teal.reporter
package previewer functionality as a teal
module.reporter_previewer_module
to wrap the teal.reporter
package previewer functionality as a teal
module.teal
to support modules
which include reporting. If any module
which supports reporting is included then a reporter_previewer_module
is included.module()
and the server argument is now a function where the second argument can be ...
or datasets
.bookmarkableShinyApp
. In future releases the teal
framework will stop supporting shiny
bookmarking (which has not officially been supported); it may be officially supported in the future. Note the filter panel in teal.slice
retains its ability to save and restore its state if used in a standalone shiny
app with bookmarking.bookmarkableShinyApp
. In future releases the teal
framework will stop supporting shiny
bookmarking (which has not officially been supported); it may be officially supported in the future. Note the filter panel in teal.slice
retains its ability to save and restore its state if used in a standalone shiny
app with bookmarking.teal.data
: creating and loading the data needed for teal
applications.teal.widgets
: shiny
components used within teal
.teal.logger
: standardizes logging within teal
framework.The teal
package contains the code to create apps (teal::init
), to create a module (teal::module
) and to group modules in the app (teal::modules
). teal
depends on teal.transform
and teal.data
which contain the functions that teal
app creators are likely to need. The other package teal
only imports from and therefore teal
module creators should either fully specify functions from these packages when required or import them into custom packages as library(teal)
will not load them.
The teal
package contains the code to create apps (teal::init
), to create a module (teal::module
) and to group modules in the app (teal::modules
). teal
depends on teal.transform
and teal.data
which contain the functions that teal
app creators are likely to need. The other package teal
only imports from and therefore teal
module creators should either fully specify functions from these packages when required or import them into custom packages as library(teal)
will not load them.
teal
module named example_module
has been included in the package.teal
package has been split into multiple smaller packages, see above.root_modules
function, users should use modules
directly inside init
.root_modules
any label
argument to modules
must be explicitly named. For example modules("lab", mod1, mod2)
should be replaced with modules(label = "lab", mod1, mod2)
.teal
: main module panel now has fixed shiny
name root
and the active tab is named active_tab
not Active_tab
.teal
: main module panel now has fixed shiny
name root
and the active tab is named active_tab
not Active_tab
.MultiAssayExperiment
is now suggested packages, not required. Objects dependent on MultiAssayExperiment
are changed to lazy-load this now suggested package.HTML
identifiers of teal
modules - now each nested module receives its own shiny
namespace.HTML
identifiers of teal
modules - now each nested module receives its own shiny
namespace.raw_dataset
, raw_dataset_connector
, named_dataset
, named_dataset_file
, named_dataset_connector
, relational_dataset
, relational_dataset_file
, relational_dataset_connector
, key
, as_cdisc
, as_cdisc_relational
.rcd_connection
and rcd_data
; scda_dataset_connectors
can be passed into cdisc_data
and teal_data
directly.rcd_dataset_connector
and rcd_cdisc_dataset_connector
with scda_dataset_connector
and scda_cdisc_dataset_connector
respectively.teal_show_js_log
option into teal.show_js_log
to match options naming convention.%is_in%
and stop_shiny
internal utility functions.logger
package.logger
package.register_logger
, which registers a logger in a given namespace.teal
framework.pid
and shiny
session token into footnote so app developers can identify logs for apps.Added print methods to the DatasetConnector
, RelationalData
, RelationalDataconnector
and JoinKeys
classes and added input validation to the implementation of the print method that was already in the Dataset
object.
Added print methods to the DatasetConnector
, RelationalData
, RelationalDataconnector
and JoinKeys
classes and added input validation to the implementation of the print method that was already in the Dataset
object.
Added public facing constructor functions for CDISCDataConnector
, RelationalDataConnector
, and DataConnection
classes.
Modified data_extract_spec
to allow both the filter
and select
parameters to be NULL
, which results in the data_extract_ui
acting as if a filter_spec
with all variables as possible choices had been supplied as the filter
argument and a select_spec
with the multiple
parameter set to TRUE
had been supplied as the select
argument.
Added support of the full screen for a module
when the filters
argument is equal NULL
.
choices_selected
now correctly removes duplicates from the array passed to its choices
parameter.FilterState
in case of using MultiAssayExperiment::subsetByColData
. Now single condition for variable containing NA
values is !is.na(var) & var == <condition>
.DatasetConnector
being dependent on other Dataset
or DatasetConnector
objects.mae_dataset()
in favor of more general dataset()
constructor.mae_dataset()
in favor of more general dataset()
constructor.teal
applications users can apply using R
options.label
argument of select_spec
and filter_spec
.FilteredDataset::get_data
to accept logical input only.shiny
version >= 1.7.MultiAssayExperiment
to the teal::init
using mae_dataset
function or through the connectors.MultiAssayExperiment
to the teal::init
using mae_dataset
function or through the connectors.MultiAssayExperiment
objects. Filters can be set on a subject level (colData
of MAE
object) and on a experiment level (colData
and rowData
of an assay).cdse_dataset_connector
to create delayed data objects from CDSE
.datasetdb_dataset_connector
to create delayed data objects from DataSetDB
.ricepass_connection
to create delayed data objects from entimICE
via ricepass
.Dataset
type determines an appearance and a functionality of related filters and filters summary.Datasets
are passed (by reference) from DDL
to FilteredData
skipping extracting data and their attributes.mutate_dataset
multiple times on the same DatasetConnector
or Dataset
object.mutate_dataset
multiple times on the same DatasetConnector
or Dataset
object.get_code
function to not reproduce its raw data set.filter_spec
to allow no variable selection upon app initialization, where the first possible value was previously selected.modules
parameter of teal::init
function can also receive a list
except root_modules
function call.split
and merge
methods to the JoinKeys
object.all_choices()
as a possible argument to the selected
parameter of filter_spec
, select_spec
and choices_selected
indicating that all choices are selected.Dataset
and DatasetConnector
objects.teal_data
to return a CDISCData
object whenever any of its arguments is a type of CDISCData
object.LICENCE
and README
with new package references.LICENCE
and README
with new package references.get_hash
to the Dataset
class returning the MD5 hash of the object stored inside the Dataset
object.random.cdisc.data
with scda
in examples and tests.JoinKeys
in Dataset
and DatasetConnector
classes.%>%
is now exported such that downstream code and packages can use it.rice
package from the documentation.mutate_data
with RelationalDataConnector
.mutate_data
with RelationalDataConnector
.as_cdisc
to behave similarly to cdisc_dataset
when called on a Dataset
object.data_extract_spec
UI elements. Both are now compressed to <data name>.<column name>
if they don’t change during runtime of the app.ADSAFTTE
to the list of recognized ADaM
dataset names.data_extract_spec
’s doc string showcasing app users can choose a variable used for filtering in the encoding panel.teal
modules.Date
or datetime
column is selected from a filter_spec
.snowflake
connection and connectors.snowflake
connection and connectors.teal
app (ending a user shiny
session), all DataConnection
s will now try to close their connections.ADHY
keys to configuration file.filter_spec
function: the parameter choices
is no longer mandatory (the function will take all possible choices by default) and the vars
parameter additionally accepts the choices_selected
and allows to change the variables for filtering using the UI elements in the encoding panel.value_choices
function to handle edge case when "NA"
and NA
values exist in the character
column that choices are derived from.value_choices
function to handle edge case when "NA"
and NA
values exist in the character
column that choices are derived from.Callable
class.FilteredData
class.FilteredData
class.JoinKeys
class (with join_keys()
constructors and join_key()
constructor for its elements) to store joining key columns between datasets.dataset()
constructor, added cdisc_dataset()
constructor and as_cdisc()
conversion function.keys()
).get_keys()
and set_keys()
functions to extract and manipulate datasets primary keys respectively.filtered_data_new
, filtered_data_set
and filtered_data_set_filters
.teal::cdisc_dataset
and other teal::RelationalDataset
constructors should now be shown when getting the code from teal::cdisc_data
objects and other teal::RelationalData
objects.teal::cdisc_dataset
and other teal::RelationalDataset
constructors should now be shown when getting the code from teal::cdisc_data
objects and other teal::RelationalData
objects.variable_choices
to use datasets with missing labels.NULL
to selected
argument of select_spec
function.python_dataset_connector
to create delayed data objects from python scripts or directly from python code.python_dataset_connector
to create delayed data objects from python scripts or directly from python code.python_dataset_connector
is not yet ready to be deployed on RSConnect
because it does not contain numpy
and pandas
, which are Python
libraries used in python_dataset_connector
.Date
and Datetime
variables in the Filter Panel.date
and datetime
filter widgets to reset the value to the original.check_key_duplicates
, which creates a short summary about rows with duplicated primary key (row numbers and the number of duplicates)character
and factor
variables in the Filter Panel.character
and factor
variables in the Filter Panel.module_filter_panel
, not only those of types numeric
, logical
, factor
, character
and Date
mutate_data
to accept the whole scope of objects for vars
.data_extract_spec
.
drop_keys
to filter_spec
to decide whether to drop or keep keys columns on single filter on those columns.keys
to variable_choices
. keys
specifies the names of the variables, which should have the new key icon shown next to them in the variable drop down menus in the left-hand side encoding panels instead of the icon appropriate for their original R variable type. variable_choices
now also works with RelationalDataset
and RelationalDatasetConnector
objects.include_factors
option in get_class_colnames
in RawDataset
.arm_ref_comp
objects.arm_ref_comp
objects.width
argument in optionalSelectInput
.lifecycle
badges to all exported functions.cdisc_dataset
and dataset
now return R6 class objects (RelationalDataset
).
cdisc_dataset
and dataset
now return R6 class objects (RelationalDataset
).
A new teal_data
function to include datasets and connectors into teal
application.
cdisc_data
function to include datasets and connectors into teal
application where a check
argument still could be used and other consistency tests are performed.
get_raw_data
can be used to derive raw data from R6 objects e.g. (RelationalDataset
).
keys
function to keys
object.choices_selected
.choices_selected
when selected
is not in choices
.pickerInput
not to display column name as label if it’s missing.teal
app to initialize without data. The data are then loaded from within the teal
app.teal
app to initialize without data. The data are then loaded from within the teal
app.DatasetConnector
, DataConnector
) to connect to various data sources, including: * connector to rice
API - rice_data
and rice_dataset_connector
* connector to RDS
files - rds_data
and rds_dataset_connector
shiny
app when shiny
is busy to update the views.labels
argument of cdisc_data
function. Labels should now already be present in the data passed to the cdisc_data
function. This can be achieved using the var_relabel
function.choices_labeled
and fix bug of not showing column name in data_extract_spec
.choices_labeled
and fix bug of not showing column name in data_extract_spec
.cdisc_dataset
(and more general dataset
) functions to properly handle dataset keys while merging.cdisc_dataset
(and more general dataset
) functions to properly handle dataset keys while merging..css
and .js
files.columns_spec
to select_spec
.variable_choices
and value_choices
.cdisc_data
and get_code
to deal with preprocessing and moving a step towards data standard independent teal.cdisc_data
and get_code
to deal with preprocessing and moving a step towards data standard independent teal.teal.utils
functions to teal
: log_app_usage
, stop_shiny
.*_spec
functions.PickerInput
and SelectInput
.teal
crashes when a filter variable gets added that has many decimal places.teal
crashes when a filter variable gets added that has many decimal places.tabs
arguments were renamed to modules
.tabs
arguments were renamed to modules
.tab_item
function is now called module
.Fixes #nnn
- + + + + - - + + diff --git a/latest-tag/reference/TealReportCard.html b/latest-tag/reference/TealReportCard.html index e777febcf4..7016c1b1b4 100644 --- a/latest-tag/reference/TealReportCard.html +++ b/latest-tag/reference/TealReportCard.html @@ -1,16 +1,32 @@ - - + + + + + +Inherited methods
+
teal.reporter::ReportCard$append_content()
teal.reporter::ReportCard$append_metadata()
teal.reporter::ReportCard$append_plot()
teal.reporter::ReportCard$append_rcode()
Public methods
teal.reporter::ReportCard$reset()
teal.reporter::ReportCard$set_name()
teal.reporter::ReportCard$to_list()
append_src()
Appends the source code to the content
meta data of this TealReportCard
.
TealReportCard$append_src(src, ...)
append_src()
+Appends the source code to the content
meta data of this TealReportCard
.
src
src
(character(1)
) code as text.
Object of class TealReportCard
, invisibly.
card <- TealReportCard$new()$append_src(
+Examples
+
+
+
+card <- TealReportCard$new()$append_src(
"plot(iris)"
)
-card$get_content()[[1]]$get_content()
+card$get_content()[[1]]$get_content()
append_fs()
+Appends the filter state list to the content
and metadata
of this TealReportCard
.
If the filter state list has an attribute named formatted
, it appends it to the card otherwise it uses
the default yaml::as.yaml
to format the list.
-If the filter state list is empty, nothing is appended to the content
.
TealReportCard$append_fs(fs)
content
.
+
fs
fs
(teal_slices
) object returned from teal_slices()
function.
append_encodings()
Appends the encodings list to the content
and metadata
of this TealReportCard
.
append_encodings()
+Appends the encodings list to the content
and metadata
of this TealReportCard
.
card <- TealReportCard$new()$append_encodings(list(variable1 = "X"))
+Examples
+
+
+
+card <- TealReportCard$new()$append_encodings(list(variable1 = "X"))
card$get_content()[[1]]$get_content()
-
+
clone()
The objects of this class are cloneable with this method.
+ +teal.reporter::ContentBlock
-> teal.reporter::TextBlock
-> TealSlicesBlock
new()
Returns a TealSlicesBlock
object.
TealSlicesBlock$new(content = teal_slices(), style = "verbatim")
new()
+Returns a TealSlicesBlock
object.
TealSlicesBlock$new(content = teal_slices(), style = "verbatim")
content
content
(teal_slices
) object returned from teal_slices()
function.
Returns a TealSlicesBlock
object with no content and no parameters.
set_content()
+Sets content of this TealSlicesBlock
.
Sets content as YAML
text which represents a list generated from teal_slices
.
The list displays limited number of fields from teal_slice
objects, but this list is
sufficient to conclude which filters were applied.
-When selected
field in teal_slice
object is a range, then it is displayed as a "min"
TealSlicesBlock$set_content(content)
selected
field in teal_slice
object is a range, then it is displayed as a "min"
+
content
content
(teal_slices
) object returned from teal_slices()
function.
from_list()
Create the RcodeBlock
from a list.
to_list()
Convert the RcodeBlock
to a list.
(character
) The browser title for the teal
app.
A shiny.tag
containing the element that adds the title and logo to the shiny
app.
Calculate app ID that will be used to stamp filter state snapshots. App ID is a hash of the app's data and modules. See "transferring snapshots" section in ?snapshot.
(character(1)
) Label shown in the navigation item for the module or module group.
For modules()
defaults to "root"
. See Details
.
datanames
also determines
a subset of datasets which are appended to the data
argument in server function.
-(character(1)
)
shiny
module id.
A reactive
expression containing the slices active in this module.
This module tracks the state of a single FilteredData
object and global teal_slices
and updates both objects as necessary. Filter states added in different modules
Filter states added any individual module are added to global teal_slices
@@ -154,17 +186,19 @@
(shiny
) input variable accessible with input$tz
which is a (character
)
@@ -137,17 +164,19 @@
(character
) names of datasets to extract code from
Character string concatenated from the following elements:
data pre-processing code (from data
argument in init
)
Character string concatenated from the following elements:
+data pre-processing code (from data
argument in init
)
hash check of loaded objects
filter code (if any)
teal
functionsteal
functions
+Main functions needed to build a teal
app
Main functions needed to build a teal
app
init()
shiny
appteal_data_module()
eval_code(<teal_data_module>,<character>)
within(<teal_data_module>)
teal
applicationsmodule()
modules()
format(<teal_module>)
print(<teal_module>)
format(<teal_modules>)
print(<teal_modules>)
teal_module
and teal_modules
objectsui_teal_with_splash()
srv_teal_with_splash()
teal
applicationteal_slices()
as.teal_slices()
c(<teal_slices>)
teal
applicationsbuild_app_title()
example_module()
teal
modulereporter_previewer_module()
teal
module for previewing a reportTealReportCard
TealReportCard
report_card_template()
TealReportCard
creation and customizationlanding_popup_module()
as_tdata()
teal_data
objects in modules for compatibilitynew_tdata()
tdata
objectget_code_tdata()
get_code.tdata
get_code.tdata
+get_metadata()
tdata
objecttdata2env()
tdata
object to an environment
tdata
object to an environment
+teal_data_module()
eval_code(<teal_data_module>,<character>)
within(<teal_data_module>)
teal
applicationsshow_rcode_modal()
R
code modaljoin_keys(<tdata>)
join_keys
from tdata
validate_has_data()
validate_has_elements()
validate_has_variable()
validate_in()
validate_inputs()
validate_n_levels()
validate_no_intersection()
validate_one_row_per_id()
(teal_data
or teal_data_module
)
For constructing the data object, refer to teal_data()
and teal_data_module()
.
Named list with server and UI functions.
arg
arg
+R/modules.R
is_arg_used.Rd
join_keys
from tdata
join_keys
from tdata
+R/tdata.R
join_keys.tdata.Rd
(character(1)
) Label of the module.
A teal_module
(extended with teal_landing_module
class) to be used in teal
applications.
(character(1)
)
shiny
module id.
A list of reactive
s, each holding a teal_slices
, as returned by filter_manager_module_srv
.
This module observes changes in the filters of each FilteredData
object
and keeps track of all filters used. A mapping of filters to modules
is kept in the mapping_matrix
object (which is actually a data.frame
)
@@ -155,17 +183,19 @@
filter_manager_modal_ui(id)
filter_manager_modal_srv(id, filtered_data_list, filter)
(character(1)
)
shiny
module id.
teal_modules
teal_modules
+R/modules.R
module_labels.Rd
A list
containing the labels of the modules. If the modules are nested,
@@ -133,17 +161,19 @@
For extract_module
, a teal_module
of class class
or teal_modules
containing modules of class class
.
For extract_module
, a teal_module
of class class
or teal_modules
containing modules of class class
.
For drop_module
, the opposite, which is all teal_modules
of class other than class
.
teal_modules
teal_modules
teal_modules
teal_modules
+R/module_nested_tabs.R
module_nested_tabs.Rd
ui_nested_tabs(id, modules, datasets, depth = 0L, is_module_specific = FALSE)
# S3 method for default
@@ -163,8 +187,10 @@ Usage
(character(1)
)
module id
Depending on the class of modules
, ui_nested_tabs
returns:
teal_module
: instantiated UI of the module.
Depending on the class of modules
, ui_nested_tabs
returns:
teal_module
: instantiated UI of the module.
teal_modules
: tabsetPanel
with each tab corresponding to recursively
calling this function on it.
srv_nested_tabs
returns a reactive which returns the active module that corresponds to the selected tab.
srv_nested_tabs
returns a reactive which returns the active module that corresponds to the selected tab.
ui_nested_tabs
ui_nested_tabs
+Each teal_modules
is translated to a tabsetPanel
and each
@@ -218,25 +251,32 @@
ui_nested_tabs
srv_nested_tabs
srv_nested_tabs
+This module recursively calls all elements of modules
and returns currently active one.
teal_module
returns self as a active module.
This module recursively calls all elements of modules
and returns currently active one.
teal_module
returns self as a active module.
teal_modules
also returns module active within self which is determined by the input$active_tab
.
ui_tabs_with_filters(id, modules, datasets, filter = teal_slices())
srv_tabs_with_filters(
@@ -133,8 +156,10 @@ Usage
(character(1)
)
module id
A shiny.tag.list
containing the main menu, placeholders for filters and placeholders for the teal
modules.