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 the new DDL #957

Merged
merged 54 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
c9887c9
approach 3
gogonzo Nov 1, 2023
e7a3526
[skip actions] Roxygen Man Pages Auto Update
dependabot-preview[bot] Nov 1, 2023
e9df325
ddl_login_password wrapper
gogonzo Nov 1, 2023
c2e84eb
[skip actions] Roxygen Man Pages Auto Update
dependabot-preview[bot] Nov 1, 2023
a8f85ca
remove redundant
gogonzo Nov 1, 2023
dc3d7e3
[skip actions] Roxygen Man Pages Auto Update
dependabot-preview[bot] Nov 1, 2023
08c7214
fixes
gogonzo Nov 2, 2023
221976a
:O
gogonzo Nov 2, 2023
55a5a80
modifying class to be defacto simpler version of `teal_module`
gogonzo Nov 3, 2023
51b3f79
[skip actions] Roxygen Man Pages Auto Update
dependabot-preview[bot] Nov 3, 2023
e43c76a
need to quote to avoid eval of language objects
gogonzo Nov 3, 2023
f0c1cc6
allow no server_args
gogonzo Nov 3, 2023
df885f3
change assertion to list(ui, server)!
gogonzo Nov 3, 2023
a324e21
tighten up asserts
gogonzo Nov 3, 2023
3ac84f9
fix asserts in teal with splash
gogonzo Nov 3, 2023
e11acd7
example ddl
gogonzo Nov 3, 2023
0bb2e70
[skip actions] Roxygen Man Pages Auto Update
dependabot-preview[bot] Nov 3, 2023
9244d33
review suggestions
gogonzo Nov 6, 2023
d6b8d2f
[skip actions] Roxygen Man Pages Auto Update
dependabot-preview[bot] Nov 6, 2023
0ce0eb5
fix notifications
gogonzo Nov 6, 2023
71c1501
[skip actions] Roxygen Man Pages Auto Update
dependabot-preview[bot] Nov 6, 2023
0c52e1a
removing delayed_data and ddl
gogonzo Nov 6, 2023
9b7de0f
add test for teal::init
gogonzo Nov 6, 2023
640c27b
- introduce `data_module`
gogonzo Nov 8, 2023
7a451d3
Merge 640c27bb8816d38081f1d2a5b6cd0adf1f406d6b into 60755551478f0f666…
gogonzo Nov 8, 2023
82fb25a
[skip actions] Restyle files
github-actions[bot] Nov 8, 2023
cfb27bc
add vignette
gogonzo Nov 8, 2023
c3adb74
Merge cfb27bcd90783cae615234f26b39879afc812579 into 60755551478f0f666…
gogonzo Nov 8, 2023
147c02f
[skip actions] Restyle files
github-actions[bot] Nov 8, 2023
cd292ea
empty
gogonzo Nov 8, 2023
e80c8ce
data_module -> teal_data_module
gogonzo Nov 8, 2023
724dcdb
WIP tests
gogonzo Nov 8, 2023
a98682d
- add asserts on datanames in teal::init
gogonzo Nov 9, 2023
507fbc3
lint
gogonzo Nov 9, 2023
0f7accd
edit vignette
Nov 9, 2023
0836745
Merge 0f7accd87e42a3bd6c4e0c041f8448806ac67029 into 60755551478f0f666…
gogonzo Nov 9, 2023
e511517
[skip actions] Restyle files
github-actions[bot] Nov 9, 2023
bbaff1f
update documentation for teal_data_module
Nov 9, 2023
2a38182
WIP review
gogonzo Nov 9, 2023
971c7d5
protect teal from teal_data_module errors
gogonzo Nov 10, 2023
72abf29
@kartikayakirar
gogonzo Nov 10, 2023
457ae2c
fix typo
chlebowa Nov 10, 2023
9d63d40
add NEWS entry and reorganize in proper sections
gogonzo Nov 10, 2023
f84ad0b
@chlebowa @ruckip review
gogonzo Nov 10, 2023
7d5ccc3
review
gogonzo Nov 10, 2023
6570474
fix error handling when simple error in teal_data_module
gogonzo Nov 10, 2023
8a0675f
change error message when teal_data_module doesn't return reactive
gogonzo Nov 10, 2023
422efa6
review
gogonzo Nov 10, 2023
8e0cc87
@ruckip review
gogonzo Nov 13, 2023
2c928b2
@ruckip review
gogonzo Nov 13, 2023
54f05ba
update snapshot manager documentation
Nov 13, 2023
0b12f7b
link to the vignette
gogonzo Nov 13, 2023
f00e181
adding link to vignette and fix error message
gogonzo Nov 13, 2023
019f26c
lintr
gogonzo Nov 13, 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: 2 additions & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ LazyData: true
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.2.3
Collate:
'data-data-utils.R'
'data-transform_module.R'
'dummy_functions.R'
'get_rcode_utils.R'
'include_css_js.R'
Expand Down
98 changes: 98 additions & 0 deletions R/data-data-utils.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#' Function runs the `code`, masks the `code` and creates `teal_data` object.
#' @param data (`teal_data`) object
#' @param code (`language`) code to evaluate
#' @param input (`list`) containing inputs to be used in the `code`
#' @param input_mask (`list`) containing inputs to be masked in the `code`
#'
#' @return `teal_data` object
#'
#' @export
eval_and_mask <- function(data,
code,
input = list(),
input_mask = list()) {
# todo: do we need also within_and_mask?
checkmate::assert_list(input)
gogonzo marked this conversation as resolved.
Show resolved Hide resolved
if (inherits(input, "reactivevalues")) {
input <- shiny::reactiveValuesToList(input)
}
# evaluate code and substitute input
data <- teal.code::eval_code(data, .substitute_code(code, args = input))
if (inherits(data, "qenv.error")) {
return(data)
}

if (identical(ls(data@env), character(0))) {
warning(
"Evaluation of `ddl` code haven't created any objects.\n",
"Please make sure that the code is syntactically correct and creates necessary data."
)
}

if (!missing(input_mask)) {
# mask dynamic inputs with mask
input <- utils::modifyList(input, input_mask)

# replace last code entry with masked code
# format_expression needed to convert expression into character(1)
# question: warnings and errors are not masked, is it ok?
data@code[length(data@code)] <- format_expression(.substitute_code(code, args = input))
}

# todo: should it be here or in datanames(data)?
if (length(datanames(data)) == 0) {
datanames(data) <- ls(data@env)
}

data
}

