Skip to content

Commit aa610ad

Browse files
averissimovedhavgogonzom7prdonyunardi
authored
📑 Reporter Refactor: Adds support for new teal_report and teal_card class (#1541)
# Pull Request - Fixes #1526 - Fixes insightsengineering/coredev-tasks#648 Built on top of: - #1499 _(will be closed once this PR is stable)_ ### Companion PRs: - #1541 - insightsengineering/teal.code#255 - insightsengineering/teal.data#370 - insightsengineering/teal.reporter#331 - insightsengineering/teal.modules.general#884 ### Changes description - [x] Adds a "Add to reporter" when a `teal_report` object is returned from the module - [x] Changes `modify_reactive_output()` to `after()` ### Sample app ```r # Make sure the required branches are checked-out # - teal_reporter (all 4: teal, teal.reporter, teal.code, teal.data) # - redesign@main (teal, teal.reporter, teal.code) pkgload::load_all("../teal.code") pkgload::load_all("../teal.data") pkgload::load_all("../teal.reporter") pkgload::load_all("../teal") example_extended <- function(label = "example teal module", datanames = "all", transformators = list(), decorators = list()) { checkmate::assert_string(label) checkmate::assert_list(decorators, "teal_transform_module") mod <- example_module(label, datanames, transformators, decorators) module( label, server = function(id, data, decorators) { moduleServer(id, function(input, output, session) { result <- mod$server("example", data, decorators) reactive({ data <- result() report(data) <- c(doc("## Code"), report(data), "## Table", data$object) data }) }) }, ui = function(id, decorators) mod$ui(shiny::NS(id, "example"), decorators), ui_args = mod$ui_args, server_args = mod$server_args, datanames = mod$datanames, transformators = mod$transformators ) } example_old_reporter <- function(label = "example teal module", datanames = "all", transformators = list(), decorators = list()) { checkmate::assert_string(label) checkmate::assert_list(decorators, "teal_transform_module") ans <- module( label, server = function(id, data, decorators, reporter, filter_panel_api) { moduleServer(id, function(input, output, session) { result <- example_module()$server("example", data, decorators) teal.widgets::verbatim_popup_srv( id = "rcode", verbatim_content = reactive(teal.code::get_code(req(result()))), title = "Example Code" ) if (inherits(reporter, "Reporter")) { card_fun <- function(comment, label) { card <- teal::report_card_template( title = "Example plot", label = label, with_filter = FALSE, filter_panel_api = filter_panel_api ) card$append_rcode(get_code(result())) card$append_text("Table", "header3") card$append_table(result()[["object"]]) card } teal.reporter::simple_reporter_srv("simple_reporter", reporter = reporter, card_fun = card_fun) } }) }, ui = function(id, decorators) { ns <- NS(id) teal.widgets::standard_layout( output = verbatimTextOutput(ns("example-text")), encoding = tags$div( teal.reporter::simple_reporter_ui(ns("simple_reporter")), selectInput(ns("example-dataname"), "Choose a dataset", choices = NULL), ui_transform_teal_data(ns("example-decorate"), transformators = decorators), teal.widgets::verbatim_popup_ui(ns("rcode"), "Show R code") ) ) }, ui_args = list(decorators = decorators), server_args = list(decorators = decorators), datanames = datanames, transformators = transformators ) attr(ans, "teal_bookmarkable") <- TRUE ans } teal::init( data = within(teal_data(), {iris <- iris}), modules = modules( example_extended(label = "🆕 Module (extended)"), example_old_reporter(label = "⏲️ Old reporter"), example_module(label = "🆕 Module (from {teal})"), example_module(label = "❌️ No reporter") |> disable_report() ) ) |> shiny::runApp() ``` --------- Signed-off-by: Marcin <[email protected]> Signed-off-by: André Veríssimo <[email protected]> Signed-off-by: Dawid Kałędkowski <[email protected]> Co-authored-by: vedhav <[email protected]> Co-authored-by: Vedha Viyash <[email protected]> Co-authored-by: Dawid Kaledkowski <[email protected]> Co-authored-by: Marcin <[email protected]> Co-authored-by: Dony Unardi <[email protected]> Co-authored-by: insights-engineering-bot <[email protected]> Co-authored-by: m7pr <[email protected]> Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 92235c4 commit aa610ad

31 files changed

+444
-446
lines changed

DESCRIPTION

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ Imports:
5555
stats,
5656
teal.code (>= 0.7.0),
5757
teal.logger (>= 0.4.0),
58-
teal.reporter (>= 0.5.0),
58+
teal.reporter (>= 0.5.0.9001),
5959
teal.widgets (>= 0.5.0),
6060
tools,
6161
utils
@@ -79,6 +79,8 @@ VignetteBuilder:
7979
rmarkdown
8080
RdMacros:
8181
lifecycle
82+
Remotes:
83+
insightsengineering/teal.reporter@main
8284
Config/Needs/verdepcheck: rstudio/shiny, insightsengineering/teal.data,
8385
insightsengineering/teal.slice, mllg/checkmate, jeroen/jsonlite,
8486
r-lib/lifecycle, daroczig/logger, r-lib/mirai, r-lib/cli,
@@ -95,9 +97,10 @@ Encoding: UTF-8
9597
Language: en-US
9698
LazyData: true
9799
Roxygen: list(markdown = TRUE, packages = c("roxy.shinylive"))
98-
RoxygenNote: 7.3.2
100+
RoxygenNote: 7.3.3
99101
Collate:
100102
'TealAppDriver.R'
103+
'after.R'
101104
'checkmate.R'
102105
'dummy_functions.R'
103106
'include_css_js.R'

NAMESPACE

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ S3method(print,teal_modules)
1515
S3method(within,teal_data_module)
1616
export(TealReportCard)
1717
export(add_landing_modal)
18+
export(after)
1819
export(as.teal_slices)
1920
export(as_tdata)
2021
export(build_app_title)
22+
export(disable_report)
2123
export(example_module)
2224
export(get_code_tdata)
2325
export(get_metadata)
@@ -56,6 +58,7 @@ export(validate_one_row_per_id)
5658
import(shiny)
5759
import(teal.data)
5860
import(teal.slice)
61+
importFrom(methods,as)
5962
importFrom(methods,new)
6063
importFrom(methods,setMethod)
6164
importFrom(shiny,reactiveVal)

NEWS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# teal 1.0.0.9002
22

3+
### New features
4+
5+
* `init` and `srv_teal` have new `reporter` parameter, that allows to pre-define `teal.reporter::Reporter` object to be
6+
used for storing the content of the report. You can also globally disable reporting by setting `reporter = NULL`
7+
(and `disable = TRUE` in `ui_teal` for cases when `ui_teal` is used as shiny module).
8+
39
# teal 1.0.0
410

511
### Breaking changes

R/after.R

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#' Executes modifications to the result of a module
2+
#'
3+
#' Primarily used to modify the output object of module to change the containing
4+
#' report.
5+
#' @param x (`teal_module`)
6+
#' @param ui (`function(id, elem, ...)`) function to receive output (`shiny.tag`) from `x$ui`
7+
#' @param server (`function(input, output, session, data, ...)`) function to receive output data from `x$server`
8+
#' @param ... additional argument passed to `ui` and `server` by matching their formals names.
9+
#' @return A `teal_report` object with the result of the server function.
10+
#' @export
11+
after <- function(x,
12+
ui = function(id, elem) elem,
13+
server = function(input, output, session, data) data,
14+
...) {
15+
# todo: make a method for teal_app and remove teal_extend_server?
16+
checkmate::assert_multi_class(x, "teal_module")
17+
if (!is.function(ui) || !all(names(formals(ui)) %in% c("id", "elem"))) {
18+
stop("ui should be a function of id and elem")
19+
}
20+
if (!is.function(server) || !all(names(formals(server)) %in% c("input", "output", "session", "data"))) {
21+
stop("server should be a function of `input` and `output`, `session`, `data`")
22+
}
23+
24+
additional_args <- list(...)
25+
new_x <- x # because overwriting x$ui/server will cause infinite recursion
26+
new_x$ui <- .after_ui(x$ui, ui, additional_args)
27+
new_x$server <- .after_server(x$server, server, additional_args)
28+
new_x
29+
}
30+
31+
.after_ui <- function(x, y, additional_args) {
32+
# add `_`-prefix to make sure objects are not masked in the wrapper functions
33+
`_x` <- x # nolint: object_name.
34+
`_y` <- y # nolint: object_name.
35+
new_x <- function(id, ...) {
36+
original_args <- as.list(environment())
37+
if ("..." %in% names(formals(`_x`))) {
38+
original_args <- c(original_args, list(...))
39+
}
40+
ns <- NS(id)
41+
original_args$id <- ns("wrapped")
42+
original_out <- do.call(`_x`, original_args, quote = TRUE)
43+
44+
wrapper_args <- c(
45+
additional_args,
46+
list(id = ns("wrapper"), elem = original_out)
47+
)
48+
do.call(`_y`, args = wrapper_args[names(formals(`_y`))])
49+
}
50+
formals(new_x) <- formals(x)
51+
new_x
52+
}
53+
54+
.after_server <- function(x, y, additional_args) {
55+
# add `_`-prefix to make sure objects are not masked in the wrapper functions
56+
`_x` <- x # nolint: object_name.
57+
`_y` <- y # nolint: object_name.
58+
new_x <- function(id, ...) {
59+
original_args <- as.list(environment())
60+
original_args$id <- "wrapped"
61+
if ("..." %in% names(formals(`_x`))) {
62+
original_args <- c(original_args, list(...))
63+
}
64+
moduleServer(id, function(input, output, session) {
65+
original_out <- if (all(c("input", "output", "session") %in% names(formals(`_x`)))) {
66+
original_args$module <- `_x`
67+
do.call(shiny::callModule, args = original_args)
68+
} else {
69+
do.call(`_x`, original_args)
70+
}
71+
original_out_r <- reactive(
72+
if (is.reactive(original_out)) {
73+
original_out()
74+
} else {
75+
original_out
76+
}
77+
)
78+
wrapper_args <- utils::modifyList(
79+
additional_args,
80+
list(id = "wrapper", input = input, output = output, session = session)
81+
)
82+
reactive({
83+
req(original_out_r())
84+
wrapper_args$data <- original_out()
85+
do.call(`_y`, wrapper_args[names(formals(`_y`))], quote = TRUE)
86+
})
87+
})
88+
}
89+
formals(new_x) <- formals(x)
90+
new_x
91+
}

R/init.R

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#' a string specifying the `shiny` module id in cases it is used as a `shiny` module
3535
#' rather than a standalone `shiny` app.
3636
#' This parameter is no longer supported. Use [ui_teal()] and [srv_teal()] instead.
37+
#' @param reporter (`Reporter`) object used to store report contents. Set to `NULL` to globally disable reporting.
3738
#'
3839
#' @return Named list containing server and UI functions.
3940
#'
@@ -99,7 +100,8 @@ init <- function(data,
99100
title = lifecycle::deprecated(),
100101
header = lifecycle::deprecated(),
101102
footer = lifecycle::deprecated(),
102-
id = lifecycle::deprecated()) {
103+
id = lifecycle::deprecated(),
104+
reporter = teal.reporter::Reporter$new()) {
103105
logger::log_debug("init initializing teal app with: data ('{ class(data) }').")
104106

105107
# argument checking (independent)
@@ -183,7 +185,6 @@ init <- function(data,
183185
landing <- extract_module(modules, "teal_module_landing")
184186
modules <- drop_module(modules, "teal_module_landing")
185187

186-
187188
if (lifecycle::is_present(id)) {
188189
lifecycle::deprecate_soft(
189190
when = "0.16.0",
@@ -235,7 +236,13 @@ init <- function(data,
235236
)
236237
},
237238
server = function(input, output, session) {
238-
srv_teal(id = "teal", data = data, modules = modules, filter = deep_copy_filter(filter))
239+
srv_teal(
240+
id = "teal",
241+
data = data,
242+
modules = modules,
243+
filter = deep_copy_filter(filter),
244+
reporter = if (!is.null(reporter)) reporter$clone(deep = TRUE)
245+
)
239246
srv_session_info("teal-footer-session_info")
240247
}
241248
),

R/module_init_data.R

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,15 +90,31 @@ srv_init_data <- function(id, data) {
9090
#' @keywords internal
9191
.add_signature_to_data <- function(data) {
9292
hashes <- .get_hashes_code(data)
93+
data_teal_report <- as(data, "teal_report")
94+
if (!inherits(data, "teal_report")) {
95+
teal.reporter::teal_card(data_teal_report) <- c(
96+
teal.reporter::teal_card(),
97+
"## Code preparation",
98+
teal.reporter::teal_card(data_teal_report)
99+
)
100+
}
93101
tdata <- do.call(
94-
teal.data::teal_data,
102+
teal.reporter::teal_report,
95103
c(
96-
list(code = trimws(c(teal.code::get_code(data), hashes), which = "right")),
97-
list(join_keys = teal.data::join_keys(data)),
98-
as.list(data, all.names = TRUE)
104+
list(
105+
code = trimws(c(teal.code::get_code(data_teal_report), hashes), which = "right"),
106+
join_keys = teal.data::join_keys(data_teal_report),
107+
teal_card = teal.reporter::teal_card(data_teal_report)
108+
),
109+
sapply(
110+
names(data_teal_report),
111+
base::get,
112+
envir = data_teal_report,
113+
simplify = FALSE
114+
)
99115
)
100116
)
101-
tdata@verified <- data@verified
117+
tdata@verified <- data_teal_report@verified
102118
tdata
103119
}
104120

R/module_nested_tabs.R

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ srv_teal_module <- function(id,
225225
tab_pane <- div(
226226
id = container_id,
227227
class = c("tab-pane", "teal_module", if (identical(module_id, active_module_id)) "active"),
228+
ui_add_reporter(ns("add_reporter_wrapper")),
228229
tagList(
229230
.modules_breadcrumb(modules),
230231
if (!is.null(modules$datanames)) {
@@ -335,7 +336,7 @@ srv_teal_module <- function(id,
335336
checkmate::assert_multi_class(modules, c("teal_modules", "teal_module"))
336337
assert_reactive(datasets, null.ok = TRUE)
337338
checkmate::assert_class(slices_global, ".slicesGlobal")
338-
checkmate::assert_class(reporter, "Reporter")
339+
checkmate::assert_class(reporter, "Reporter", null.ok = TRUE)
339340
assert_reactive(data_load_status)
340341
UseMethod(".srv_teal_module", modules)
341342
}
@@ -489,21 +490,16 @@ srv_teal_module <- function(id,
489490
})
490491

491492
# Call modules.
492-
if (!inherits(modules, "teal_module_previewer")) {
493-
obs_module <- .call_once_when(
494-
!is.null(module_teal_data()),
495-
ignoreNULL = TRUE,
496-
handlerExpr = {
497-
module_out(.call_teal_module(modules, datasets, module_teal_data, reporter))
498-
}
499-
)
500-
} else {
501-
# Report previewer must be initiated on app start for report cards to be included in bookmarks.
502-
# When previewer is delayed, cards are bookmarked only if previewer has been initiated (visited).
503-
module_out(.call_teal_module(modules, datasets, module_teal_data, reporter))
504-
}
493+
obs_module <- .call_once_when(
494+
!is.null(module_teal_data()),
495+
ignoreNULL = TRUE,
496+
handlerExpr = {
497+
out <- .call_teal_module(modules, datasets, module_teal_data, reporter)
498+
srv_add_reporter("add_reporter_wrapper", out, reporter)
499+
module_out(out)
500+
}
501+
)
505502
})
506-
507503
module_out
508504
})
509505
}
@@ -514,7 +510,7 @@ srv_teal_module <- function(id,
514510

515511
# collect arguments to run teal_module
516512
args <- c(list(id = "module"), modules$server_args)
517-
if (is_arg_used(modules$server, "reporter")) {
513+
if (is_arg_used(modules$server, "reporter") && !is.null(reporter)) {
518514
args <- c(args, list(reporter = reporter))
519515
}
520516

R/module_snapshot_manager.R

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,12 @@ srv_snapshot_manager <- function(id, slices_global) {
183183
modalDialog(
184184
easyClose = TRUE,
185185
textInput(ns("snapshot_name"), "Name the snapshot", width = "100%", placeholder = "Meaningful, unique name"),
186+
tags$script(
187+
shiny::HTML(
188+
sprintf("shinyjs.autoFocusModal('%s');", ns("snapshot_name")),
189+
sprintf("shinyjs.enterToSubmit('%s', '%s');", ns("snapshot_name"), ns("snapshot_name_accept"))
190+
)
191+
),
186192
footer = shiny::div(
187193
shiny::tags$button(
188194
type = "button",

R/module_teal.R

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,9 @@ ui_teal <- function(id, modules) {
5353
mod <- extract_module(modules, class = "teal_module_previewer")
5454
reporter_opts <- if (length(mod)) .get_reporter_options(mod[[1]]$server_args)
5555
modules <- drop_module(modules, "teal_module_landing")
56-
modules <- drop_module(modules, "teal_module_previewer")
5756

57+
# show busy icon when `shiny` session is busy computing stuff
58+
# based on https://stackoverflow.com/questions/17325521/r-shiny-display-loading-message-while-function-is-running/22475216#22475216 # nolint: line_length.
5859
shiny_busy_message_panel <- conditionalPanel(
5960
condition = "(($('html').hasClass('shiny-busy')) && (document.getElementById('shiny-notification-panel') == null))", # nolint: line_length.
6061
tags$div(
@@ -108,6 +109,7 @@ ui_teal <- function(id, modules) {
108109
theme = get_teal_bs_theme(),
109110
include_teal_css_js(),
110111
shinyjs::useShinyjs(),
112+
shiny::includeScript(system.file("js/extendShinyJs.js", package = "teal.reporter")),
111113
shiny_busy_message_panel,
112114
tags$div(id = ns("tabpanel_wrapper"), class = "teal-body", navbar),
113115
tags$hr(style = "margin: 1rem 0 0.5rem 0;")
@@ -116,14 +118,13 @@ ui_teal <- function(id, modules) {
116118

117119
#' @rdname module_teal
118120
#' @export
119-
srv_teal <- function(id, data, modules, filter = teal_slices()) {
121+
srv_teal <- function(id, data, modules, filter = teal_slices(), reporter = teal.reporter::Reporter$new()) {
120122
checkmate::assert_character(id, max.len = 1, any.missing = FALSE)
121123
checkmate::assert_multi_class(data, c("teal_data", "teal_data_module", "reactive"))
122124
checkmate::assert_class(modules, "teal_modules")
123125
checkmate::assert_class(filter, "teal_slices")
124126

125127
modules <- drop_module(modules, "teal_module_landing")
126-
modules <- drop_module(modules, "teal_module_previewer")
127128

128129
moduleServer(id, function(input, output, session) {
129130
logger::log_debug("srv_teal initializing.")
@@ -159,7 +160,6 @@ srv_teal <- function(id, data, modules, filter = teal_slices()) {
159160
srv_check_module_datanames("datanames_warning", data_handled, modules)
160161

161162
data_validated <- .trigger_on_success(data_handled)
162-
163163
data_signatured <- reactive({
164164
req(inherits(data_validated(), "teal_data"))
165165
is_filter_ok <- check_filter_datanames(filter, names(data_validated()))
@@ -239,17 +239,16 @@ srv_teal <- function(id, data, modules, filter = teal_slices()) {
239239
ui = tags$div(validate_ui)
240240
)
241241
}
242-
243-
if (is_arg_used(modules, "reporter")) {
242+
if (!is.null(reporter)) {
244243
shinyjs::show("reporter_menu_container")
244+
reporter$set_id(attr(filter, "app_id"))
245+
teal.reporter::preview_report_button_srv("preview_report", reporter)
246+
teal.reporter::report_load_srv("load_report", reporter)
247+
teal.reporter::download_report_button_srv(id = "download_report", reporter = reporter)
248+
teal.reporter::reset_report_button_srv("reset_reports", reporter)
245249
} else {
246250
removeUI(selector = sprintf("#%s", session$ns("reporter_menu_container")))
247251
}
248-
reporter <- teal.reporter::Reporter$new()$set_id(attr(filter, "app_id"))
249-
teal.reporter::preview_report_button_srv("preview_report", reporter)
250-
teal.reporter::report_load_srv("load_report", reporter)
251-
teal.reporter::download_report_button_srv(id = "download_report", reporter = reporter)
252-
teal.reporter::reset_report_button_srv("reset_reports", reporter)
253252

254253
datasets_rv <- if (!isTRUE(attr(filter, "module_specific"))) {
255254
eventReactive(data_signatured(), {

R/teal_data_module-eval_code.R

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,13 @@ setOldClass("teal_data_module")
1818
#' @include teal_data_module.R
1919
#' @name eval_code
2020
#' @rdname teal_data_module
21-
#' @aliases eval_code,teal_data_module,character-method
22-
#' @aliases eval_code,teal_data_module,language-method
23-
#' @aliases eval_code,teal_data_module,expression-method
21+
#' @aliases eval_code,teal_data_module
22+
#' @aliases \S4method{eval_code}{teal_data_module}
2423
#'
2524
#' @importFrom methods setMethod
2625
#' @importMethodsFrom teal.code eval_code
2726
#'
28-
setMethod("eval_code", signature = c("teal_data_module", "character"), function(object, code) {
27+
setMethod("eval_code", signature = c(object = "teal_data_module"), function(object, code) {
2928
teal_data_module(
3029
ui = function(id) {
3130
ns <- NS(id)
@@ -50,11 +49,3 @@ setMethod("eval_code", signature = c("teal_data_module", "character"), function(
5049
once = attr(object, "once")
5150
)
5251
})
53-
54-
setMethod("eval_code", signature = c("teal_data_module", "language"), function(object, code) {
55-
eval_code(object, code = paste(lang2calls(code), collapse = "\n"))
56-
})
57-
58-
setMethod("eval_code", signature = c("teal_data_module", "expression"), function(object, code) {
59-
eval_code(object, code = paste(lang2calls(code), collapse = "\n"))
60-
})

0 commit comments

Comments
 (0)