Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce teal_data class #925

Merged
merged 29 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
5c17ccf
following new teal_data class
gogonzo Oct 4, 2023
4051ded
reverting breaking changes and supporting teal_data
gogonzo Oct 5, 2023
c3eab5a
filtered data constructor in teal internals
gogonzo Oct 6, 2023
8164671
new_teal_data env to data
gogonzo Oct 6, 2023
354a79d
[skip actions] Roxygen Man Pages Auto Update
dependabot-preview[bot] Oct 6, 2023
0185fb7
Merge branch 'main' into teal_data@main
gogonzo Oct 17, 2023
34bad8c
fix tests
gogonzo Oct 19, 2023
e8c9d86
Merge remote-tracking branch 'origin/main' into teal_data@main
gogonzo Oct 19, 2023
8b3a082
Merge remote-tracking branch 'origin/main' into teal_data@main
gogonzo Oct 23, 2023
fc83084
fix 1 after merge
gogonzo Oct 23, 2023
2f11384
fix 2 after merge
gogonzo Oct 23, 2023
ec575f9
teal_data instead of new_teal_data
gogonzo Oct 23, 2023
a256bbd
Merge remote-tracking branch 'origin/main' into teal_data@main
gogonzo Oct 25, 2023
c1cf841
remove generic get_join_keys (duplicated with teal.data)
gogonzo Oct 25, 2023
29d171c
add docs
gogonzo Oct 25, 2023
62cb664
Merge 29d171cd631a655cd53df650810987ec9321a360 into 8b2653dc1b7f67120…
gogonzo Oct 25, 2023
a035f84
[skip actions] Restyle files
github-actions[bot] Oct 25, 2023
c7ce543
fix hashing of ddl
gogonzo Oct 25, 2023
96f8b1f
Merge branch 'main' into teal_data@main
gogonzo Oct 26, 2023
1e061f1
fix pkgdown
gogonzo Oct 26, 2023
56424e9
fix pkgdown
gogonzo Oct 27, 2023
6ec2c22
resolve_modules_datanames to utils.R
gogonzo Oct 27, 2023
77d9702
Merge 6ec2c22d9c6bc559f5aa4f8bb5e1019afa41c00c into a3cbbe13498c8a9ab…
gogonzo Oct 27, 2023
45d2985
[skip actions] Restyle files
github-actions[bot] Oct 27, 2023
1a6e11d
rerun
gogonzo Oct 27, 2023
e5ef022
fix spelling
gogonzo Oct 27, 2023
c593734
skipping lint of a long function
gogonzo Oct 27, 2023
2b13b23
addressing old comments
gogonzo Oct 27, 2023
2d688f2
fix typo
chlebowa Oct 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

