insightsengineering / teal.modules.general

General Purpose Teal Modules
https://insightsengineering.github.io/teal.modules.general/
Other
9 stars 13 forks source link

736 Allow custom card functions in modules #737

Open chlebowa opened 7 months ago

chlebowa commented 7 months ago

Solves #736

Allows app developers to pass card functions to modules as arguments.

Demonstration

Example app demonstrating use of locally defined card function:

example app ``` rm(list = ls()) library(teal) devtools::load_all("./teal.modules.general") library(teal.widgets) local_card_function <- function(comment, label) { card <- teal::report_card_template( title = "Distribution Plot", label = label, with_filter = FALSE, filter_panel_api = filter_panel_api ) card$append_text("Dummy Card", "header2") card$append_text(paste( "This report card was generated by a card function", "defined in the global environment." )) if (!comment == "") { card$append_text("Comment", "header3") card$append_text(comment) } card } data <- teal_data() |> within({ iris <- iris mtcars <- mtcars faithful <- faithful }) datanames(data) <- c("iris", "mtcars", "faithful") filter <- teal_slices( teal_slice("iris", "Petal.Length", selected = c(1, 5.1)), teal_slice("iris", "Species", selected = c("setosa", "virginica")), teal_slice("mtcars", "cyl", selected = 6L), teal_slice("faithful", "waiting", selected = c(76, 82)), module_specific = TRUE, mapping = list( # "Distribution Module" = "iris Petal.Length", "Example" = "faithful waiting", "global_filters" = "iris Species" ) ) app <- init( data = data, modules = modules( tm_g_distribution( label = "Default card function", dist_var = data_extract_spec( dataname = "iris", select = select_spec(variable_choices("iris"), "Petal.Length") ), ggplot2_args = ggplot2_args( labs = list(subtitle = "Plot generated by Distribution Module") ) ), tm_g_distribution( label = "Local card function", dist_var = data_extract_spec( dataname = "iris", select = select_spec(variable_choices("iris"), "Petal.Length") ), ggplot2_args = ggplot2_args( labs = list(subtitle = "Plot generated by Distribution Module") ), card_function = local_card_function ), example_module("Example") ), filter = filter ) runApp(app, launch.browser = TRUE) ```

Also works with card function defined in another package (not shown).

Rationale

Currently each module has a hard-coded card function so the module developer has full control over what is added to the report. Neither the app user, nor the app developer has any influence on this.

Being able to redefine the card function is a valid use case.

Scoping Consideration

Current card functions are defined within the module server functions because they rely heavily on bindings that exist in the execution environment of the server function. In order for a function defined elsewhere to be able to access those bindings, a function is introduced, hydrate_function. hydrate_function changes the enclosing environment of a function (here, a card function) to bring another frame into scope. It can also add other bindings to the funciton's enclosure.

Implementation

NOTE

This is a proof of concept, changes have only been made to tm_g_distribution.

Progress

pawelru commented 7 months ago

Being able to redefine the card function is a valid use case.

I very much agree with this. I like the request and I do like the idea for implementation - that is: a card factory function. There are some details that I am still not quite sure yet and these could potentially require changes in teal.reporter itself (hopefully not but I cannot guarantee).

github-actions[bot] commented 6 months ago

CLA Assistant Lite bot ✅ All contributors have signed the CLA

chlebowa commented 6 months ago

I have read the CLA Document and I hereby sign the CLA