Including Static Dashboards in Crunch

Crunch allows you to host Shiny apps on the Crunch Jupyter server. Shiny apps are great, but they can be difficult to program, and can be very slow if not designed properly. Flexdashboards provide an easy-to-use alternative to Shiny apps. Instead of re-rendering dynamically based on the user action, Flexdashboards are effectively static websites that only include data which is live at the time of rendering. This can be desirable both because they are much easier to write, and because you can test and validate the dashboard’s behavior before it goes to the client. Additionally, since you have more control over when the dashboard is rendered, you can include more computationally expensive operations like fitting a machine learning model without affecting user experience.

You can host Flexdashboards on Crunch’s Shiny infrastructure using Shiny’s includeHTML() function. This function adds an HTML file to the Shiny UI. If all you include in the Shiny UI is the Flexdashboard html file, then the Shiny app effectively becomes a static site.

Basic workflow

Follow these steps to include a Flexdashboard in your Shiny app:

  1. First create a new Rmarkdown document with a Flexdashboard output option
  2. Render the Rmd file into an html file
  3. Upload the rendered html file to your app’s Jupyter folder
  4. In your shiny UI code add includeHTML(<filename>) to the ui object. Your app.R file might look something like this:
library(crunchy)
ui <- fluidPage(
    includeHTML("dashboard")
)

server <- function(input, output, session) {}

# Run the application
shinyApp(ui = ui, server = server)

Note that we haven’t included any authentication in this app which means that users who do not have access to a dataset could theoretically access it. The next step is to check that the user is logged into Crunch.

Check User Authentication

Crunchy includes a shinyUser() function, which lets us display different content to the user, including verifying whether they are logged into Crunch of not. To do this, we’ll also make use of Shiny’s renderUI function to conditionally create a different UI.

ui <- fluidPage(
    uiOutput("dashboard")
)

This creates the UI object, but most of the work is going to be done on the server side. We can use the shinyUser() function to get a user record for the current viewer of the app. This record includes some useful pieces of information, including the user’s name, their email address, and their Crunch preferences. If the user isn’t logged into Crunch, they will see a red error message, and they won’t see the dashboard.

By checking the value of the shinyUser before calling includeHTML(), we can prevent unauthorized users from seeing the rendered dashboard:

server <- function(input, output, session) {
    user <- shinyUser()
    output$dashboard <- renderUI({
        user()
        includeHTML("acme_dashboard.html")
    })
}

Conditional dashboards and user identification

We can take this pattern one step further to show different users different dashboards. We use the same UI object, but check the user’s email and change the displayed HTML file based on that email.

server <- function(input, output, session) {
    user <- shinyUser()
    output$dashboard <- renderUI({
        current_user <- email(user())
        if (grepl("acme.com$", current_user)) {
            includeHTML("acme_dashboard.html")
        } else if (grepl("globex.com$", current_user)) {
            includeHTML("globex_dashboard.html")
        } else {
            h1("You do not have access to this dashboard, please contact your dataset administrator.")
        }
    })
}

What this does is check the user’s email address to identify clients from one company, and then displays a rendered dashboard for that company. An example app of this kind is here, to run it, copy the app folder, open app.R and run it with shiny::runApp("app.R"), or by clicking the “Run App” button in RStudio.

Managing Dashboard Renders

The above code assumes that you are going to be re-rendering your Rmd files manually, and uploading the results to the Crunch Shiny server. You can also re-render the Rmd files dynamically as part of the Shiny app by calling rmarkdown::render() somewhere in your server function. For example the following app re-renders the dashboard whenever the user clicks an action button. Note that for this to work smoothly you should use the reactivePoll to ensure that the UI is refreshed after the file is regenerated. If you don’t use reactivePoll(), you might end up in a situation where the UI refreshes before the RMarkdown file is finished rendering.

library(crunchy)

ui <- fluidPage(
    actionButton('refresh', 'Refresh Dashboard'),
    textOutput("last_render"),
    uiOutput("dashboard")
)

server <- function (input, output, session) {
    file <- reactivePoll(1000, session,
        checkFunc = function () {
            file.info("acme_dashboard.html")$ctime
        },
        valueFunc = function () {
            list(
                path = "acme_dashboard.html",
                time = Sys.time()
            )
        }
    )
    user <- shinyUser()
    observeEvent(input$refresh, rmarkdown::render("acme_dashboard.Rmd"))
    output$last_render <- renderText(paste("Last Render:", file()$time))
    output$dashboard <- renderUI({
        user()
        includeHTML(file()$path)
    })
}

shinyApp(ui = ui, server = server)

A full example can be found on Github.

Conclusion

Flexdashboards provide a simple way to communicate with your users. By using the Crunch Shiny infrastructure you can host and distribute static dashboards in the same way as Shiny apps. This allows you to customize the behavior Crunch Dashboards without needing to worry about some of the complexity which comes along with Shiny apps.