The teal
package offers an integrated reporting feature utilizing the teal.reporter
package.
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
.
Thus the app user can interact with the report.
The responsibilities of a module developer include:
The entire life cycle of objects involved in creating the report and configuring the module to preview the report is handled by teal
.
Let us consider an example module, based on the example module from teal
:
library(teal) example_module <- function(label = "example teal module") { module( label = label, server = function(id, data) { checkmate::assert_class(data, "reactive") checkmate::assert_class(isolate(data()), "teal_data") moduleServer(id, function(input, output, session) { updateSelectInput(session, "dataname", choices = isolate(datanames(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:
app <- init( data = teal_data(IRIS = iris, MTCARS = mtcars), modules = example_module() ) if (interactive()) shinyApp(app$ui, app$server)
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:
example_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(datanames(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
:
app <- init( data = teal_data(IRIS = iris, MTCARS = mtcars), modules = example_module_with_reporting() ) if (interactive()) shinyApp(app$ui, app$server)
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.
teal.reporter
moduleThe UI and the server logic necessary for adding cards from example_module_with_reporting
to the report are provided by teal.reporter::simple_reporter_ui
and teal.reporter::simple_reporter_srv
.
example_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(datanames(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:
app <- init( data = teal_data(IRIS = iris, MTCARS = mtcars), modules = example_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.
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
.
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 card
object, otherwise errors may occur in teal
.
custom_function <- function(card = teal.reporter::ReportCard$new()) { card$append_text("This is content from a custom teal module!") card } example_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 = custom_function ) updateSelectInput(session, "dataname", choices = isolate(datanames(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"))) ) } ) }
app <- init( data = teal_data(IRIS = iris, MTCARS = mtcars), modules = example_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.
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:
custom_function <- function(card = TealReportCard$new()) { # ... some code ... # card }
Without this definition, the API of TealReportCard
will not be available within the function.
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
.
library(teal) library(teal.reporter) 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(datanames(data()))) dat <- reactive(data()[[input$dataname]]) observe({ 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 ) teal.reporter::download_report_button_srv("downloadButton", reporter = reporter) teal.reporter::reset_report_button_srv("resetButton", reporter) ### }) }, 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( 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")) ), selectInput(ns("dataname"), "Choose a dataset", choices = NULL), sliderInput(ns("nrow"), "Number of rows", min = 1, max = 1, value = 1, step = 1) ), mainPanel(tableOutput(ns("table"))) ) } ) } app <- init( data = teal_data(AIR = airquality, IRIS = iris), modules = list( example_reporter_module(label = "with Reporter"), example_module(label = "without Reporter") ), filter = teal_slices(teal_slice(dataname = "AIR", varname = "Temp", selected = c(72, 85))), header = "Example teal app with reporter" ) if (interactive()) shinyApp(app$ui, app$server)
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.