-
-
Notifications
You must be signed in to change notification settings - Fork 13
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
736 Allow custom card functions in modules #737
base: main
Are you sure you want to change the base?
Changes from all commits
59da2e1
55dc29e
e2a1a40
676b2af
180be37
d13fb79
377da56
e33a666
e2f51bb
ae91503
5282405
76c118d
54b5e09
3023fb2
1df517c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,6 +35,8 @@ Imports: | |
DT (>= 0.13), | ||
forcats (>= 1.0.0), | ||
grid, | ||
logger (>= 0.3.0), | ||
rlang (>= 1.0.0), | ||
scales, | ||
shinyjs, | ||
shinyTree (>= 0.2.8), | ||
|
@@ -82,7 +84,7 @@ VignetteBuilder: | |
Config/Needs/verdepcheck: haleyjeppson/ggmosaic, tidyverse/ggplot2, | ||
rstudio/shiny, insightsengineering/teal, | ||
insightsengineering/teal.transform, mllg/checkmate, tidyverse/dplyr, | ||
rstudio/DT, tidyverse/forcats, r-lib/scales, daattali/shinyjs, | ||
rstudio/DT, tidyverse/forcats, r-lib/rlang, r-lib/scales, daattali/shinyjs, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As |
||
shinyTree/shinyTree, rstudio/shinyvalidate, dreamRs/shinyWidgets, | ||
tidyverse/stringr, insightsengineering/teal.code, | ||
insightsengineering/teal.data, insightsengineering/teal.logger, | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -39,7 +39,7 @@ | |||||
#' | ||||||
#' app <- init( | ||||||
#' data = data, | ||||||
#' modules = list( | ||||||
#' modules = modules( | ||||||
#' tm_g_distribution( | ||||||
#' dist_var = data_extract_spec( | ||||||
#' dataname = "iris", | ||||||
|
@@ -118,7 +118,8 @@ tm_g_distribution <- function(label = "Distribution Module", | |||||
plot_height = c(600, 200, 2000), | ||||||
plot_width = NULL, | ||||||
pre_output = NULL, | ||||||
post_output = NULL) { | ||||||
post_output = NULL, | ||||||
card_function) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I would expose this so that it's visible which function is used. However that would require exposing N reporting cards for N modules |
||||||
message("Initializing tm_g_distribution") | ||||||
|
||||||
# Requires Suggested packages | ||||||
|
@@ -169,6 +170,12 @@ tm_g_distribution <- function(label = "Distribution Module", | |||||
|
||||||
checkmate::assert_multi_class(pre_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) | ||||||
checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) | ||||||
|
||||||
if (missing(card_function)) { | ||||||
card_function <- tm_g_distribution_card_function | ||||||
} else { | ||||||
checkmate::assert_function(card_function) | ||||||
} | ||||||
Comment on lines
+174
to
+178
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then this can only be limited to an assert_function if we go with this approach https://github.com/insightsengineering/teal.modules.general/pull/737/files#r1671978213 |
||||||
# End of assertions | ||||||
|
||||||
# Make UI args | ||||||
|
@@ -185,7 +192,7 @@ tm_g_distribution <- function(label = "Distribution Module", | |||||
server = srv_distribution, | ||||||
server_args = c( | ||||||
data_extract_list, | ||||||
list(plot_height = plot_height, plot_width = plot_width, ggplot2_args = ggplot2_args) | ||||||
list(plot_height = plot_height, plot_width = plot_width, ggplot2_args = ggplot2_args, card_function = card_function) # nolint: line_length. | ||||||
), | ||||||
ui = ui_distribution, | ||||||
ui_args = args, | ||||||
|
@@ -350,7 +357,8 @@ srv_distribution <- function(id, | |||||
group_var, | ||||||
plot_height, | ||||||
plot_width, | ||||||
ggplot2_args) { | ||||||
ggplot2_args, | ||||||
card_function) { | ||||||
with_reporter <- !missing(reporter) && inherits(reporter, "Reporter") | ||||||
with_filter <- !missing(filter_panel_api) && inherits(filter_panel_api, "FilterPanelAPI") | ||||||
checkmate::assert_class(data, "reactive") | ||||||
|
@@ -1276,37 +1284,40 @@ srv_distribution <- function(id, | |||||
|
||||||
### REPORTER | ||||||
if (with_reporter) { | ||||||
card_fun <- function(comment, label) { | ||||||
card <- teal::report_card_template( | ||||||
title = "Distribution Plot", | ||||||
label = label, | ||||||
with_filter = with_filter, | ||||||
filter_panel_api = filter_panel_api | ||||||
) | ||||||
card$append_text("Plot", "header3") | ||||||
if (input$tabs == "Histogram") { | ||||||
card$append_plot(dist_r(), dim = pws1$dim()) | ||||||
} else if (input$tabs == "QQplot") { | ||||||
card$append_plot(qq_r(), dim = pws2$dim()) | ||||||
} | ||||||
card$append_text("Statistics table", "header3") | ||||||
|
||||||
card$append_table(common_q()[["summary_table"]]) | ||||||
tests_error <- tryCatch(expr = tests_r(), error = function(e) "error") | ||||||
if (inherits(tests_error, "data.frame")) { | ||||||
card$append_text("Tests table", "header3") | ||||||
card$append_table(tests_r()) | ||||||
} | ||||||
|
||||||
if (!comment == "") { | ||||||
card$append_text("Comment", "header3") | ||||||
card$append_text(comment) | ||||||
} | ||||||
card$append_src(teal.code::get_code(output_q())) | ||||||
card | ||||||
} | ||||||
teal.reporter::simple_reporter_srv("simple_reporter", reporter = reporter, card_fun = card_fun) | ||||||
card_function <- hydrate_function(card_function, with_filter, filter_panel_api) | ||||||
teal.reporter::simple_reporter_srv("simple_reporter", reporter = reporter, card_fun = card_function) | ||||||
} | ||||||
### | ||||||
}) | ||||||
} | ||||||
|
||||||
#' @keywords internal | ||||||
tm_g_distribution_card_function <- function(comment, label) { #nolint: object_length. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. my main concern is that this is not a true list of required arguments. We are using
(I really hope that there is no strict check for these and only these argument somewhere in the teal.reporter). Each has its pros and cons and we probably need to think more which one would be best for this task. Glad you stopped early to allow for a discussion like this. Looking at the changes - this is how it was written in the past so I definitely not blaming you for this. This PR is a great opportunity to make it right. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There is one, actually. I considered just adding I tried to limit the proposed changes to the module packages because I assumed modifying Note that passing the caller environment is not sufficient, at least in this module (I assume in others as well). I will be happy to discuss a satisfactory solution. Note also that with the proposed solution the This in turn opens another possibility: have a function called There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahh that's bad :( Then I would say that please feel free to modify this in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have a look at this alternative: #742
EDIT: I had got a little bit of tunnel vision, with an added There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Haven't yet checked the alternative, but in this case I would opt for relaxing teal.reporter checks to allow passing ellipsis which would simply the process and would not require the usage of hydrating in this case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. passing |
||||||
card <- teal::report_card_template( | ||||||
title = "Distribution Plot", | ||||||
label = label, | ||||||
with_filter = with_filter, | ||||||
filter_panel_api = filter_panel_api | ||||||
) | ||||||
card$append_text("Plot", "header3") | ||||||
if (input$tabs == "Histogram") { | ||||||
card$append_plot(dist_r(), dim = pws1$dim()) | ||||||
} else if (input$tabs == "QQplot") { | ||||||
card$append_plot(qq_r(), dim = pws2$dim()) | ||||||
} | ||||||
card$append_text("Statistics table", "header3") | ||||||
|
||||||
card$append_table(common_q()[["summary_table"]]) | ||||||
tests_error <- tryCatch(expr = tests_r(), error = function(e) "error") | ||||||
if (inherits(tests_error, "data.frame")) { | ||||||
card$append_text("Tests table", "header3") | ||||||
card$append_table(tests_r()) | ||||||
} | ||||||
|
||||||
if (!comment == "") { | ||||||
card$append_text("Comment", "header3") | ||||||
card$append_text(comment) | ||||||
} | ||||||
card$append_src(teal.code::get_code(output_q())) | ||||||
card | ||||||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -35,6 +35,10 @@ | |||||
#' - When the length of `size` is three: the plot points size are dynamically adjusted based on | ||||||
#' vector of `value`, `min`, and `max`. | ||||||
#' | ||||||
#' @param card_function (`function`) optional, custom function to create a report card. | ||||||
#' See [this vignette](https://insightsengineering.github.io/teal/latest-tag/articles/adding-support-for-reporting.html) | ||||||
#' for details. | ||||||
#' | ||||||
#' @return Object of class `teal_module` to be used in `teal` applications. | ||||||
#' | ||||||
#' @name shared_params | ||||||
|
@@ -278,3 +282,56 @@ assert_single_selection <- function(x, | |||||
} | ||||||
invisible(TRUE) | ||||||
} | ||||||
|
||||||
#' Hydrate a function's enclosing environment | ||||||
#' | ||||||
#' Add bindings of an environment to a function's parent environment. | ||||||
#' | ||||||
#' This allows any funciton to use bindings present in any environment | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
#' as if the funciton were defined there. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
#' All bindings of the additional environment are added to the function's enclosure, | ||||||
#' except bindings existing in the enclosure are _not_ overwritten. | ||||||
#' | ||||||
#' One may also want to add variables that are not bound in the caller | ||||||
#' but are accessible from the caller, e.g. they exist in the caller's parent frame. | ||||||
#' This may happen in `shiny` modules because `moduleServer` is called | ||||||
#' by the module server function so the server funciton's arguments are in scope | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
#' of `moduleServer` but are not bindings in its environment. | ||||||
#' Such variables should be passed to `...`. | ||||||
#' As in the case of calling environment bindings, no overwriting will occur. | ||||||
#' | ||||||
#' Variables passed to `...` ass `name:value` pairs will be assigned with `value` under `name`. | ||||||
#' Variables passed directly will be assigned under the same name. | ||||||
#' | ||||||
#' Note that the `added_env` argument must be passed named, otherwise it will be captured by `...`. | ||||||
#' | ||||||
#' @param fun (`function`) | ||||||
#' @param ... additional variables to add to the new enclosure, see `Details` | ||||||
#' @param added_env (`environment`) environment to hydrate `fun`'s enclosure with | ||||||
#' | ||||||
#' @return A `function` which will work just like `fun` but in a different scope. | ||||||
#' | ||||||
#' @keywords internal | ||||||
#' | ||||||
hydrate_function <- function(fun, ..., added_env = parent.frame()) { | ||||||
enclos_env <- environment(fun) | ||||||
env_new <- rlang::env_clone(enclos_env) | ||||||
|
||||||
added_vars <- setdiff(names(added_env), names(enclos_env)) | ||||||
lapply(added_vars, function(nm) { | ||||||
assign(nm, get0(nm, envir = added_env, inherits = FALSE), envir = env_new) | ||||||
}) | ||||||
|
||||||
args <- list(...) | ||||||
arg_names <- vapply(as.list(substitute(list(...)))[-1L], as.character, character(1L)) | ||||||
names(arg_names)[names(arg_names) == ""] <- arg_names[names(arg_names) == ""] | ||||||
names(args) <- arg_names | ||||||
|
||||||
extras <- setdiff(arg_names, names(enclos_env)) | ||||||
lapply(extras, function(nm) { | ||||||
assign(nm, args[[nm]], envir = env_new) | ||||||
}) | ||||||
|
||||||
environment(fun) <- env_new | ||||||
fun | ||||||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.