#' Substitute symbols in the code
#'
#' Function replaces symbols in the provided code by values of the `args` argument.
#'
#' @param code (`language`) code to substitute
#' @param args (`list`) named list or arguments
#' @keywords internal
.substitute_code <- function(code, args) {
do.call(
substitute,
list(
expr = do.call(
substitute,
list(expr = code)
),
env = args
)
)
}

#' Convenience wrapper for ddl_login_password
ddl_login_password <- function(data, code, input_mask) {
srv <- function(id, data) {
moduleServer(id, function(input, output, session) {
eventReactive(input$submit, {
eval_and_mask(data, code = code, input = input, input_mask = input_mask)
})
})
}

ui <- function(id) {
ns <- NS(id)
actionButton(inputId = ns("submit"), label = "Submit")
}

teal_transform(data, ui, server)
}


# todo: to remove before merge -------------
#' @export
open_conn <- function(username, password) {
if (password != "pass") stop("Invalid credentials. 'pass' is the password") else TRUE
}
#' @export
close_conn <- function(conn) {
message("closed")
return(NULL)
}
23 changes: 23 additions & 0 deletions R/data-transform_module.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#' Transform module for `teal_data`
#'
#' Function creates object of class `teal_trnasform_module` which allows
gogonzo marked this conversation as resolved.
Show resolved Hide resolved
#' `teal` app developer to transform freely `teal_data` object passed to `data` argument in
gogonzo marked this conversation as resolved.
Show resolved Hide resolved
#' [teal::init()]. This helps in case when app developer wants to use `teal` app
#' where `data` can be influenced by app user. For example, app developer can create
#' `teal` app which allows user to connect to database and then use data from this database.
#' @param data `teal_data` object
#' @param ui (`function(id)`) function to create UI
#' @param server (`function(id, data)`) `shiny` server
#' which returns `teal_data` object wrapped in `reactive`.
#' @export
teal_transform <- function(data, ui, server) {
checkmate::assert_class(data, "teal_data")
checkmate::assert_function(ui, args = "id")
checkmate::assert_function(server, args = c("id", "data"))

structure(
list(ui = ui, server = server),
data = data,
class = "teal_transform_module"
)
}
41 changes: 8 additions & 33 deletions R/init.R
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
#' shinyApp(app$ui, app$server)
#' }
#'
init <- function(data,
init <- function(data = teal_data(),
modules,
title = NULL,
filter = teal_slices(),
Expand All @@ -115,10 +115,11 @@ init <- function(data,
id = character(0)) {
logger::log_trace("init initializing teal app with: data ({ class(data)[1] }).")

if (!inherits(data, c("TealData", "teal_data"))) {
if (!inherits(data, c("TealData", "teal_data", "teal_transform_module"))) {
data <- teal.data::to_relational_data(data = data)
}
checkmate::assert_multi_class(data, c("TealData", "teal_data"))

checkmate::assert_multi_class(data, c("TealData", "teal_data", "teal_transform_module"))
checkmate::assert_multi_class(modules, c("teal_module", "list", "teal_modules"))
checkmate::assert_string(title, null.ok = TRUE)
checkmate::assert(
Expand All @@ -142,26 +143,12 @@ init <- function(data,
if (length(landing) > 1L) stop("Only one `landing_popup_module` can be used.")
modules <- drop_module(modules, "teal_module_landing")

# resolve modules datanames
datanames <- teal.data::get_dataname(data)
join_keys <- teal.data::get_join_keys(data)
modules <- resolve_modules_datanames(modules = modules, datanames = datanames, join_keys = join_keys)

if (!inherits(filter, "teal_slices")) {
checkmate::assert_subset(names(filter), choices = datanames)
# list_to_teal_slices is lifted from teal.slice package, see zzz.R
# This is a temporary measure and will be removed two release cycles from now (now meaning 0.13.0).
filter <- list_to_teal_slices(filter)
}
# convert teal.slice::teal_slices to teal::teal_slices
filter <- as.teal_slices(as.list(filter))

# Calculate app hash to ensure snapshot compatibility. See ?snapshot. Raw data must be extracted from environments.
hashables <- mget(c("data", "modules"))
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 (inherits(data, "teal_transform_module")) {
# what?
chlebowa marked this conversation as resolved.
Show resolved Hide resolved
} else if (hashables$data$is_pulled()) {
sapply(get_dataname(hashables$data), simplify = FALSE, function(dn) {
hashables$data$get_dataset(dn)$get_raw_data()
Expand All @@ -172,20 +159,8 @@ init <- function(data,

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

# check teal_slices
for (i in seq_along(filter)) {
dataname_i <- shiny::isolate(filter[[i]]$dataname)
if (!dataname_i %in% datanames) {
stop(
sprintf(
"filter[[%s]] has a different dataname than available in a 'data':\n %s not in %s",
i,
dataname_i,
toString(datanames)
)
)
}
}
# convert teal.slice::teal_slices to teal::teal_slices
filter <- as.teal_slices(as.list(filter))

if (isTRUE(attr(filter, "module_specific"))) {
module_names <- unlist(c(module_labels(modules), "global_filters"))
Expand Down
6 changes: 5 additions & 1 deletion R/module_nested_tabs.R
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,11 @@ srv_nested_tabs.teal_module <- function(id, datasets, modules, is_module_specifi
checkmate::assert_class(datasets, "FilteredData")
checkmate::assert_class(trigger_data, "reactiveVal")

datanames <- if (is.null(module$datanames)) datasets$datanames() else module$datanames
datanames <- if (is.null(module$datanames) || identical(module$datanames, "all")) {
datasets$datanames()
} else {
unique(module$datanames) # todo: include parents!
}

# list of reactive filtered data
data <- sapply(
Expand Down
8 changes: 7 additions & 1 deletion R/module_tabs_with_filters.R
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,13 @@ srv_tabs_with_filters <- function(id,
)

if (!is_module_specific) {
active_datanames <- reactive(active_module()$datanames)
active_datanames <- reactive({
if (identical(active_module()$datanames, "all")) {
singleton$datanames()
} else {
active_module()$datanames
}
})
singleton <- unlist(datasets)[[1]]
singleton$srv_filter_panel("filter_panel", active_datanames = active_datanames)

Expand Down
48 changes: 24 additions & 24 deletions R/module_teal.R
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,21 @@ srv_teal <- function(id, modules, raw_data, filter = teal_slices()) {
}
)

reporter <- teal.reporter::Reporter$new()
if (is_arg_used(modules, "reporter") && length(extract_module(modules, "teal_module_previewer")) == 0) {
modules <- append_module(modules, reporter_previewer_module())
}

# Replace splash / welcome screen once data is loaded ----
# ignoreNULL to not trigger at the beginning when data is NULL
# just handle it once because data obtained through delayed loading should
# usually not change afterwards
# if restored from bookmarked state, `filter` is ignored
env <- environment()
datasets_reactive <- eventReactive(raw_data(), {
observeEvent(raw_data(), {
logger::log_trace("srv_teal@5 setting main ui after data was pulled")
env$progress <- shiny::Progress$new(session)
on.exit(env$progress$close())
env$progress$set(0.25, message = "Setting data")

# create a list of data following structure of the nested modules list structure.
Expand All @@ -171,6 +183,7 @@ srv_teal <- function(id, modules, raw_data, filter = teal_slices()) {
# 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)

module_datasets <- function(modules) {
if (inherits(modules, "teal_modules")) {
datasets <- lapply(modules$children, module_datasets)
Expand All @@ -180,11 +193,16 @@ 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)) teal.data::get_dataname(raw_data()) else modules$datanames
# todo: subset tdata object to datanames
datanames <- if (is.null(modules$datanames) || modules$datanames == "all") {
include_parent_datanames(raw_data()@datanames, raw_data()@join_keys) # todo: use methods instead
gogonzo marked this conversation as resolved.
Show resolved Hide resolved
} else {
modules$datanames
}
# todo: subset teal_data to datanames
datasets_module <- teal_data_to_filtered_data(raw_data())

# set initial filters
# - filtering filters for this module
slices <- Filter(x = filter, f = function(x) {
x$id %in% unique(unlist(attr(filter, "mapping")[c(modules$label, "global_filters")])) &&
x$dataname %in% datanames
Expand All @@ -201,26 +219,8 @@ srv_teal <- function(id, modules, raw_data, filter = teal_slices()) {
}
datasets <- module_datasets(modules)

logger::log_trace("srv_teal@4 Raw Data transferred to FilteredData.")
datasets
})

reporter <- teal.reporter::Reporter$new()
if (is_arg_used(modules, "reporter") && length(extract_module(modules, "teal_module_previewer")) == 0) {
modules <- append_module(modules, reporter_previewer_module())
}

# Replace splash / welcome screen once data is loaded ----
# ignoreNULL to not trigger at the beginning when data is NULL
# just handle it once because data obtained through delayed loading should
# usually not change afterwards
# if restored from bookmarked state, `filter` is ignored
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())
# main_ui_container contains splash screen first and we remove it and replace it by the real UI

env$progress$set(0.5, message = "Setting up main UI")
removeUI(sprintf("#%s:first-child", session$ns("main_ui_container")))
insertUI(
selector = paste0("#", session$ns("main_ui_container")),
Expand All @@ -230,7 +230,7 @@ srv_teal <- function(id, modules, raw_data, filter = teal_slices()) {
ui = div(ui_tabs_with_filters(
session$ns("main_ui"),
modules = modules,
datasets = datasets_reactive(),
datasets = datasets,
filter = filter
)),
# needed so that the UI inputs are available and can be immediately updated, otherwise, updating may not
Expand All @@ -242,7 +242,7 @@ srv_teal <- function(id, modules, raw_data, filter = teal_slices()) {
# registered once (calling server functions twice would trigger observers twice each time)
active_module <- srv_tabs_with_filters(
id = "main_ui",
datasets = datasets_reactive(),
datasets = datasets,
modules = modules,
reporter = reporter,
filter = filter
Expand Down
Loading
Loading