Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Polyglot UI #397

Merged
merged 14 commits into from
Sep 8, 2020
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 exercise's execution language is displayed in the top left corner of the exercise. This can be overwritten by setting the chunk option `exercise.cap.engine`. ([#397](https://github.com/rstudio/learnr/pull/397))
schloerke marked this conversation as resolved.
Show resolved Hide resolved
* `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))

## 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) {
cpsievert marked this conversation as resolved.
Show resolved Hide resolved
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
4 changes: 3 additions & 1 deletion R/knitr-hooks.R
Original file line number Diff line number Diff line change
Expand Up @@ -234,9 +234,11 @@ 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"]] %||% "Code"
engine_caption <- options[["exercise.cap.engine"]] %||% options$engine %||% ""
paste0('<div class="tutorial-', class,
'" data-label="', options$label,
'" data-engine="', engine_caption,
'" data-caption="', caption,
'" data-completion="', completion,
'" data-diagnostics="', diagnostics,
Expand Down
16 changes: 12 additions & 4 deletions docs/exercises.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,22 @@ Exercises are interactive R code chunks that allow readers to directly execute R
<td>Caption for exercise chunk (defaults to "Code")</td>
</tr>
<tr class="even">
<td><code>exercise.cap.engine</code></td>
schloerke marked this conversation as resolved.
Show resolved Hide resolved
<td>Caption for exercise chunk engine (defaults to the chunk engine value)</td>
</tr>
<tr class="odd">
<td><code>exercise.eval</code></td>
<td>Whether to pre-evaluate the exercise so the reader can see some default output (defaults to <code>FALSE</code>).</td>
</tr>
<tr class="odd">
<tr class="even">
<td><code>exercise.lines</code></td>
<td>Lines of code for exercise editor (default to size of code chunk).</td>
</tr>
<tr class="even">
<tr class="odd">
<td><code>exercise.timelimit</code></td>
<td>Number of seconds to limit execution time to (defaults to 30).</td>
</tr>
<tr class="odd">
<tr class="even">
<td><code>exercise.checker</code></td>
<td>Function used to check exercise answers (e.g., <code>gradethis::grade_learnr()</code>).</td>
</tr>
Expand Down Expand Up @@ -304,6 +308,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 +323,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
5 changes: 5 additions & 0 deletions inst/lib/tutorial/tutorial.css
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,8 @@
height: 100%;
border: solid 1px #cccccc;
}

.tutorial-exercise-engine {
color: #c0c0c0;
margin-right: 10px;
}
15 changes: 14 additions & 1 deletion inst/lib/tutorial/tutorial.js
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,8 @@ Tutorial.prototype.$initializeExerciseEditors = function() {
// capture label and caption
var label = exercise.attr('data-label');
var caption = exercise.attr('data-caption');
var engine_txt = exercise.attr('data-engine');


// helper to create an id
function create_id(suffix) {
Expand Down Expand Up @@ -895,7 +897,11 @@ Tutorial.prototype.$initializeExerciseEditors = function() {

// creating heading
var panel_heading = $('<div class="panel-heading tutorial-panel-heading"></div>');
panel_heading.text(caption);
// add engine if it exists
if (engine_txt && engine_txt.length > 0) {
panel_heading.append($('<span class="tutorial-exercise-engine"></span>').text(engine_txt));
}
panel_heading.append($('<span class="tutorial-exercise-caption"></span>').text(caption));
input_div.append(panel_heading);

// create body
Expand Down Expand Up @@ -966,6 +972,13 @@ Tutorial.prototype.$initializeExerciseEditors = function() {
// get the engine
var engine = chunk_options["engine"]

if (engine != "r") {
schloerke marked this conversation as resolved.
Show resolved Hide resolved
// 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: "Zoë Wilkinson Saldaña"
schloerke marked this conversation as resolved.
Show resolved Hide resolved
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