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)
<- function(label = "example teal module") {
example_module module(
label = label,
server = function(id, data) {
::assert_class(data, "reactive")
checkmate::assert_class(isolate(data()), "teal_data")
checkmate
moduleServer(id, function(input, output, session) {
updateSelectInput(session, "dataname", choices = isolate(datanames(data())))
$dataset <- renderPrint({
outputreq(input$dataname)
data()[[input$dataname]]
})
})
},ui = function(id) {
<- NS(id)
ns 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:
<- init(
app 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:
<- function(label = "example teal module") {
example_module_with_reporting module(
label = label,
server = function(id, data, reporter) {
moduleServer(id, function(input, output, session) {
updateSelectInput(session, "dataname", choices = isolate(datanames(data())))
$dataset <- renderPrint({
outputreq(input$dataname)
data()[[input$dataname]]
})
})
},ui = function(id) {
<- NS(id)
ns 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
:
<- init(
app 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
.
<- function(label = "example teal module") {
example_module_with_reporting module(
label = label,
server = function(id, data, reporter) {
moduleServer(id, function(input, output, session) {
::simple_reporter_srv(
teal.reporterid = "reporter",
reporter = reporter,
card_fun = function(card) card
)updateSelectInput(session, "dataname", choices = isolate(datanames(data())))
$dataset <- renderPrint({
outputreq(input$dataname)
data()[[input$dataname]]
})
})
},ui = function(id) {
<- NS(id)
ns sidebarLayout(
sidebarPanel(
::simple_reporter_ui(ns("reporter")),
teal.reporterselectInput(ns("dataname"), "Choose a dataset", choices = NULL)
),mainPanel(verbatimTextOutput(ns("dataset")))
)
}
) }
This updated module is now ready to be launched:
<- init(
app 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
.
<- function(card = teal.reporter::ReportCard$new()) {
custom_function $append_text("This is content from a custom teal module!")
card
card
}
<- function(label = "example teal module") {
example_module_with_reporting module(
label = label,
server = function(id, data, reporter) {
moduleServer(id, function(input, output, session) {
::simple_reporter_srv(
teal.reporterid = "reporter",
reporter = reporter,
card_fun = custom_function
)updateSelectInput(session, "dataname", choices = isolate(datanames(data())))
$dataset <- renderPrint({
outputreq(input$dataname)
data()[[input$dataname]]
})
})
},ui = function(id) {
<- NS(id)
ns sidebarLayout(
sidebarPanel(
::simple_reporter_ui(ns("reporter")),
teal.reporterselectInput(ns("dataname"), "Choose a dataset", choices = NULL)
),mainPanel(verbatimTextOutput(ns("dataset")))
)
}
) }
<- init(
app 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:
<- function(card = TealReportCard$new()) {
custom_function # ... 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)
##
<- function(label = "Example") {
example_reporter_module module(
label = label,
server = function(id, data, reporter, filter_panel_api) {
<- !missing(filter_panel_api) && inherits(filter_panel_api, "FilterPanelApi")
with_filter moduleServer(id, function(input, output, session) {
updateSelectInput(session, "dataname", choices = isolate(datanames(data())))
<- reactive(data()[[input$dataname]])
dat observe({
req(dat())
updateSliderInput(session, "nrow", max = nrow(dat()), value = floor(nrow(dat()) / 5))
})
<- reactive({
table_q req(input$dataname)
req(input$nrow)
within(
data(),
<- head(dataset, nrows),
result dataset = as.name(input$dataname),
nrows = input$nrow
)
})
$table <- renderTable(table_q()[["result"]])
output
### REPORTER
<- function(card = teal.reporter::ReportCard$new(), comment) {
card_fun $set_name("Table Module")
card$append_text(paste("Selected dataset", input$dataname), "header2")
card$append_text("Selected Filters", "header3")
cardif (with_filter) {
$append_text(filter_panel_api$get_filter_state(), "verbatim")
card
}$append_text("Encoding", "header3")
card$append_text(
card::as.yaml(
yaml::setNames(
statslapply(c("dataname", "nrow"), function(x) input[[x]]), c("dataname", "nrow")
)
),"verbatim"
)$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")
cardif (!comment == "") {
$append_text("Comment", "header3")
card$append_text(comment)
card
}
card
}::add_card_button_srv(
teal.reporter"addReportCard",
reporter = reporter,
card_fun = card_fun
)::download_report_button_srv("downloadButton", reporter = reporter)
teal.reporter::reset_report_button_srv("resetButton", reporter)
teal.reporter###
})
},ui = function(id) {
<- NS(id)
ns
sidebarLayout(
sidebarPanel(selectInput(ns("dataname"), "Choose a dataset", choices = NULL)),
mainPanel(
::simple_reporter_ui(ns("reporter")),
teal.reporterverbatimTextOutput(ns("dataset"))
)
)
sidebarLayout(
sidebarPanel(
div(
::add_card_button_ui(ns("addReportCard")),
teal.reporter::download_report_button_ui(ns("downloadButton")),
teal.reporter::reset_report_button_ui(ns("resetButton"))
teal.reporter
),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")))
)
}
)
}
<- init(
app 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)