diff --git a/R/dummy_functions.R b/R/dummy_functions.R
index becb2706de..872786d9df 100644
--- a/R/dummy_functions.R
+++ b/R/dummy_functions.R
@@ -11,6 +11,16 @@
#' @inheritParams teal_modules
#' @param decorators `r lifecycle::badge("experimental")` (`list` of `teal_transform_module`) optional,
#' decorator for `object` included in the module.
+#'
+#' @section Reporting:
+#'
+#' This module returns an object of class `teal_module`, that contains a `server` function.
+#' Since the server function returns a `teal_report` object, this makes this module reportable, which means that
+#' the reporting functionality will be turned on automatically by the `teal` framework.
+#'
+#' For more information on reporting in `teal`, see the vignettes:
+#' - `vignette("managing-reproducible-report-documents-in-teal", package = "teal")`
+#' - `vignette("adding-support-for-reporting-to-custom-modules", package = "teal")`
#'
#' @return A `teal` module which can be included in the `modules` argument to [init()].
#'
diff --git a/_pkgdown.yml b/_pkgdown.yml
index f0bc2d1578..6427e7f5ac 100644
--- a/_pkgdown.yml
+++ b/_pkgdown.yml
@@ -68,6 +68,7 @@ articles:
contents:
- creating-custom-modules
- adding-support-for-reporting
+ - managing-reproducible-report-documents-in-teal
- transform-module-output
- title: Using `teal`
navbar: Using `teal`
diff --git a/man/example_module.Rd b/man/example_module.Rd
index 7e5251a87a..797d3bf60a 100644
--- a/man/example_module.Rd
+++ b/man/example_module.Rd
@@ -43,6 +43,20 @@ The object can be anything that can be handled by \code{renderPrint()}.
See the \code{vignette("transform-module-output", package = "teal")} or \code{\link{teal_transform_module}}
to read more about decorators.
}
+\section{Reporting}{
+
+
+This module returns an object of class \code{teal_module}, that contains a \code{server} function.
+Since the server function returns a \code{teal_report} object, this makes this module reportable, which means that
+the reporting functionality will be turned on automatically by the \code{teal} framework.
+
+For more information on reporting in \code{teal}, see the vignettes:
+\itemize{
+\item \code{vignette("managing-reproducible-report-documents-in-teal", package = "teal")}
+\item \code{vignette("adding-support-for-reporting-to-custom-modules", package = "teal")}
+}
+}
+
\examples{
app <- init(
data = teal_data(IRIS = iris, MTCARS = mtcars),
@@ -56,8 +70,8 @@ if (interactive()) {
\describe{
\item{example-1}{
\href{https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAGwEsAjAJykYE8AKcqagSgB0ItMnGYFStAG5wABAB4AtNIBmAVwhjaJdj2kAVLAFUAogIFpUcxUNql2A6dIAmUUlGkBeaV2oB9Z6-YASSxAgGUPaVpGWgBnXGkAWV0AYQBBLHDPGFICFhieXHtpGCJHFWo4GIi4AA9YVAqfErKK7QF+QSVpdiFyUXEpbR0QIpiACyFWVPR2cwASFVp4+ZiRKUYOgF8wTYBdIA}{Open in Shinylive}
- \if{html}{\out{}}
- \if{html}{\out{}}
+ \if{html}{\out{}}
+ \if{html}{\out{}}
}
}
}
diff --git a/vignettes/adding-support-for-reporting.Rmd b/vignettes/adding-support-for-reporting.Rmd
index 4e04e3fb8f..c754f2ef22 100644
--- a/vignettes/adding-support-for-reporting.Rmd
+++ b/vignettes/adding-support-for-reporting.Rmd
@@ -16,412 +16,94 @@ The `teal` package offers an integrated reporting feature utilizing the `teal.re
For a comprehensive explanation of the reporting functionality itself, please refer to the documentation therein.
This article is intended for module developers and aims to provide guidance on enhancing a custom `teal` module with an automatic reporting feature.
-This enhancement enables users to incorporate snapshots of the module outputs into a report which can then be reviewed in another module automatically provided by `teal`.
+This enhancement enables users to incorporate snapshots of the module outputs into a report, which can be viewed in another module called `Reporter previewer` (automatically provided by `teal`).
Thus the app user can interact with the report.
The responsibilities of a module developer include:
-- Adding support for reporting to their module.
+- Adding support for reporting to their module, by returning a reactive `teal_data` object from the module's `server` function.
- Specifying the outputs that constitute a snapshot of their module.
The entire life cycle of objects involved in creating the report and configuring the module to preview the report is handled by `teal`.
-## Custom module
-
-```{r setup, include=FALSE}
-library(teal)
-library(teal.reporter)
-```
-```{r as_interactive, eval=FALSE, echo=FALSE}
-interactive <- function() TRUE
-```
-
-Let us consider an example module, based on the example module from `teal`:
-```{r module_1}
-library(teal)
-library(teal.reporter)
-
-my_module <- function(label = "example teal module") {
- module(
- label = label,
- server = function(id, data) {
- checkmate::assert_class(isolate(data()), "teal_data")
-
- moduleServer(id, function(input, output, session) {
- updateSelectInput(session, "dataname", choices = isolate(names(data())))
- output$dataset <- renderPrint({
- req(input$dataname)
- data()[[input$dataname]]
- })
- })
- },
- ui = function(id) {
- ns <- NS(id)
- sidebarLayout(
- sidebarPanel(selectInput(ns("dataname"), "Choose a dataset", choices = NULL)),
- mainPanel(verbatimTextOutput(ns("dataset")))
- )
- }
- )
-}
-```
-
-Using `teal`, you can launch this example module with the following:
-
-```{r app_1}
-app <- init(
- data = teal_data(IRIS = iris, MTCARS = mtcars),
- modules = my_module()
-)
-
-if (interactive()) {
- shinyApp(app$ui, app$server)
-}
-```
-
-```{r shinylive_iframe_1, echo = FALSE, out.width = '150%', out.extra = 'style = "position: relative; z-index:1"', eval = requireNamespace("roxy.shinylive", quietly = TRUE) && knitr::is_html_output() && identical(Sys.getenv("IN_PKGDOWN"), "true")}
-code <- paste0(c(
- knitr::knit_code$get("as_interactive"),
- knitr::knit_code$get("module_1"),
- knitr::knit_code$get("app_1")
-), collapse = "\n")
-
-url <- roxy.shinylive::create_shinylive_url(code)
-knitr::include_url(url, height = "800px")
-```
-
-
## Add support for reporting
-### Modify the declaration of the server function
+To inform `teal` that the module requires reporting, return a `reactive` containing `teal_report` from the `server` function.
+Current outputs of the modules and reproducible code will be included in the module when "add to report" button is clicked.
-The first step is to add an additional argument to the server function declaration - `reporter`.
-This informs `teal` that the module requires `reporter`, and it will be included when the module is called.
-See below:
+Every input `teal_data` object in `teal` gets converted to a `teal_report` object, which you can then enhance with additional content.
-```{r module_2}
-my_module_with_reporting <- function(label = "example teal module") {
- module(
- label = label,
- server = function(id, data, reporter) {
- moduleServer(id, function(input, output, session) {
- updateSelectInput(session, "dataname", choices = isolate(names(data())))
- output$dataset <- renderPrint({
- req(input$dataname)
- data()[[input$dataname]]
- })
- })
- },
- ui = function(id) {
- ns <- NS(id)
- sidebarLayout(
- sidebarPanel(selectInput(ns("dataname"), "Choose a dataset", choices = NULL)),
- mainPanel(verbatimTextOutput(ns("dataset")))
- )
- }
- )
-}
-```
-
-With these modifications, the module is now ready to be launched with `teal`:
+For detailed information about the `teal_report` class and its capabilities, see the [teal_report Class vignette](https://insightsengineering.github.io/teal.reporter/latest-tag/articles/teal-report-class.html).
-```{r app_2}
-app <- init(
- data = teal_data(IRIS = iris, MTCARS = mtcars),
- modules = my_module_with_reporting()
-)
-
-if (interactive()) {
- shinyApp(app$ui, app$server)
-}
-```
-
-```{r shinylive_iframe_2, echo = FALSE, out.width = '150%', out.extra = 'style = "position: relative; z-index:1"', eval = requireNamespace("roxy.shinylive", quietly = TRUE) && knitr::is_html_output() && identical(Sys.getenv("IN_PKGDOWN"), "true")}
-code <- paste0(c(
- knitr::knit_code$get("as_interactive"),
- knitr::knit_code$get("setup"),
- knitr::knit_code$get("module_2"),
- knitr::knit_code$get("app_2")
-), collapse = "\n")
-
-url <- roxy.shinylive::create_shinylive_url(code)
-knitr::include_url(url, height = "800px")
-```
-
-
-`teal` adds another tab to the application, titled `Report previewer`.
-However, there is no visible change in how the module operates and appears and the user cannot add content to the report from this module.
-That requires inserting UI and server elements of the `teal.reporter` module into the module body.
-
-### Insert `teal.reporter` module
-
-The UI and the server logic necessary for adding cards from `my_module_with_reporting` to the report are provided by `teal.reporter::simple_reporter_ui` and `teal.reporter::simple_reporter_srv`.
-
-```{r module_3}
-my_module_with_reporting <- function(label = "example teal module") {
- module(
- label = label,
- server = function(id, data, reporter) {
- moduleServer(id, function(input, output, session) {
- teal.reporter::simple_reporter_srv(
- id = "reporter",
- reporter = reporter,
- card_fun = function(card) card
- )
- updateSelectInput(session, "dataname", choices = isolate(names(data())))
- output$dataset <- renderPrint({
- req(input$dataname)
- data()[[input$dataname]]
- })
- })
- },
- ui = function(id) {
- ns <- NS(id)
- sidebarLayout(
- sidebarPanel(
- teal.reporter::simple_reporter_ui(ns("reporter")),
- selectInput(ns("dataname"), "Choose a dataset", choices = NULL)
- ),
- mainPanel(verbatimTextOutput(ns("dataset")))
- )
- }
- )
-}
-```
-
-This updated module is now ready to be launched:
-
-```{r app_3}
-app <- init(
- data = teal_data(IRIS = iris, MTCARS = mtcars),
- modules = my_module_with_reporting()
-)
-
-if (interactive()) {
- shinyApp(app$ui, app$server)
-}
-```
-
-A new piece of `UI` has been added, and the buttons are clickable.
-The user can now add a card to the report and view it in the `Report previewer` module but the preview is still empty since we have not instructed our module what to put on the card.
-
-```{r shinylive_iframe_3, echo = FALSE, out.width = '150%', out.extra = 'style = "position: relative; z-index:1"', eval = requireNamespace("roxy.shinylive", quietly = TRUE) && knitr::is_html_output() && identical(Sys.getenv("IN_PKGDOWN"), "true")}
-code <- paste0(c(
- knitr::knit_code$get("as_interactive"),
- knitr::knit_code$get("setup"),
- knitr::knit_code$get("module_3"),
- knitr::knit_code$get("app_3")
-), collapse = "\n")
-
-url <- roxy.shinylive::create_shinylive_url(code)
-knitr::include_url(url, height = "800px")
-```
+## Example
-### Add content to the card
+Below is an example demonstrating how to build a `teal` module with reporting functionality.
-To add content to a card, we will utilize the public API exposed by the `TealReportCard` class.
-The `teal.reporter::simple_reporter_srv` module accepts the `card_fun` argument that determines the appearance of the output from our custom module.
-`ReportCard` and its derivatives allow the sequential addition of content according to the order of method calls.
-To explore the content, we can use the `$get_content` method.
-For further details, refer to the documentation of `TealReportCard` and `teal.reporter::ReportCard`.
+The key aspects of this implementation are:
-We will add simple text to the card by modifying the `card_fun` argument passed to `teal.reporter::simple_reporter_srv`.
-The function must return the `teal_card` object, otherwise errors may occur in `teal`.
+1. **Return a reactive `teal_report`**: The module's server function returns a reactive containing an enhanced `teal_report` object
+2. **Add content with `teal_card()`**: Commentary and section headers are added using `teal_card()`
+3. **Capture computations with `eval_code()`**: Code execution and results are captured for reproducibility
-```{r module_4}
-custom_function <- function(card = teal.reporter::ReportCard$new()) {
- card$append_text("This is content from a custom teal module!")
- card
-}
+```{r app_5}
+library(teal)
+library(teal.reporter)
-my_module_with_reporting <- function(label = "example teal module") {
+tm_simple_regression <- function(label = "Simple Regression") {
module(
label = label,
- server = function(id, data, reporter) {
- moduleServer(id, function(input, output, session) {
- teal.reporter::simple_reporter_srv(
- id = "reporter",
- reporter = reporter,
- card_fun = custom_function
- )
- updateSelectInput(session, "dataname", choices = isolate(names(data())))
- output$dataset <- renderPrint({
- req(input$dataname)
- data()[[input$dataname]]
- })
+ server = function(input, output, session, data, reporter, ...) {
+
+ output$plot <- renderPlot({
+ req(input$x_var, input$y_var)
+ dataset <- data()[["iris"]]
+ plot(dataset[[input$x_var]], dataset[[input$y_var]])
})
- },
- ui = function(id) {
- ns <- NS(id)
- sidebarLayout(
- sidebarPanel(
- teal.reporter::simple_reporter_ui(ns("reporter")),
- selectInput(ns("dataname"), "Choose a dataset", choices = NULL)
- ),
- mainPanel(verbatimTextOutput(ns("dataset")))
- )
- }
- )
-}
-```
-
-```{r app_4}
-app <- init(
- data = teal_data(IRIS = iris, MTCARS = mtcars),
- modules = my_module_with_reporting()
-)
-
-if (interactive()) {
- shinyApp(app$ui, app$server)
-}
-```
-
-Now, an application user can see the text added by `custom_function` in the `Report previewer` module.
-
-```{r shinylive_iframe_4, echo = FALSE, out.width = '150%', out.extra = 'style = "position: relative; z-index:1"', eval = requireNamespace("roxy.shinylive", quietly = TRUE) && knitr::is_html_output() && identical(Sys.getenv("IN_PKGDOWN"), "true")}
-code <- paste0(c(
- knitr::knit_code$get("as_interactive"),
- knitr::knit_code$get("setup"),
- knitr::knit_code$get("module_4"),
- knitr::knit_code$get("app_4")
-), collapse = "\n")
-
-url <- roxy.shinylive::create_shinylive_url(code)
-knitr::include_url(url, height = "800px")
-```
-
-### Add non-text content to the card
-
-`teal.reporter` supports the addition of tables, charts, and more.
-For more information, explore the API of `teal.reporter::ReportCard` to learn about the supported content types.
-
-### `TealReportCard`
-
-`teal` exports the `TealReportCard` class, which extends the `teal.reporter::ReportCard` class and provides several convenient methods to facilitate working with `teal` features like the filter panel or source code.
-For more details, refer to the documentation of `TealReportCard`.
-
-To support `TealReportCard`, the function that is passed to `teal.reporter::simple_reporter_srv` must define a default value for the card, as shown below:
-
-```{r}
-custom_function <- function(card = TealReportCard$new()) {
- # ... some code ... #
- card
-}
-```
-
-Without this definition, the API of `TealReportCard` will not be available within the function.
-
-## Example
-
-In conclusion, we have demonstrated how to build a standard `teal` app with code reproducibility and reporter functionalities.
-Note that the server function requires the `filter_panel_api` argument so that the filter panel state can be added to the report.
-
-In the final example, we have incorporated `teal.code` snippets.
-`teal.code` is an `R` library that offers utilities for storing code and associating it with an execution environment.
-This allows `ReporterCard` to store the code necessary to generate the table along with the table itself.
-To learn more about `teal.code` see the vignette _`qenv`_ in `teal.code`.
-
-```{r app_5}
-example_reporter_module <- function(label = "Example") {
- module(
- label = label,
- server = function(id, data, reporter, filter_panel_api) {
- with_filter <- !missing(filter_panel_api) && inherits(filter_panel_api, "FilterPanelApi")
- moduleServer(id, function(input, output, session) {
- updateSelectInput(session, "dataname", choices = isolate(names(data())))
- dat <- reactive(data()[[input$dataname]])
- observe({
- req(input$dataname)
- req(dat())
- updateSliderInput(session, "nrow", max = nrow(dat()), value = floor(nrow(dat()) / 5))
- })
-
- table_q <- reactive({
- req(input$dataname)
- req(input$nrow)
- within(
- data(),
- result <- head(dataset, nrows),
- dataset = as.name(input$dataname),
- nrows = input$nrow
- )
- })
-
- output$table <- renderTable(table_q()[["result"]])
-
- ### REPORTER
- card_fun <- function(card = teal.reporter::ReportCard$new(), comment) {
- card$set_name("Table Module")
- card$append_text(paste("Selected dataset", input$dataname), "header2")
- card$append_text("Selected Filters", "header3")
- if (with_filter) {
- card$append_text(filter_panel_api$get_filter_state(), "verbatim")
- }
- card$append_text("Encoding", "header3")
- card$append_text(
- yaml::as.yaml(
- stats::setNames(
- lapply(c("dataname", "nrow"), function(x) input[[x]]), c("dataname", "nrow")
- )
- ),
- "verbatim"
- )
- card$append_text("Module Table", "header3")
- card$append_table(table_q()[["result"]])
- card$append_text("Show R Code", "header3")
- card$append_text(teal.code::get_code(table_q()), "verbatim")
- if (!comment == "") {
- card$append_text("Comment", "header3")
- card$append_text(comment)
- }
- card
- }
- teal.reporter::add_card_button_srv(
- "addReportCard",
- reporter = reporter,
- card_fun = card_fun
+
+ report_data <- reactive({
+ obj <- data()
+ teal_card(obj) <-
+ c(teal_card(obj), "# Module's computation")
+ eval_code(
+ obj,
+ sprintf(
+ "correlation <- cor(iris[['%s']], iris[['%s']]);correlation",
+ input$x_var,
+ input$y_var
+ ),
+ keep_output = TRUE
)
- teal.reporter::download_report_button_srv("downloadButton", reporter = reporter)
- teal.reporter::reset_report_button_srv("resetButton", reporter)
- ###
})
+ report_data
},
+
ui = function(id) {
ns <- NS(id)
-
- sidebarLayout(
- sidebarPanel(selectInput(ns("dataname"), "Choose a dataset", choices = NULL)),
- mainPanel(
- teal.reporter::simple_reporter_ui(ns("reporter")),
- verbatimTextOutput(ns("dataset"))
- )
- )
-
- sidebarLayout(
- sidebarPanel(
- tags$div(
- teal.reporter::add_card_button_ui(ns("addReportCard")),
- teal.reporter::download_report_button_ui(ns("downloadButton")),
- teal.reporter::reset_report_button_ui(ns("resetButton"))
+ fluidPage(
+ h3("Simple Regression Plot"),
+ fluidRow(
+ column(6,
+ selectInput(ns("x_var"), "X Variable:",
+ choices = names(iris)[1:4], selected = "Sepal.Length")
),
- selectInput(ns("dataname"), "Choose a dataset", choices = NULL),
- sliderInput(ns("nrow"), "Number of rows", min = 1, max = 1, value = 1, step = 1)
+ column(6,
+ selectInput(ns("y_var"), "Y Variable:",
+ choices = names(iris)[1:4], selected = "Sepal.Width")
+ )
),
- mainPanel(tableOutput(ns("table")))
+ plotOutput(ns("plot"))
)
}
)
}
app <- init(
- data = teal_data(AIR = airquality, IRIS = iris),
- modules = list(
- example_reporter_module(label = "with Reporter"),
- my_module(label = "without Reporter")
+ data = teal_data(iris = iris),
+ modules = modules(
+ tm_simple_regression("Regression Analysis")
),
- filter = teal_slices(teal_slice(dataname = "AIR", varname = "Temp", selected = c(72, 85)))
-) |>
- modify_header(tags$h2("Example teal app with reporter"))
+ header = "Simple Teal App with Reporting"
+)
if (interactive()) {
shinyApp(app$ui, app$server)
diff --git a/vignettes/getting-started-with-teal.Rmd b/vignettes/getting-started-with-teal.Rmd
index 472dab4740..96c8c73d6a 100644
--- a/vignettes/getting-started-with-teal.Rmd
+++ b/vignettes/getting-started-with-teal.Rmd
@@ -126,9 +126,18 @@ For further details see [Filter Panel vignette](filter-panel.html).
### Reporting
-If any of the `modules` in your `teal` application support reporting (see [`teal.reporter`](https://insightsengineering.github.io/teal.reporter/) for more details), users of your application can add the outputs of the modules to a report.
-This report can then be downloaded and a special _Report Previewer_ module will be added to your application as an additional tab, where users can view and configure their reports before downloading them. See more details in [this vignette](adding-support-for-reporting.html).
+The `init` function includes a `reporter` parameter that controls the global reporting functionality of your `teal` application. This parameter determines whether and how users can generate reports from the analyses performed in your application.
+The `reporter` parameter in `init` accepts three types of values:
+
+* `parameter is omitted`: reporting is enabled by default globally.
+* `NULL`: reporting is disabled globally.
+* `teal.reporter::Report$new()`: predefined reporter object, possibly containing some initial cards.
+
+For more details on reporting functionality and how to implement it in your modules, see:
+- [adding support for reporting](adding-support-for-reporting.html),
+- [managing reproducible report documents in teal](managing-reproducible-report-documents-in-teal.html),
+- [`teal.reporter`](https://insightsengineering.github.io/teal.reporter/) package documentation.
### Reproducible code
diff --git a/vignettes/managing-reproducible-report-documents-in-teal.rmd b/vignettes/managing-reproducible-report-documents-in-teal.rmd
new file mode 100644
index 0000000000..075f33a241
--- /dev/null
+++ b/vignettes/managing-reproducible-report-documents-in-teal.rmd
@@ -0,0 +1,195 @@
+---
+title: "Managing Reproducible Report Documents in teal"
+author: "NEST CoreDev"
+output: rmarkdown::html_vignette
+vignette: >
+ %\VignetteIndexEntry{Managing Reproducible Report Documents in teal}
+ %\VignetteEngine{knitr::rmarkdown}
+ %\VignetteEncoding{UTF-8}
+---
+
+## Introduction
+
+`teal` provides reporting capabilities that allow app users to compose reproducible documents from their analysis modules. This vignette covers the key components for managing report documents in `teal` applications.
+
+The reporting system offers two types of control:
+
+1. **Global reporting control** via the `reporter` parameter in `init()`
+2. **Module-level control** via `after()` and `disable_report()`
+
+This vignette demonstrates how to use these features effectively to create flexible and customized reporting workflows.
+
+## Global Reporting Control
+
+##### 1. Default Reporting (parameter omitted)
+When you don't specify the `reporter` parameter, `teal` automatically enables default reporting functionality:
+
+```{r default_reporting}
+# Default reporting - automatically enabled
+app_default <- init(
+ data = teal_data(IRIS = iris, MTCARS = mtcars),
+ modules = modules(
+ example_module("Analysis 1"),
+ example_module("Analysis 2")
+ )
+)
+# Start app with
+# if (interactive()) shinyApp(app$ui, app$server)
+```
+
+With default reporting enabled:
+- A "Report previewer" tab is automatically added to your application
+- Users can add module outputs to reports and download them
+- Reports include reproducible code for all analyses
+
+##### 2. Disabled Reporting (`reporter = NULL`)
+To completely disable reporting functionality across your entire application:
+
+```{r disabled_reporting}
+# Reporting disabled - no report functionality available
+app_no_reporting <- init(
+ data = teal_data(IRIS = iris, MTCARS = mtcars),
+ modules = modules(
+ example_module("Analysis 1"),
+ example_module("Analysis 2")
+ ),
+ reporter = NULL
+)
+# Start app with
+# if (interactive()) shinyApp(app_no_reporting$ui, app_no_reporting$server)
+```
+
+When `reporter = NULL`:
+- No "Report previewer" tab appears
+- Modules cannot add content to reports
+- All reporting-related UI elements are hidden
+
+##### 3. Custom Reporter
+For advanced use cases, you can provide a custom initial reporter object:
+
+```{r custom_reporter}
+library(teal.reporter)
+
+# Create a custom reporter with specific configuration
+custom_reporter <- Reporter$new()
+custom_card <- teal_card("## Welcome", "Welcome message", head(iris))
+metadata(custom_card, "title") <- "The title"
+custom_reporter$append_cards(list(WelcomeCard = custom_card))
+
+# Use the custom reporter in your app
+app_custom <- init(
+ data = teal_data(IRIS = iris, MTCARS = mtcars),
+ modules = modules(
+ example_module("Analysis 1"),
+ example_module("Analysis 2")
+ ),
+ reporter = custom_reporter # TODO: doesn't work yet, app do not start with a predefined card
+)
+# Start app with
+# if (interactive()) shinyApp(app_custom$ui, app_custom$server)
+```
+
+##### 4. Custom Reporter with a template
+
+Custom initial reporter can also include a template function that will be applied to every card.
+With the templated approach, you can included disclaimers, data sources, data privacy information, etc.
+
+```{r custom_reporter_template}
+library(teal.reporter)
+
+# Create a custom reporter with specific configuration
+custom_reporter_template <- Reporter$new()
+custom_reporter_template$set_template(
+ # Need to return named `teal_card`
+ function(card) {
+ tcard <- c("Templated information", card)
+ attributes(tcard) <- attributes(card)
+ tcard
+ }
+)
+
+# Use the custom reporter in your app
+app_custom <- init(
+ data = teal_data(IRIS = iris, MTCARS = mtcars),
+ modules = modules(
+ example_module("Analysis 1"),
+ example_module("Analysis 2")
+ ),
+ reporter = custom_reporter_template
+)
+# Start app with
+# if (interactive()) shinyApp(app_custom$ui, app_custom$server)
+```
+
+#### Module-Level Reporting Control
+
+While the `reporter` parameter controls reporting globally, you can also disable reporting for specific modules using `disable_report()`. This allows you to have fine-grained control over which modules support reporting:
+
+```{r module_level_control}
+# App with global reporting enabled, but specific modules excluded
+app_selective <- init(
+ data = teal_data(IRIS = iris, MTCARS = mtcars),
+ modules = modules(
+ example_module("Analysis 1"), # Reporting enabled
+ disable_report(example_module("Analysis 2")), # Reporting disabled for this module
+ example_module("Analysis 3") # Reporting enabled
+ )
+)
+# Start app with
+# if (interactive()) shinyApp(app_selective$ui, app_selective$server)
+```
+
+In this example:
+- "Analysis 1" and "Analysis 3" modules can add content to reports
+- "Analysis 2" module cannot add content to reports (no "Add to Report" buttons appear)
+- The "Report previewer" tab is still available since reporting is globally enabled
+
+#### Choosing the Right Configuration
+
+* **Use default reporting** (omit parameter) for most applications where you want standard reporting functionality
+* **Use `reporter = NULL`** when you want to disable reporting entirely, perhaps for:
+ - Demo applications
+ - Apps where reporting is not needed
+ - Performance-critical applications
+* **Use a custom reporter** when you need:
+ - Special report formatting
+ - Custom report templates
+ - Integration with external reporting systems
+* **Use `disable_report()`** for individual modules when you want:
+ - Most modules to support reporting but exclude specific ones
+ - To disable reporting for modules that don't produce meaningful report content
+ - Fine-grained control over the reporting experience
+
+
+## Adjusting the Report Card
+
+### Using the `after` Function to Customize Module Report Cards
+
+The `after` function in `teal` provides a mechanism to customize and enhance the report cards generated by modules. This function allows you to inject additional content, modify existing content, or perform post-processing operations on report cards before they are finalized.
+
+#### Understanding the `after` Function
+
+The `after` function is typically used within module definitions to specify custom logic that should be executed after the module's standard report card is created. This enables you to:
+
+- Add custom text, plots, or tables to the report
+- Modify the formatting or structure of the report card
+- Include additional metadata or context
+- Perform validation or quality checks on the report content
+
+#### Basic Usage Pattern
+
+```{r after_basic_usage}
+# Example module with custom report card adjustment
+app_after <- init(
+ data = teal_data(IRIS = iris, MTCARS = mtcars),
+ modules = modules(
+ example_module("Analysis 1") |>
+ after(server = function(data) {
+ teal_card(data) <- c(teal_card(data), "## Custom Analysis Summary", head(iris))
+ return(data)
+ })
+ )
+)
+# Start app with
+# if (interactive()) shinyApp(app_after$ui, app_after$server)
+```