Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
^\.github$
^.*\.Rproj$
^\.Rproj\.user$
*.html
1 change: 1 addition & 0 deletions .Rprofile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# renv disabled - using DESCRIPTION for dependencies
57 changes: 57 additions & 0 deletions .github/workflows/deploy-quiz.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Deploy Quiz to shinyapps.io

on:
push:
branches: [main, dev]
workflow_dispatch: # Allow manual deployment

jobs:
deploy:
runs-on: ubuntu-latest

env:
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup R
uses: r-lib/actions/setup-r@v2
with:
r-version: '4.4.0'
use-public-rspm: true

- name: Setup Pandoc
uses: r-lib/actions/setup-pandoc@v2

- name: Install dependencies from DESCRIPTION
uses: r-lib/actions/setup-r-dependencies@v2
with:
dependencies: '"hard"' # Install Imports, Depends, LinkingTo

- name: Configure rsconnect authentication
run: |
rsconnect::setAccountInfo(
name = '${{ secrets.SHINYAPPS_ACCOUNT }}',
token = '${{ secrets.SHINYAPPS_TOKEN }}',
secret = '${{ secrets.SHINYAPPS_SECRET }}'
)
shell: Rscript {0}

- name: Run build script
run: |
cat("🚀 Starting deployment process...\n")
source("build.R")
cat("✅ All deployments completed successfully!\n")
shell: Rscript {0}

