Skip to content

Commit

Permalink
Polyglot UI (#397)
Browse files Browse the repository at this point in the history
Co-authored-by: Barret Schloerke <schloerke@gmail.com>
Co-authored-by: Cass (Z.) Wilkinson Saldaña <12399460+zoews@users.noreply.github.com>
Co-authored-by: Carson Sievert <cpsievert1@gmail.com>
  • Loading branch information
4 people committed Sep 8, 2020
1 parent 5ff9030 commit 3460acb
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 14 deletions.
1 change: 1 addition & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
rsconnect/
^\.github/
^inst/tutorials/.*\.html$
^inst/tutorials/.*\.sqlite$
^revdep$
^LICENSE\.md$
^cran-comments\.md$
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ issues

# html tutorial output
inst/tutorials/*/*.html
inst/tutorials/*/*.sqlite

# deployment output
rsconnect/
Expand Down
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ learnr (development version)
* Added a new `tutorial_options()`, namely `exercise.error.checker`, for customizing feedback when exercise submission code produces an evaluation error. This option accepts a function with the same arguments as `exercise.checker`. Use `gradethis::grade_learnr_error()` for a sensible default for this option. ([#403](https://github.com/rstudio/learnr/pull/403))
* Added an event handler system, with the functions `event_register_handler()` and `one_time()`. There is also a new event `"section_viewed"`, which is triggered when a new section becomes visible. ([#398](https://github.com/rstudio/learnr/pull/398))
* Previously, when a question submission was reset, it would be recorded as a `"question_submission"` event with the value `reset=TRUE`. Now it a separate event, `"reset_question_submission"`. ([#398](https://github.com/rstudio/learnr/pull/398))
* Added a new `polyglot` tutorial to learnr. This tutorial displays mixing R, python, and sql exercises. See [`run_tutorial("polyglot", "learnr")`](https://learnr-examples.shinyapps.io/polyglot) for a an example. ([#397](https://github.com/rstudio/learnr/pull/397))

## Minor new features and improvements

Expand All @@ -27,6 +28,8 @@ learnr (development version)
* Added error handling when user specifies a non-existent label for `exercise.setup` option with an error message. ([#390](https://github.com/rstudio/learnr/pull/390))
* We no longer forward the checker code to browser (in html), but instead cache it. ([#390](https://github.com/rstudio/learnr/pull/390))
* We no longer display an invisible exercise result warning automatically. Instead, authors must set the exercise chunk option `exercise.warn_invisible = TRUE` to display an invisible result warning message. ([#373](https://github.com/rstudio/learnr/pull/373))
* With the addition of multi-language support, the default caption for an exercise has been switched to the combination of the exercise engine and `" code"`. This can be overwritten by setting the chunk option `exercise.cap`. ([#397](https://github.com/rstudio/learnr/pull/397))
* `engine` is now passed to the `exercise.checker` to help distinguish what language is being checked in the exercise. ([#397](https://github.com/rstudio/learnr/pull/397))
* Hitting the `TAB` key in an exercise has always opened the auto-completion drop down. Now, hitting the `TAB` key will also complete the currently selected code completion. ([#428](https://github.com/rstudio/learnr/pull/428))

## Bug fixes
Expand Down
14 changes: 9 additions & 5 deletions R/exercise.R
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,8 @@ evaluate_exercise <- function(exercise, envir, evaluate_global_setup = FALSE) {
envir_result = NULL,
evaluate_result = NULL,
envir_prep = envir_prep,
last_value = NULL
last_value = NULL,
engine = exercise$engine
)
if (is_exercise_result(checker_feedback)) {
return(checker_feedback)
Expand All @@ -232,7 +233,8 @@ evaluate_exercise <- function(exercise, envir, evaluate_global_setup = FALSE) {
envir_result = envir,
evaluate_result = rmd_results$evaluate_result,
envir_prep = envir_prep,
last_value = rmd_results$last_value
last_value = rmd_results$last_value,
engine = exercise$engine
)
}

Expand All @@ -246,7 +248,7 @@ evaluate_exercise <- function(exercise, envir, evaluate_global_setup = FALSE) {

try_checker <- function(exercise, name, check_code, envir_result,
evaluate_result, envir_prep, last_value,
html_output) {
engine) {
checker_func <- tryCatch(
get_checker_func(exercise, name, envir_prep),
error = function(e) {
Expand All @@ -267,7 +269,8 @@ try_checker <- function(exercise, name, check_code, envir_result,
envir_result = envir_result,
evaluate_result = evaluate_result,
envir_prep = envir_prep,
last_value = last_value
last_value = last_value,
engine = engine
)
# Throw better error messaging if the checker function signature is ill-defined
missing_args <- setdiff(names(args), checker_args)
Expand Down Expand Up @@ -402,7 +405,8 @@ render_exercise <- function(exercise, envir, envir_prep) {
envir_result = envir,
evaluate_result = evaluate_result,
envir_prep = envir_prep,
last_value = e
last_value = e,
engine = exercise$engine
)
if (is_exercise_result(checker_feedback)) {
return(checker_feedback)
Expand Down
2 changes: 1 addition & 1 deletion R/knitr-hooks.R
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ install_knitr_hooks <- function() {
completion <- as.numeric(options$exercise.completion %||% 1 > 0)
diagnostics <- as.numeric(options$exercise.diagnostics %||% 1 > 0)
startover <- as.numeric(options$exercise.startover %||% 1 > 0)
caption <- ifelse(is.null(options$exercise.cap), "Code", options$exercise.cap)
caption <- options[["exercise.cap"]] %||% paste0(options$engine, " code")
paste0('<div class="tutorial-', class,
'" data-label="', options$label,
'" data-caption="', caption,
Expand Down
20 changes: 12 additions & 8 deletions docs/exercises.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,23 @@ Exercises are interactive R code chunks that allow readers to directly execute R
<td><code>exercise.checker</code></td>
<td>Function used to check exercise answers (e.g., <code>gradethis::grade_learnr()</code>).</td>
</tr>
<tr class="odd">
<tr class="even">
<td><code>exercise.error.check.code</code></td>
<td>A string containing R code to use for checking code when an exercise evaluation error occurs (e.g., <code>"gradethis::grade_code()"</code>).</td>
</tr>
<tr class="even">
<tr class="odd">
<td><code>exercise.completion</code></td>
<td>Whether to enable code completion in the exercise editor.</td>
</tr>
<tr class="odd">
<tr class="even">
<td><code>exercise.diagnostics</code></td>
<td>Whether to enable code diagnostics in the exercise editor.</td>
</tr>
<tr class="even">
<tr class="odd">
<td><code>exercise.startover</code></td>
<td>Whether to include a "Start Over" button for the exercise.</td>
</tr>
<tr class="odd">
<tr class="even">
<td><code>exercise.warn_invisible</code></td>
<td>Whether to display an invisible result warning if the last value returned is invisible.</td>
</tr>
Expand Down Expand Up @@ -105,7 +105,7 @@ You can however arrange for setup code to be run before evaluation of an exercis

<div id="exercisesetupchained" style="width: 95%;"></div>
<script type="text/javascript">loadSnippet('exercisesetupchained')</script>

5. You can chain exercise chunks as well, but keep in mind that pre-filled code is used to serve as the setup code instead of user input. For example, if you turned `filtered-flights` back to an exercise, the pre-filled code is used as setup for the `arrange` exercise that use it as its setup:

<div id="exercisechained" style="width: 95%;"></div>
Expand Down Expand Up @@ -256,7 +256,7 @@ If, at any one of these stages, feedback should be provided, then `exercise.chec
</tbody>
</table>

Below is a rough sketch of how one might implement an `exercise.checker` function. Notice how the presence of `envir_result` may be used to determine which type of check is being done (i.e., code or result check).
Below is a rough sketch of how one might implement an `exercise.checker` function. Notice how the presence of `envir_result` may be used to determine which type of check is being done (i.e., code or result check).

<div id="customchecker"></div>
<script type="text/javascript">loadSnippet('customchecker')</script>
Expand Down Expand Up @@ -304,6 +304,10 @@ See the table below for a full description of all the arguments supplied to `exe
<td>The last value of the evaluated chunk.</td>
</tr>
<tr class="odd">
<td><code>engine</code></td>
<td>The <code>engine</code> value of the evaluated chunk.</td>
</tr>
<tr class="even">
<td><code>...</code></td>
<td>Unused (include for compatibility with parameters to be added in the future)</td>
</tr>
Expand All @@ -315,7 +319,7 @@ See the table below for a full description of all the arguments supplied to `exe

## Exercise Caption

By default exercises are displayed with caption of "Code". However, in some cases you may want either a custom per-chunk caption or a generic caption with a different connodation (e.g. "Exercise" or "Sandbox"). For example:
By default exercises are displayed with caption of "Code". However, in some cases you may want either a custom per-chunk caption or a generic caption with a different connotation (e.g. "Exercise" or "Sandbox"). For example:

<div id="exercisecaption"></div>
<script type="text/javascript">loadSnippet('exercisecaption')</script>
Expand Down
7 changes: 7 additions & 0 deletions inst/lib/tutorial/tutorial.js
Original file line number Diff line number Diff line change
Expand Up @@ -966,6 +966,13 @@ Tutorial.prototype.$initializeExerciseEditors = function() {
// get the engine
var engine = chunk_options["engine"]

if (engine.toLowerCase() != "r") {
// disable ace editor diagnostics if a non-r langauge engine is set
diagnostics = null;
// disable auto-completion if a non-r language engine is set
completion = null;
}

// set tutorial options/data
editor.tutorial = {
label: label,
Expand Down
56 changes: 56 additions & 0 deletions inst/tutorials/polyglot/polyglot.Rmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
title: "Multi-language exercises"
author: "Cass (Z.) Wilkinson Saldaña"
output: learnr::tutorial
runtime: shiny_prerendered
---

```{r setup, include = FALSE}
library(learnr)
# altered from https://datacarpentry.org/R-ecology-lesson/05-r-and-databases.html
if (!file.exists("portal_mammals.sqlite")) {
download.file(url = "https://ndownloader.figshare.com/files/2292171",
destfile = "portal_mammals.sqlite", mode = "wb")
}
mammals <- DBI::dbConnect(RSQLite::SQLite(), "portal_mammals.sqlite")
```

### Python or R?

The following two code exercises run language agnostic code. It will finally evaluate to either 5 or 10.

```{r r-not-python, exercise=TRUE, exercise.lines = 5}
# if running in R, this should return 10
# if running in Python, this should return FALSE and 5
x = 5
x <-10
print(x)
```

```{python python-not-r, exercise=TRUE, exercise.lines = 5}
# if running in R, this should return 10
# if running in Python, this should return FALSE and 5
x = 5
x <-10
print(x)
```

### Bash

Execute exercises in bash

```{bash bash-not-r, exercise = TRUE}
echo "hello world"
date
```

### SQL

Use a SQL connection defined in a setup chunk.

```{sql sql-not-r, exercise = TRUE, connection="mammals"}
SELECT *
FROM `surveys`
LIMIT 10
```
1 change: 1 addition & 0 deletions tests/testthat/test-available-tutorials.R
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ test_that("Tutorial names are retrieved", {
- ex-data-summarise : \"Summarise Tables\"
- ex-setup-r : \"Set Up\"
- hello : \"Hello, Tutorial!\"
- polyglot : \"Multi-language exercises\"
- quiz_question : \"Tutorial Quiz Questions in `learnr`\"
- setup-chunks : \"Chained setup chunks\"
- slidy : \"Slidly demo\""
Expand Down

0 comments on commit 3460acb

Please sign in to comment.