From 899f2e0877eff75310083fc1fdd17c4ebb149414 Mon Sep 17 00:00:00 2001 From: Candace Savonen Date: Thu, 1 Jun 2023 12:28:54 -0400 Subject: [PATCH 1/2] Making example scripts for google porting --- scripts/make_material_df.R | 136 ++++++++++++++++++++ scripts/port_quizzes_to_google.R | 206 +++++++++++++++++++++++++++++++ 2 files changed, 342 insertions(+) create mode 100644 scripts/make_material_df.R create mode 100644 scripts/port_quizzes_to_google.R diff --git a/scripts/make_material_df.R b/scripts/make_material_df.R new file mode 100644 index 00000000..bc922f8f --- /dev/null +++ b/scripts/make_material_df.R @@ -0,0 +1,136 @@ +#!/usr/bin/env Rscript + +# This script sets up a dataframe that has all the information for the +# quizzes, topics, materials, projects, and swirl modules and in what order they belong + +# Written by Candace Savonen June 2023 + +if (!('devtools' %in% installed.packages())) { + # install.packages("remotes", repos = "http://cran.us.r-project.org") +} + +if (!('optparse' %in% installed.packages())) { + # install.packages("optparse", repos = "http://cran.us.r-project.org") +} + +# Find .git root directory +root_dir <- rprojroot::find_root(rprojroot::has_dir(".git")) + + +library(optparse) +library(magrittr) + +option_list <- list( + optparse::make_option( + c("--repo"), + type = "character", + default = NULL, + help = "GitHub repository name, e.g. jhudsl/OTTR_Template", + ), + optparse::make_option( + c("--git_pat"), + type = "character", + default = NULL, + help = "GitHub personal access token", + ), + optparse::make_option( + c("--output_dir"), + type = "character", + default = "resources/chapt_screen_images", + help = "Output directory where the chapter's screen images should be stored", + ), + optparse::make_option( + c("--base_url"), + type = "character", + default = NULL, + help = "Output directory where the chapter's screen images should be stored", + ) +) + +# Read the arguments passed +opt_parser <- optparse::OptionParser(option_list = option_list) +opt <- optparse::parse_args(opt_parser) + + +# Establish output folder +output_folder <- file.path(opt$output_dir) +if (!dir.exists(output_folder)) { + dir.create(output_folder, recursive = TRUE) +} + +# Retrieve base_url for github pages if it has not been specified +if (is.null(opt$base_url)) { + base_url <- cow::get_pages_url(repo_name = opt$repo, git_pat = opt$git_pat) + base_url <- gsub("/$", "", base_url) +} + + +#### Get chapters +# Retrieve chapters from what's on the github pages +retrieve_chapters <- ottrpal::get_chapters(base_url) + +## Get chapter list and urls +chapter_df <- data.frame( + retrieve_chapters, + full_urls = paste0(base_url, retrieve_chapters$url) +) + +##### Get Quizzes +quiz_dir <- file.path(root_dir, "quizzes") + +## Autogenerate Book.txt file +ottrpal::bookdown_to_book_txt(quiz_dir) + +## Read in Book.txt +book_txt <- readLines(file.path(root_dir, "manuscript", "Book.txt")) + +## Make a data frame from this +book_txt_df <- data.frame(file_names = book_txt) %>% + dplyr::mutate(type = dplyr::case_when( + grepl("quiz", file_names) ~ "quiz", + TRUE ~ "chapter")) + +# Join it to the chapter df +book_txt_df <- book_txt_df %>% + dplyr::inner_join(book_txt_df, + by = "chapt_title") + +# We need to make this column so we can link swirl modules to their chapters +chapter_df <- chapter_df %>% + dplyr::mutate(core_file_name = gsub("_quiz.md$", "", chapter_df $quiz_file)) + +#### Link swirl modules to chapters +swirl_modules <- googlesheets4::read_sheet( + "https://docs.google.com/spreadsheets/d/1b60iMYr6gtJ0X0ifcXlR4u3TSe1JaUW3uIUN9SOl14E/edit#gid=0", + sheet = "Swirl_Key") %>% + dplyr::filter(!is.na(module_name)) %>% + dplyr::select(-rowname) + +chapter_df <- chapter_df %>% + dplyr::left_join(swirl_modules, + by = c("core_file_name" = "associated_chapter")) + +### Signify which chapters are projects +chapter_df <- chapter_df %>% + dplyr::mutate(type = dplyr::case_when( + grepl("Project$", chapter_df$chapt_title) ~ "Project", + grepl(paste0(topics, collapse = "$|"), chapter_df$chapt_title) ~ "New topic", + TRUE ~ "Chapter")) + +chapter_df <- chapter_df %>% + ### Establish unit times + dplyr::mutate(unit_time = dplyr::case_when(type == "Chapter" ~ 1, + type == "Project" ~ 5, + TRUE ~ 0)) %>% + dplyr::mutate(swirl_yn = !is.na(module_name), + quiz_yn = !is.na(quiz_file), + unit_time = unit_time + swirl_yn + quiz_yn) + +googlesheets4::write_sheet( + chapter_df, + "https://docs.google.com/spreadsheets/d/14PRS2qEed3E636QsorJFkgN94ikj3SeF1AgpoC-0bo0/edit#gid=0", + sheet = "chapter_df" + ) + +# Write this to a TSV +readr::write_tsv(chapter_df, file.path(root_dir, "materials_order.tsv")) diff --git a/scripts/port_quizzes_to_google.R b/scripts/port_quizzes_to_google.R new file mode 100644 index 00000000..c00200c2 --- /dev/null +++ b/scripts/port_quizzes_to_google.R @@ -0,0 +1,206 @@ +#!/usr/bin/env Rscript + +# This script takes information about the curriculum that is set up into a dataframe by `make_material_df.R` +# and it ports all info to a Google Classroom using rgoogleclassroom + +library(magrittr) +library(lubridate) + + +# Find .git root directory +root_dir <- rprojroot::find_root(rprojroot::has_dir(".git")) + +curriculum_df <- googlesheets4::read_sheet( + "https://docs.google.com/spreadsheets/d/14PRS2qEed3E636QsorJFkgN94ikj3SeF1AgpoC-0bo0/edit#gid=971038940", + sheet = "chapter_df" +) + + +### Figure out a rough schedule +total_units <- sum(curriculum_df$unit_time) + +### 5 days a week * 15 weeks +number_of_days <- 5*15 + +units_per_day <- round(total_units/number_of_days) + +### Set up rough schedule +start_date <- ymd("2023-07-03") +due_date_time <- hms("11:59:59") + +dist_from_tues <- 3 - wday(start_date) +dist_from_thurs <- 5 - wday(start_date) + +# We want due dates on the same +gaps <- seq(from = 0, to = 7*15, by = 7) + +tuesdays <- start_date + dist_from_tues + days(gaps) +thursdays <- start_date + dist_from_thurs + days(gaps) + +# Make a due_date column +curriculum_df <- curriculum_df %>% + dplyr::mutate(unit_period = cumsum(unit_time)) + +sum(curriculum_df$unit_time)/length(thursdays) + +which(abs(x - your.number) == min(abs(x - your.number))) + + +###### Create a new course +devtools::load_all("../../Hutch/rgoogleclassroom") + +authorize() +owner_id <- get_owner_id() +datatrail_course <- create_course(owner_id$id, name = "DataTrail") + +#### Create topics +topic_ids <- lapply(unique(curriculum_df$topic), function(topic_name) { + topic_output <- create_topic(course_id = datatrail_course$id, + name = topic_name) + return(topic_output$topicId) +}) + +# Add topic_ids +curriculum_df <- curriculum_df %>% dplyr::inner_join( + data.frame(topic = unique(curriculum_df$topic), topic_id = unlist(topic_ids)) +) + + +#### Create materials +material_prompt <- "Read this material at this link below before taking the next quiz." + +chapter_df <- curriculum_df %>% + dplyr::filter(type == "Chapter") %>% + dplyr::select(topic_id, chapt_title, full_urls) + +material_results <- purrr::pmap(chapter_df, function(topic_id, chapt_title, full_urls) { + create_material(course_id = datatrail_course$id, + topic_id = topic_id, + title = chapt_title, + link = full_urls, + description = material_prompt) +}) + +#### Create quizzes +quiz_assignment_prompt <- "Take the attached quiz! Remember to use the same email." +quiz_description_prompt <- "Answer the following questions after having read the chapters" + +# Filter to only quizzes +quiz_df <- curriculum_df %>% + dplyr::filter(!is.na(quiz_file)) %>% + dplyr::select(quiz_file, topic_id, chapt_title) + +# Convert quizzes to correct format +dir.create("quiz_jsons", showWarnings = FALSE) + +convert_quizzes <- purrr::pmap(quiz_df[62:nrow(quiz_df), ], function(quiz_file, topic_id, chapt_title) { + ottr_quiz_to_google(course_id = datatrail_course$id, + quiz_path = file.path("quizzes", quiz_file), + form_id = "https://docs.google.com/forms/d/1eSCaKC7GDFsm60AKOWYHXK_yXCbrQbR-r2PomeKC_VY/edit", + topic_id = topic_id, + quiz_title = chapt_title, + new_name = chapt_title, + coursework_title = paste0("Quiz:", chapt_title), + due_date = "2025-5-1", + copy_from_template_quiz = TRUE, + quiz_description = quiz_description_prompt, + assignment_description = quiz_assignment_prompt, + output_path = file.path("quiz_jsons", gsub("\\.md$", ".json", quiz_file))) +}) + + +### Set up Projects +# Put together the prompts +project_prompt <- readLines(file.path(root_dir, "scripts", "prompts", "project_prompt.md")) +project_prompt <- paste0(project_prompt, sep = "\n", collapse = "") + +# Filter to only projects +project_df <- curriculum_df %>% + dplyr::filter(type == "Project") %>% + dplyr::select(topic_id, chapt_title) + +# Set up data +folders_df <- data.frame( + folder_name = + c( + "01_Forming_Questions", + "02_Getting_Data", + "03_Cleaning_Data", + "04_Plotting_Data", + "05_Getting_Stats", + "06_Sharing_Results" + ), + rmd_name = c( + "first_project.Rmd", + "leanpub_project.Rmd", + "countries_project.Rmd", + "airbnb_project.Rmd", + "countries_stats_project.Rmd", + "data_project_final.Rmd" + ) +) + +# Add the folder and Rmd names +project_df <- dplyr::bind_cols(project_df, folders_df) + +create_projects <- purrr::pmap(project_df, function(topic_id, chapt_title, folder_name, rmd_name) { + + # Fill in the blanks + project_description <- stringr::str_replace_all( project_prompt, "\\{FOLDER_NAME\\}", folder_name) + project_description <- stringr::str_replace_all(project_description, "\\{RMARKDOWN_NAME\\}", rmd_name) + + result <- create_coursework(course_id = datatrail_course$id, + topic_id = topic_id, + title = paste("Project:", folder_name), + due_date = "2025-12-1", + description = project_description, + link = "https://posit.cloud/spaces/3919/content/4475631" + ) + return(result) +}) + + +#### Set up Swirl + +# Put together the prompts +swirl_prompt <- readLines(file.path(root_dir, "scripts", "prompts", "swirl_prompt.md")) +swirl_prompt <- paste0(swirl_prompt, sep = "\n", collapse = "") + +# Filter to only projects +swirl_df <- curriculum_df %>% + dplyr::filter(!is.na(module_name)) %>% + dplyr::select(topic_id, module_name, topic) + +module_df <- data.frame(modules = c( + "DataTrail_01_Forming_Questions", + "DataTrail_02_Getting_Data", + "DataTrail_03_Cleaning_Data", + "DataTrail_04_Plotting_Data", + "DataTrail_05_Getting_Stats", + "DataTrail_06_Sharing_Results")) %>% + dplyr::mutate(topic = stringr::word(modules, sep = "_", start = -2, end = -1), + topic = gsub("_", " ", topic), + # I have to do this stupid thing because the topic is called 'Stats' and the module is called 'Statistics' + topic = gsub("Stats$", "Statistics", topic)) + +# Join the module names to the swirl_df +swirl_df <- dplyr::left_join(swirl_df, module_df, by = "topic") %>% + dplyr::select(-topic) + + +# Create the swirl assignments +create_swirls <- purrr::pmap(swirl_df, function(topic_id, module_name, modules) { + + # Fill in the blanks + swirl_description <- stringr::str_replace_all(swirl_prompt, "\\{LESSON_NAME\\}", module_name) + swirl_description <- stringr::str_replace_all(swirl_description, "\\{COURSE_NAME\\}", modules) + + result <- create_coursework(course_id = datatrail_course$id, + topic_id = topic_id, + title = paste("Swirl:", gsub("_", " ", module_name)), + due_date = "2025-12-1", + description = swirl_description, + link = "https://posit.cloud/spaces/3919/content/4475631" + ) + return(result) +}) From 86b6b17e1137787c8770652185bd5ace54409e57 Mon Sep 17 00:00:00 2001 From: Candace Savonen Date: Thu, 1 Jun 2023 13:01:54 -0400 Subject: [PATCH 2/2] Very rough working example --- manuscript/Book.txt | 3 +- quiz_jsons/quiz_ch1.json | 1 + resources/course_units.tsv | 7 ++ scripts/make_material_df.R | 79 ++++++++----------- ...to_google.R => port_to_google_classroom.R} | 0 5 files changed, 42 insertions(+), 48 deletions(-) create mode 100644 quiz_jsons/quiz_ch1.json create mode 100644 resources/course_units.tsv rename scripts/{port_quizzes_to_google.R => port_to_google_classroom.R} (100%) diff --git a/manuscript/Book.txt b/manuscript/Book.txt index 6576cbb6..c0552c97 100644 --- a/manuscript/Book.txt +++ b/manuscript/Book.txt @@ -1,5 +1,6 @@ 1-Introduction.md +quiz_ch1.md 2-A-new-chapter.md 3-References.md -About-this-Course.md About-the-Authors.md +About-this-Course.md diff --git a/quiz_jsons/quiz_ch1.json b/quiz_jsons/quiz_ch1.json new file mode 100644 index 00000000..1211c9a5 --- /dev/null +++ b/quiz_jsons/quiz_ch1.json @@ -0,0 +1 @@ +{"question_info_df":[{"question":"First question to ask goes here. (Note- you need a question mark at end like this. Just one is required if using a question mark in your question field)?","question_type":"multiple_choice","shuffle_opt":true,"correct_answer":1,"_row":"First question to ask goes here. (Note- you need a question mark at end like this. Just one is required if using a question mark in your question field)?"},{"question":"Question example with just a question mark?","question_type":"multiple_choice","shuffle_opt":true,"correct_answer":1,"_row":"Question example with just a question mark?"},{"question":"Second question to ask goes here?","question_type":"multiple_choice","shuffle_opt":true,"correct_answer":1,"_row":"Second question to ask goes here?"},{"question":"A more complicated example. Note the question mark at the end of the options! Which of the following are correct","question_type":"multiple_choice","shuffle_opt":true,"correct_answer":1,"_row":"A more complicated example. Note the question mark at the end of the options! Which of the following are correct"},{"question":"A question in which the order of choices is important?","question_type":"multiple_choice","shuffle_opt":false,"correct_answer":4,"_row":"A question in which the order of choices is important?"}],"choice_vectors":[["One correct answer here marked with a \"C\"","Mandatory incorrect answers have an \"m\"","A second mandatory incorrect answer","An optional incorrect answer here marked with an \"o\"","A second optional incorrect answer here"],["One correct answer here marked with a \"C\"","Mandatory incorrect answers have an \"m\"","A second mandatory incorrect answer","An optional incorrect answer here marked with an \"o\"","A second optional incorrect answer here"],["A correct answer here!","Mandatory incorrect answers have an \"m\"","A second mandatory incorrect answer","An optional incorrect answer here marked with an \"o\"","A second optional incorrect answer here"],["All of the examples listed except 5","1, 3, and 5","1, 2, and 3","All of the examples except 1 and 5","All of the examples listed"],["The possible responses should be labeled a, b, c, etc","The correct answer has a capital letter, in this case \"D\" is correct","both a and b","All of the above will always be last choice in this question"]]} diff --git a/resources/course_units.tsv b/resources/course_units.tsv new file mode 100644 index 00000000..8fa606f3 --- /dev/null +++ b/resources/course_units.tsv @@ -0,0 +1,7 @@ +md_name type url chapt_title full_urls +1-Introduction.md chapter introduction.html 1 Introduction https://jhudatascience.org/OTTR_Template/introduction.html +quiz_ch1.md quiz NA NA NA +2-A-new-chapter.md chapter a-new-chapter.html 2 A new chapter https://jhudatascience.org/OTTR_Template/a-new-chapter.html +3-References.md chapter references.html 3 References https://jhudatascience.org/OTTR_Template/references.html +About-the-Authors.md chapter about-the-authors.html About the Authors https://jhudatascience.org/OTTR_Template/about-the-authors.html +About-this-Course.md chapter index.html About this Course https://jhudatascience.org/OTTR_Template/index.html diff --git a/scripts/make_material_df.R b/scripts/make_material_df.R index bc922f8f..f5bd0db2 100644 --- a/scripts/make_material_df.R +++ b/scripts/make_material_df.R @@ -21,6 +21,18 @@ library(optparse) library(magrittr) option_list <- list( + optparse::make_option( + c("--owner_id"), + type = "character", + default = NULL, + help = "Owner id", + ), + optparse::make_option( + c("--course_name"), + type = "character", + default = NULL, + help = "Course name for this", + ), optparse::make_option( c("--repo"), type = "character", @@ -44,6 +56,11 @@ option_list <- list( type = "character", default = NULL, help = "Output directory where the chapter's screen images should be stored", + ), + optparse::make_option( + c("--make_book_txt"), + action = "store_true", + help = "Should book.txt file be made freshly?", ) ) @@ -72,65 +89,33 @@ retrieve_chapters <- ottrpal::get_chapters(base_url) ## Get chapter list and urls chapter_df <- data.frame( retrieve_chapters, - full_urls = paste0(base_url, retrieve_chapters$url) + full_urls = paste0(base_url, retrieve_chapters$url), + md_name = paste0(gsub(" ", "-", retrieve_chapters$chapt_title), ".md") ) ##### Get Quizzes -quiz_dir <- file.path(root_dir, "quizzes") +quiz_files <- list.files(file.path(root_dir, "quizzes"), pattern = ".md") -## Autogenerate Book.txt file -ottrpal::bookdown_to_book_txt(quiz_dir) +if (opt$make_book_txt) { + # Make book.txt fresh + ottrpal::bookdown_to_book_txt(md_files = list.files(file.path(root_dir, "manuscript"), pattern = ".md")) +} ## Read in Book.txt book_txt <- readLines(file.path(root_dir, "manuscript", "Book.txt")) ## Make a data frame from this -book_txt_df <- data.frame(file_names = book_txt) %>% +book_txt_df <- data.frame(md_name = book_txt) %>% dplyr::mutate(type = dplyr::case_when( - grepl("quiz", file_names) ~ "quiz", + grepl("quiz", md_name) ~ "quiz", TRUE ~ "chapter")) # Join it to the chapter df book_txt_df <- book_txt_df %>% - dplyr::inner_join(book_txt_df, - by = "chapt_title") - -# We need to make this column so we can link swirl modules to their chapters -chapter_df <- chapter_df %>% - dplyr::mutate(core_file_name = gsub("_quiz.md$", "", chapter_df $quiz_file)) - -#### Link swirl modules to chapters -swirl_modules <- googlesheets4::read_sheet( - "https://docs.google.com/spreadsheets/d/1b60iMYr6gtJ0X0ifcXlR4u3TSe1JaUW3uIUN9SOl14E/edit#gid=0", - sheet = "Swirl_Key") %>% - dplyr::filter(!is.na(module_name)) %>% - dplyr::select(-rowname) - -chapter_df <- chapter_df %>% - dplyr::left_join(swirl_modules, - by = c("core_file_name" = "associated_chapter")) - -### Signify which chapters are projects -chapter_df <- chapter_df %>% - dplyr::mutate(type = dplyr::case_when( - grepl("Project$", chapter_df$chapt_title) ~ "Project", - grepl(paste0(topics, collapse = "$|"), chapter_df$chapt_title) ~ "New topic", - TRUE ~ "Chapter")) - -chapter_df <- chapter_df %>% - ### Establish unit times - dplyr::mutate(unit_time = dplyr::case_when(type == "Chapter" ~ 1, - type == "Project" ~ 5, - TRUE ~ 0)) %>% - dplyr::mutate(swirl_yn = !is.na(module_name), - quiz_yn = !is.na(quiz_file), - unit_time = unit_time + swirl_yn + quiz_yn) - -googlesheets4::write_sheet( - chapter_df, - "https://docs.google.com/spreadsheets/d/14PRS2qEed3E636QsorJFkgN94ikj3SeF1AgpoC-0bo0/edit#gid=0", - sheet = "chapter_df" - ) + dplyr::left_join(chapter_df, + by = "md_name") + +## Write this file. +### You may want to add due dates to this file manually. +readr::write_tsv(book_txt_df, file.path("resources", "course_units.tsv")) -# Write this to a TSV -readr::write_tsv(chapter_df, file.path(root_dir, "materials_order.tsv")) diff --git a/scripts/port_quizzes_to_google.R b/scripts/port_to_google_classroom.R similarity index 100% rename from scripts/port_quizzes_to_google.R rename to scripts/port_to_google_classroom.R