- name: Upload deployment logs
uses: actions/upload-artifact@v4
if: failure()
with:
name: deployment-logs-${{ github.run_id }}
path: |
*.log
rsconnect/
modules/rsconnect/
10 changes: 9 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,12 @@
_freeze/
docs/
!docs/*.md
*.dcf
*.dcf
*.html

# renv - using DESCRIPTION for dependencies instead
renv/
renv.lock

# Shiny deployment
rsconnect/
17 changes: 12 additions & 5 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,19 @@ URL: https://github.com/ds4owd-dev/quiz,
BugReports: https://github.com/ds4owd-dev/quiz/issues
Imports:
learnr,
tidyverse,
dplyr,
gapminder,
gt,
gradethis
gradethis,
learnrhash,
httr,
knitr,
rmarkdown,
shiny,
bslib,
rsconnect
Suggests:
pkgdown,
quarto
pkgdown
Remotes:
rstudio/gradethis
rstudio/gradethis,
rundel/learnrhash
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,46 @@ The quizzes now include automatic submission to Google Forms for tracking studen

## Deployment Process

### Automated Deployment (CI/CD)

Quizzes are automatically deployed to shinyapps.io via GitHub Actions when changes are pushed to the `main` or `dev` branches.

#### Setting Up Automated Deployment

1. **Get shinyapps.io credentials**:
- Go to [shinyapps.io](https://www.shinyapps.io/) → Account → Tokens
- Click "Add Token" to generate new credentials
- Copy the `name`, `token`, and `secret` values

2. **Configure GitHub Secrets**:
- Go to your GitHub repository → Settings → Secrets and variables → Actions
- Add these repository secrets:
- `SHINYAPPS_ACCOUNT`: Your shinyapps.io account name
- `SHINYAPPS_TOKEN`: The token from shinyapps.io
- `SHINYAPPS_SECRET`: The secret from shinyapps.io

3. **Add new quizzes to automated deployment**:
- Edit `build.R` and add your new quiz:
```r
# Deploy new quiz module
rsconnect::deployDoc(
doc = "modules/md-02-quiz.Rmd",
appName = "openwashdata-module2-quiz",
forceUpdate = TRUE
)
```

#### How the CI/CD Workflow Works

The GitHub Action (`.github/workflows/deploy-quiz.yml`):
- **Triggers**: On pushes to `main`/`dev` branches or manual workflow dispatch
- **Environment**: Sets up R 4.3.2 with required packages from DESCRIPTION
- **Authentication**: Uses repository secrets to authenticate with shinyapps.io
- **Deployment**: Executes `build.R` to deploy all applications defined there
- **Logging**: Uploads deployment logs if any failures occur

### Manual Deployment

This quiz system uses a two-part deployment approach:

### 1. Deploy Individual Quizzes
Expand Down
18 changes: 18 additions & 0 deletions build.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
rsconnect::deployApp(
appName = "openwashdata-quiz-hub",
forceUpdate = TRUE
)

rsconnect::deployDoc(
doc = "modules/md-01-quiz.Rmd",
appName = "openwashdata-module1-quiz",
forceUpdate = TRUE
)

# Example of what to add when creating new quiz:

#rsconnect::deployDoc(
# doc = "modules/md-02-quiz.Rmd",
# appName = "openwashdata-module2-quiz",
# forceUpdate = TRUE
#)
111 changes: 110 additions & 1 deletion modules/_github_username.Rmd
Original file line number Diff line number Diff line change
@@ -1,3 +1,112 @@
```{r github-username-setup, include=FALSE}
# Read GitHub usernames from CSV
tryCatch({
github_users <- read.csv("github_usernames.csv", stringsAsFactors = FALSE)
# Create choices with display format: "First Last (username)"
username_choices <- setNames(
github_users$GitHub.Username,
paste0(github_users$First.Name, " ", github_users$Last.Name, " (", github_users$GitHub.Username, ")")
)
}, error = function(e) {
# Fallback if CSV not found
username_choices <- character(0)
})
```

```{r github-username, echo=FALSE}
textInput("github_username", "GitHub Username:", placeholder = "Enter your GitHub username")
div(
selectizeInput(
"github_username",
"GitHub Username:",
choices = NULL, # Start empty for performance
options = list(
placeholder = "Start typing your name or GitHub username...",
maxItems = 1,
searchField = c("value", "text"),
create = TRUE, # Allow creating new entries
persist = TRUE, # Keep options persistent
closeAfterSelect = TRUE,
loadThrottle = 200 # Delay loading to prevent auto-selection
)
),
# Warning message for new usernames
div(id = "username-warning", style = "color: orange; font-size: 12px; margin-top: 5px;"),
# Confirmation for new usernames
div(id = "username-confirmation", style = "margin-top: 10px;")
)
```

```{r, context="server"}
# Read GitHub usernames in server context
username_choices <- reactive({
tryCatch({
github_users <- read.csv("modules/github_usernames.csv", stringsAsFactors = FALSE)
# Create choices with display format: "First Last (username)"
setNames(
github_users$GitHub.Username,
paste0(github_users$First.Name, " ", github_users$Last.Name, " (", github_users$GitHub.Username, ")")
)
}, error = function(e) {
# Fallback if CSV not found
character(0)
})
})

# Update selectize choices on server side - delay to prevent auto-selection
observeEvent(session$clientData, {
# Small delay to ensure UI is ready before populating choices
invalidateLater(500, session)
isolate({
updateSelectizeInput(
session = session,
inputId = "github_username",
choices = username_choices(),
selected = character(0), # Ensure nothing is pre-selected
server = FALSE
)
})
}, once = TRUE)

# Validate username and show warning/confirmation
observeEvent(input$github_username, {
if (!is.null(input$github_username) && input$github_username != "") {
# Check if username is in the approved list
is_approved <- input$github_username %in% username_choices()

if (!is_approved) {
# Show warning for new username
output$`username-warning` <- renderUI({
div(
style = "color: orange; font-size: 12px; margin-top: 5px;",
HTML("⚠️ <strong>Warning:</strong> This username is not in the approved list. Please confirm it's correct.")
)
})

# Show confirmation checkbox
output$`username-confirmation` <- renderUI({
div(
style = "margin-top: 10px;",
checkboxInput(
"confirm_username",
HTML(paste0("I confirm that <strong>", input$github_username, "</strong> is my correct GitHub username")),
value = FALSE
)
)
})
} else {
# Clear warnings for approved username
output$`username-warning` <- renderUI({
div(
style = "color: green; font-size: 12px; margin-top: 5px;",
HTML("✓ Username found in approved list")
)
})
output$`username-confirmation` <- renderUI({})
}
} else {
# Clear all messages when empty
output$`username-warning` <- renderUI({})
output$`username-confirmation` <- renderUI({})
}
}, ignoreInit = TRUE)
```
9 changes: 9 additions & 0 deletions modules/github_usernames.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
First Name,Last Name,Email,Registration Time,Approval Status,GitHub Username
Rainbow,Train,larnsce@gmail.com,08/28/2025 1:36:03 PM,approved,rainbow-train
John,Doe,john.doe@email.com,08/25/2025 9:15:22 AM,approved,johndoe
Jane,Smith,jane.smith@email.com,08/26/2025 2:30:45 PM,approved,janesmith
Alice,Johnson,alice.j@email.com,08/27/2025 11:20:10 AM,approved,alice-johnson
Bob,Wilson,bob.wilson@email.com,08/27/2025 3:45:33 PM,approved,bobwilson
Carol,Brown,carol.brown@email.com,08/28/2025 8:12:15 AM,approved,carol-brown
David,Miller,d.miller@email.com,08/28/2025 10:25:40 AM,approved,davidmiller
Emily,Davis,emily.davis@email.com,08/28/2025 4:18:22 PM,approved,emily-davis
8 changes: 4 additions & 4 deletions modules/md-01-quiz.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ tutorial:

```{r setup, include=FALSE}
library(learnr)
library(tidyverse)
library(dplyr)
library(gapminder)
library(knitr)
library(gradethis)
Expand Down Expand Up @@ -88,7 +88,7 @@ question("Which chunk options would you use to hide both code and messages when
Use the gapminder dataset to create a summary table showing the average life expectancy by continent in 2007:

```{r create-table-setup}
library(tidyverse)
library(dplyr)
library(gapminder)
library(knitr)
```
Expand Down Expand Up @@ -131,7 +131,7 @@ grade_this_code()
Calculate the mean GDP per capita for Switzerland in 2007:

```{r inline-code-setup}
library(tidyverse)
library(dplyr)
library(gapminder)
```

Expand Down Expand Up @@ -164,7 +164,7 @@ grade_this_code()
Create a line plot showing the life expectancy over time for African countries with a population greater than 30 million in 2007:

```{r visualization-setup}
library(tidyverse)
library(dplyr)
library(gapminder)

# Identify African countries with population > 30 million in 2007
Expand Down