S3method(c,teal_slices)
S3method(get_code,tdata)
S3method(get_join_keys,default)
S3method(get_join_keys,tdata)
S3method(get_metadata,default)
S3method(get_metadata,tdata)
Expand All @@ -21,7 +20,6 @@ export(TealReportCard)
export(as.teal_slices)
export(example_module)
export(get_code_tdata)
export(get_join_keys)
export(get_metadata)
export(init)
export(landing_popup_module)
Expand Down
11 changes: 3 additions & 8 deletions R/dummy_functions.R
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,9 @@ example_cdisc_data <- function() { # nolint
ADSL$logical_test <- sample(c(TRUE, FALSE, NA), size = nrow(ADSL), replace = TRUE) # nolint
ADSL$SEX[c(2, 5)] <- NA # nolint

cdisc_data_obj <- teal.data::cdisc_data(
cdisc_dataset(dataname = "ADSL", x = ADSL),
cdisc_dataset(dataname = "ADTTE", x = ADTTE)
)

res <- teal.data::cdisc_data(
teal.data::cdisc_dataset(dataname = "ADSL", x = ADSL),
teal.data::cdisc_dataset(dataname = "ADTTE", x = ADTTE),
ADSL = ADSL,
ADTTE = ADTTE,
code = '
ADSL <- data.frame(
STUDYID = "study",
Expand Down Expand Up @@ -62,7 +57,7 @@ example_cdisc_data <- function() { # nolint
#' @keywords internal
example_datasets <- function() { # nolint
dummy_cdisc_data <- example_cdisc_data()
datasets <- teal.slice::init_filtered_data(dummy_cdisc_data)
datasets <- teal_data_to_filtered_data(dummy_cdisc_data)
list(
"d2" = list(
"d3" = list(
Expand Down
44 changes: 32 additions & 12 deletions R/init.R
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
#' an end-user, don't use this function, but instead this module.
#'
#' @param data (`TealData` or `TealDataset` or `TealDatasetConnector` or `list` or `data.frame`
#' or `MultiAssayExperiment`)\cr
#' or `MultiAssayExperiment`, `teal_data`)\cr
#' `R6` object as returned by [teal.data::cdisc_data()], [teal.data::teal_data()],
#' [teal.data::cdisc_dataset()], [teal.data::dataset()], [teal.data::dataset_connector()] or
#' [teal.data::cdisc_dataset_connector()] or a single `data.frame` or a `MultiAssayExperiment`
Expand Down Expand Up @@ -114,9 +114,11 @@ init <- function(data,
footer = tags$p(),
id = character(0)) {
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")
if (!inherits(data, c("TealData", "teal_data"))) {
data <- teal.data::to_relational_data(data = data)
}
pawelru marked this conversation as resolved.
Show resolved Hide resolved
checkmate::assert_multi_class(data, c("TealData", "teal_data"))
chlebowa marked this conversation as resolved.
Show resolved Hide resolved
checkmate::assert_multi_class(modules, c("teal_module", "list", "teal_modules"))
checkmate::assert_string(title, null.ok = TRUE)
checkmate::assert(
Expand All @@ -142,7 +144,7 @@ init <- function(data,

# resolve modules datanames
datanames <- teal.data::get_dataname(data)
join_keys <- data$get_join_keys()
join_keys <- teal.data::get_join_keys(data)
gogonzo marked this conversation as resolved.
Show resolved Hide resolved
resolve_modules_datanames <- function(modules) {
gogonzo marked this conversation as resolved.
Show resolved Hide resolved
if (inherits(modules, "teal_modules")) {
modules$children <- sapply(modules$children, resolve_modules_datanames, simplify = FALSE)
Expand All @@ -151,6 +153,17 @@ init <- function(data,
modules$datanames <- if (identical(modules$datanames, "all")) {
datanames
} else if (is.character(modules$datanames)) {
extra_datanames <- setdiff(modules$datanames, datanames)
if (length(extra_datanames)) {
stop(
sprintf(
"Module %s has datanames that are not available in a 'data':\n %s not in %s",
modules$label,
toString(extra_datanames),
toString(datanames)
)
)
}
datanames_adjusted <- intersect(modules$datanames, datanames)
include_parent_datanames(dataname = datanames_adjusted, join_keys = join_keys)
chlebowa marked this conversation as resolved.
Show resolved Hide resolved
}
Expand All @@ -170,13 +183,18 @@ init <- function(data,

# Calculate app hash to ensure snapshot compatibility. See ?snapshot. Raw data must be extracted from environments.
hashables <- mget(c("data", "modules"))
hashables$data <- sapply(hashables$data$get_datanames(), function(dn) {
if (hashables$data$is_pulled()) {
hashables$data <- if (inherits(hashables$data, "teal_data")) {
as.list(hashables$data@env)
} else if (inherits(hashables$data, "ddl")) {
attr(hashables$data, "code")
} else if (hashables$data$is_pulled()) {
sapply(get_dataname(hashables$data), simplify = FALSE, function(dn) {
hashables$data$get_dataset(dn)$get_raw_data()
} else {
hashables$data$get_code(dn)
}
}, simplify = FALSE)
})
} else {
hashables$data$get_code()
}

attr(filter, "app_id") <- rlang::hash(hashables)

# check teal_slices
Expand Down Expand Up @@ -231,8 +249,10 @@ init <- function(data,
landing_module <- landing[[1L]]
do.call(landing_module$server, c(list(id = "landing_module_shiny_id"), landing_module$server_args))
}
# copy object so that load won't be shared between the session
data <- data$copy(deep = TRUE)
if (inherits(data, "TealDataAbstract")) {
# copy TealData so that load won't be shared between the session
data <- data$copy(deep = TRUE)
}
filter <- deep_copy_filter(filter)
srv_teal_with_splash(id = id, data = data, modules = modules, filter = filter)
}
Expand Down
14 changes: 8 additions & 6 deletions R/module_nested_tabs.R
Original file line number Diff line number Diff line change
Expand Up @@ -314,12 +314,14 @@ srv_nested_tabs.teal_module <- function(id, datasets, modules, is_module_specifi
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)
)
{
c(
get_rcode_str_install(),
get_rcode_libraries(),
get_datasets_code(datanames, datasets, hashes),
teal.slice::get_filter_expr(datasets, datanames)
)
}
gogonzo marked this conversation as resolved.
Show resolved Hide resolved
),
datasets$get_join_keys(),
metadata
Expand Down
32 changes: 6 additions & 26 deletions R/module_teal.R
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
#' [init()] about how to call the corresponding server function.
#'
#' @param raw_data (`reactive`)\cr
#' returns the `TealData`, only evaluated once, `NULL` value is ignored
#' returns the `teal_data`, only evaluated once, `NULL` value is ignored
#'
#' @return
#' `ui_teal` returns `HTML` for Shiny module UI.
Expand Down Expand Up @@ -160,18 +160,14 @@ srv_teal <- function(id, modules, raw_data, filter = teal_slices()) {
}
)

# loading the data -----
env <- environment()
datasets_reactive <- reactive({
if (is.null(raw_data())) {
return(NULL)
}
datasets_reactive <- eventReactive(raw_data(), ignoreNULL = TRUE, {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason for explicitly setting ignoreNULL to default?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I should remove it because eventReactive has ignoreNULL = TRUE by default, which is exactly what we need here.

env$progress <- shiny::Progress$new(session)
env$progress$set(0.25, message = "Setting data")

# create a list of data following structure of the nested modules list structure.
# Because it's easier to unpack modules and datasets when they follow the same nested structure.
datasets_singleton <- teal.slice::init_filtered_data(raw_data())
datasets_singleton <- teal_data_to_filtered_data(raw_data())
# Singleton starts with only global filters active.
filter_global <- Filter(function(x) x$id %in% attr(filter, "mapping")$global_filters, filter)
datasets_singleton$set_filter_state(filter_global)
Expand All @@ -184,25 +180,9 @@ srv_teal <- function(id, modules, raw_data, filter = teal_slices()) {
} else if (isTRUE(attr(filter, "module_specific"))) {
# we should create FilteredData even if modules$datanames is null
# null controls a display of filter panel but data should be still passed
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()
)
datanames <- if (is.null(modules$datanames)) teal.data::get_dataname(raw_data()) else modules$datanames
# todo: subset tdata object to datanames
datasets_module <- teal_data_to_filtered_data(raw_data())

# set initial filters
slices <- Filter(x = filter, f = function(x) {
Expand Down
55 changes: 39 additions & 16 deletions R/module_teal_with_splash.R
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,16 @@ ui_teal_with_splash <- function(id,
title,
header = tags$p("Add Title Here"),
footer = tags$p("Add Footer Here")) {
checkmate::assert_class(data, "TealDataAbstract")
is_pulled_data <- teal.data::is_pulled(data)
checkmate::assert_multi_class(data, c("TealDataAbstract", "teal_data"))
ns <- NS(id)

# Startup splash screen for delayed loading
# We use delayed loading in all cases, even when the data does not need to be fetched.
# This has the benefit that when filtering the data takes a lot of time initially, the
# Shiny app does not time out.
splash_ui <- if (is_pulled_data) {
# blank ui if data is already pulled
splash_ui <- if (inherits(data, "teal_data")) {
div()
} else if (teal.data::is_pulled(data)) {
div()
} else {
message("App was initialized with delayed data loading.")
Expand All @@ -52,36 +52,59 @@ ui_teal_with_splash <- function(id,
#' will be displayed in the teal application. See [modules()] and [module()] for
#' more details.
#' @inheritParams shiny::moduleServer
#' @return `reactive`, return value of [srv_teal()]
#' @return `reactive` containing `teal_data` object when data is loaded.
#' If data is not loaded yet, `reactive` returns `NULL`.
#' @export
srv_teal_with_splash <- function(id, data, modules, filter = teal_slices()) {
checkmate::assert_class(data, "TealDataAbstract")
checkmate::assert_multi_class(data, c("TealDataAbstract", "teal_data"))
moduleServer(id, function(input, output, session) {
logger::log_trace(
"srv_teal_with_splash initializing module with data { paste(data$get_datanames(), collapse = ' ')}."
)
logger::log_trace("srv_teal_with_splash initializing module with data { toString(get_dataname(data))}.")

if (getOption("teal.show_js_log", default = FALSE)) {
shinyjs::showLog()
}

is_pulled_data <- teal.data::is_pulled(data)
# raw_data contains TealDataAbstract, i.e. R6 object and container for data
# reactive to get data through delayed loading
# we must leave it inside the server because of callModule which needs to pick up the right session
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this still true?

if (is_pulled_data) {
raw_data <- reactiveVal(data) # will trigger by setting it
raw_data <- if (inherits(data, "teal_data")) {
reactiveVal(data)
} else if (teal.data::is_pulled(data)) {
gogonzo marked this conversation as resolved.
Show resolved Hide resolved
new_data <- do.call(
teal.data::teal_data,
c(
lapply(data$get_datasets(), function(x) x$get_raw_data()),
code = data$get_code(),
join_keys = data$get_join_keys()
)
)
reactiveVal(new_data) # will trigger by setting it
} else {
raw_data <- data$get_server()(id = "startapp_module")
raw_data_old <- data$get_server()(id = "startapp_module")
raw_data <- reactive({
data <- raw_data_old()
if (!is.null(data)) {
# raw_data is a reactive which returns data only when submit button clicked
# otherwise it returns NULL
do.call(
teal.data::teal_data,
c(
lapply(data$get_datasets(), function(x) x$get_raw_data()),
code = data$get_code(),
join_keys = data$get_join_keys()
)
)
chlebowa marked this conversation as resolved.
Show resolved Hide resolved
}
})

if (!is.reactive(raw_data)) {
stop("The delayed loading module has to return a reactive object.")
}
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 = ' ') }."
)
logger::log_trace("srv_teal_with_splash initialized module with data { toString(get_dataname(data))}.")
return(res)
})
}
19 changes: 2 additions & 17 deletions R/tdata.R
Original file line number Diff line number Diff line change
Expand Up @@ -123,29 +123,14 @@ get_code_tdata <- function(data) {
get_code(data)
}


#' Function to get join keys from a `tdata` object
#' @param data `tdata` - object to extract the join keys
#' @return Either `JoinKeys` object or `NULL` if no join keys
#' @export
get_join_keys <- function(data) {
UseMethod("get_join_keys", data)
}


#' Extract `JoinKeys` from `tdata`
#' @rdname get_join_keys
#' @param data (`tdata`) object
#' @export
get_join_keys.tdata <- function(data) {
attr(data, "join_keys")
}


#' @rdname get_join_keys
#' @export
get_join_keys.default <- function(data) {
stop("get_join_keys function not implemented for this object")
}

#' Function to get metadata from a `tdata` object
#' @param data `tdata` - object to extract the data from
#' @param dataname `character(1)` the dataset name whose metadata is requested
Expand Down
23 changes: 23 additions & 0 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,29 @@ include_parent_datanames <- function(dataname, join_keys) {
return(unique(c(parents, dataname)))
}



#' Create a `FilteredData`
#'
#' Create a `FilteredData` object from a `teal_data` object
#' @param x (`teal_data`) object
#' @return (`FilteredData`) object
#' @keywords internal
teal_data_to_filtered_data <- function(x) { # nolint
gogonzo marked this conversation as resolved.
Show resolved Hide resolved
checkmate::assert_class(x, "teal_data")
datanames <- x@datanames

teal.slice::init_filtered_data(
x = as.list(x@env)[datanames],
join_keys = x@join_keys,
code = teal.data:::CodeClass$new(
pawelru marked this conversation as resolved.
Show resolved Hide resolved
code = paste(teal.code::get_code(x), collapse = "\n"),
dataname = teal.data::get_dataname(x)
),
check = FALSE
)
}
vedhav marked this conversation as resolved.
Show resolved Hide resolved

#' Template Function for `TealReportCard` Creation and Customization
#'
#' This function generates a report card with a title,
Expand Down
1 change: 0 additions & 1 deletion _pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ reference:
contents:
- tdata
- get_code_tdata
- get_join_keys
- get_metadata
- tdata2env
- show_rcode_modal
Expand Down
Loading
Loading