Tutorials can be published all of the same ways that Shiny applications can, including running locally on an end-user’s machine or running on a Shiny Server or hosting service like shinyapps.io.

R Package

A very convenient way to publish tutorials is within R packages. Tutorials included within R packages can be run via the learnr::run_tutorial function. For example, the following functions runs the “hello” and “slidy” sample tutorials built in to the learnr package:

learnr::run_tutorial("hello", package = "learnr")
learnr::run_tutorial("slidy", package = "learnr")

To bundle a tutorial into an R package and make it available for running via the run_tutorial function you should:

  1. Create a tutorials directory within the inst sub-directory of your package and then create a directory for your tutorial there (e.g. inst/tutorials/hello, inst/tutorials/slidy, etc.).

  2. Render your tutorial .Rmd to .html and include the rendered HTML in your R package (this will happen automatically during development & preview of the tutorial, you just need to be sure to include the .html file within the R package when you build it).

Once you’ve done this users can run your tutorial as follows (note they should be sure to install the learnr package before attempting to run the tutorial):

Users would then simply install tutor directly before running your tutorial, for example:

install.packages("learnr", type = "source")
learnr::run_tutorial("introduction", package = "mypackage")

Exercise Checkers

Note that if your tutorial performs exercise checking via an external package then you should be sure to add the package you use for checking as an Imports dependency of your package so it’s installed automatically along with your package.

Note that it’s likely that the learnr package will eventually include or depend on another package that provides checking functions. For the time being though explicit installation of external checking packages is a requirement.

Shiny Server

You can also deploy tutorials on a server as you’d deploy any other Shiny application. There are several ways to do this:

  1. Publish to the shinyapps.io cloud service.

  2. Publish to an RStudio Connect server.

  3. Publish to a Shiny Server.

For the first two methods you can use the RStudio IDE’s integrated one-click publishing to publish directly from your desktop:

Note that you should install the latest release of RStudio (v1.0.136 or higher) before attempting to use one-click publishing with tutorials.

Resource Usage

Since users can execute arbitrary R code within a tutorial, this code can also consume arbitrary resources and time! (e.g. users could create an infinite loop or allocate all available memory on the machine).

To limit the time taken for the execution of exercises you can use the exercise.timelimit option. see the documentation on Exercise Time Limits for additional details.

You can use various features of RStudio Connect and Shiny Server Pro to run tutorials within a resource and/or filesystem sandbox.

See also the section below on Exercise Execution for details on further customizing the evaluation environment for exercises.

Security Considerations

Since tutorials enable end users to submit R code for execution on the server, you need to architect your deployment of tutorials so that code is placed in an appropriate sandbox. There are a variety of ways to accomplish this including placing the entire Shiny Server in a container or Linux namespace that limits it’s access to the filesystem and/or other system resources.

Note that by default when running on UNIX systems the learnr package runs exercise code within a forked child process. This means that it’s not possible for exercise code to modify the state of the parent Shiny Server process. You can further customize the evaluation environment for exercises using AppArmor and other system resource management facilities (see the [Exercise Evaluation] section below for details).

Hosting Services

Publishing tutorials to a server makes them instantly accessible to any user with a web browser, and requires no local installation of R or R packages. It also opens up the possibility of collecting data on user interactions with tutorials (progress made, errors encountered, etc.).

The learnr package includes a variety of mechanisms intended to make it easy to deploy within a tutorial hosting service, each of which are described below. As of this writing there are no publicly available tutorial hosting services, however we hope this will change soon, and will update this documentation as services become available.

Tutorial Storage

Tutorials save the state of inputs and outputs (e.g. exercise and question submissions) and automatically restore them when users revisit the tutorial. For locally deployed tutorials (e.g. tutorials run from within an R package) this state is saved within the local filesystem.

For server deployed tutorials the state is saved within the per-domain storage provided by the end user’s web browser. This has the benefit of saving and restoring state on a per-user basis without any notion of user authentication or identity, however will only work within a single web browser (i.e. state won’t be restored if the users accesses the same tutorial from another machine).

Storage Provider

Hosting services will typically want to provide a custom implementation for the storage of tutorial state which is tied to their tutorial provisioning and user identity system.

A custom storage provider is specified by assigning an R list which includes the storage functions to the tutorial.storage global option. For example, here is a “no-op” storage provider:

