ETC5523: Communicating with Data

Workshop 11: Trace, package and audit a Shiny app

Author

Michael Lydeamore

Published

1 November 2001

🎯 Objectives

By the end of this workshop, you will be able to:

  • use reactlog to inspect a Shiny dependency graph;
  • replace repeated calculations with one shared reactive expression;
  • add a package function that launches a Shiny app; and
  • audit an app before web deployment.

1. Clone and run the app (5 minutes)

Clone the workshop repository and open wplshare.Rproj:

git clone https://github.com/numbats/etc5523-wpl-share.git
cd etc5523-wpl-share

Install any packages you do not already have:

install.packages(c(
  "bslib", "devtools", "dplyr", "ggplot2", "reactlog", "rsconnect", "shiny"
))

Start a fresh R session, enable reactive logging, and run the app:

reactlog::reactlog_enable()
shiny::runApp("inst/app")

Change both inputs and check the number, plot and table.

2. Inspect the current graph (10 minutes)

With the app running, open the reactive log:

  • macOS: Cmd + F3
  • Windows/Linux: Ctrl + F3

Find input$season, input$minimum_runs and the four outputs. The log may also contain browser and layout nodes; use search or double-click an output to isolate its dependency family.

Then return to the app and set Minimum runs to exactly 150 in season 2024/25. Compare the number, plot and table before inspecting app.R.

Answer:

  1. Why does the table disagree with the other outputs?
  2. Why do both inputs connect directly to every output in reactlog?
Note

If the keyboard shortcut does not work, stop the app and run shiny::reactlogShow() in the console.

3. Put the filter in one place (15 minutes)

Add this labelled reactive expression at the top of server():

selected_batters <- reactive({
  batters |>
    filter(
      season == input$season,
      runs >= input$minimum_runs
    )
}, label = "selected_batters")

Replace the repeated filtering code in all four outputs with calls to selected_batters().

Run the app and repeat the 150-run test. Then stop the app, clear the old log, run it again, and change one input:

shiny::reactlogReset()
shiny::runApp("inst/app")

Reopen reactlog. Identify which nodes depend on selected_batters and step through what happens after the input changes.

4. Make the app launchable from the package (12 minutes)

The repository already has the structure of an R package. Files under inst/app will be installed with it.

Create R/run_app.R, then add a documented, exported run_app() function:

usethis::use_r("run_app")

The function should:

  1. locate the installed app directory with system.file(); and
  2. pass that directory to shiny::runApp().

Generate the documentation, install the package and test the launcher:

devtools::document()
devtools::install()
wplshare::run_app()

Why would a path such as /Users/name/wpl-share/inst/app fail for another package user?

5. Audit the web deployment (10 minutes)

Treat the hosting service as a clean Linux computer that receives only the app bundle. Mark each item ready, not ready, or not applicable:

  • all required files are inside inst/app;
  • file paths are relative and match the exact case of the file names;
  • package dependencies can be detected from the app code;
  • there is no install.packages() call in the app;
  • no password, token or secret is stored in the repository;
  • every possible input state produces a useful response; and
  • any durable writes use storage outside the app’s local filesystem.

Ask rsconnect what it detects:

rsconnect::appDependencies("inst/app")

Which audit item does the current app handle least well? If time permits, make one improvement and rerun the app.