The parameters passed to the storage provider’s functions are as follows (note that the various ID fields can be customized by a hosting provider, see the Tutorial Identifiers section below for details):

ID Default
tutorial_id Unique identfier for tutorial.
tutorial_version Tutorial version.
user_id Unique identfier for user.
object_id Unique identifier for R object.
data R object to be saved

Exercise Execution

Note that by default when running on UNIX systems the learnr package runs exercise code within a forked child process. This means that it’s not possible for exercise code to modify the state of the parent Shiny Server process. The section below describe the various ways of further customizing exercise execution.

Start and Cleanup Hooks

The learnr package can have it’s exercise evaluation environment customized using onstart and oncleanup hooks. Within these hooks you can use functions from RAppArmor package or another package that provides interfaces to system resource management functions.

Using this method you can apply time limits, resource limits, and filesystem limits. Here are the steps required to use RAppArmor with the onstart and oncleanup hooks:

  1. Install and configure the RAppArmor package as described here: https://github.com/jeroenooms/RAppArmor#readme

  2. Add the following line to the /etc/apparmor.d/rapparmor.d/r-user profile (this is required so that the default AppArmor profile also support calling the pandoc markdown renderer):

    /usr/lib/rstudio/bin/pandoc/* rix,
  3. Define onstart and oncleaup hooks by setting the tutorial.exercise.evaluator.onstart and tutorial.exercise.evaluator.oncleanup global options. For example:

You’d likely set these options within Rprofile.site or another R source file that is evaluated prior to running the tutorial with rmarkdown::run.

Fully Custom Evaluator

You can replace the default exercise evaluation code entirely with your own via the tutorial.exercise.evaluator global R option. The evaluator is an R function that accepts two arguments (an expression to evaluate and a timelimit for evaluation) and returns a list of functions (start, completed, and result) to use for execution .

The two built-in evaluators (used for Windows/MacOS and UNIX systems respectively are):

  1. inline_evaluator — Evaluate exercises inline within the main Shiny Server process (this is the default on Windows and Mac OS systems).

  2. forked_evaluator — Evaluate exercises within a forked child process (this is the default on UNIX systems).

You can see the source code for these evaluators here: https://github.com/rstudio/learnr/blob/master/R/evaluators.R.

Recording Events

As part of deploying a tutorial you might want to record the various user events which occur within tutorials (e.g. exercise and question submissions, requests for hints/solutions, etc.).

Event Recorder Function

You can capture events by using a custom event recorder function. This function is specified via the tutorial.event_recorder global option. For example, here’s how you would define a simple event recorder function that prints to stdout:

The following parameters are passed to the event recorder function (note that the various ID fields can be customized by a hosting provider, see the Tutorial Identifiers section below for details):

ID Default
tutorial_id Unique identfier for tutorial.
tutorial_version Tutorial version.
user_id Unique identfier for user.
event Event name (see below for various valid values).
data Custom event data.

The event parameter is one of the following values:

Event Description
exercise_hint User requested a hint or solution for an exercise.
exercise_error Error occurred within R code submitted as an answer for an exercise.
exercise_submission User submitted an answer for an exercise.
question_submission User submitted an answer for a multiple-choice question.
video_progress User watched a segment of a video.

The data parameter is an R list which provides additional data that varies depending on which event is being recorded.

Tutorial Identifiers

The Tutorial Storage and Recording Events sections above describe various ways to record user progress and actions within tutorials. Storage of tutorial state requires unique identifiers for tutorials (and their versions) as well as users.

Tutorial hosting services will often need to customize these identifiers based on their own notions of tutorial provisioning and user identify. This section describes how to do this.

Default Identifiers

The default tutorial and user identifiers are determined as follows:

ID Default
tutorial_id Network origin and path of tutorial.
tutorial_version 1.0
user_id Account name of server user executing the tutorial

In addition, tutorial authors can use YAML metadata to provide custom tutorial IDs and versions. For example:

Custom Identifiers

Tutorial hosting services will very often need to provide custom external definitions for tutorial IDs/versions and user IDs. This can be accomplished by adding HTTP headers to the requests that route to the tutorial. The names of the headers are configurable, and should be specified using the tutorial.http_header_tutorial_id, tutorial.http_header_tutorial_version and tutorial.http_header_user_id global options. For example:

Once configuring these custom header names you’d then need to ensure that the HTTP proxy layer mediating traffic to tutorials set them to the appropriate values.