├── lib ├── tasks │ ├── .gitkeep │ ├── demo-images │ │ ├── men.jpg │ │ └── women.jpg │ ├── screenshots.rake │ └── factory_bot_lint.rake ├── assets │ ├── .gitkeep │ ├── dockerfiles │ │ ├── java-grader.Dockerfile │ │ └── racket-grader.Dockerfile │ └── python-config.json ├── audit.rb ├── array_split.rb ├── fake_upload.rb ├── devise │ └── strategies │ │ └── debug_login.rb └── json_parser.rb ├── public ├── favicon.ico ├── robots.txt └── 422.html ├── .ruby-version ├── app ├── mailers │ ├── .gitkeep │ └── notification_mailer.rb ├── models │ ├── .gitkeep │ ├── team_user.rb │ ├── reg_request_section.rb │ ├── submission_enabled_toggle.rb │ ├── submission_view.rb │ ├── assignments │ │ └── files.rb │ ├── user_submission.rb │ ├── grading_conflict_audit.rb │ ├── registration_section.rb │ ├── used_sub.rb │ ├── grader_allocation.rb │ ├── sandbox.rb │ ├── review_feedback.rb │ ├── application_record.rb │ ├── submissions │ │ └── files_sub.rb │ ├── bucket.rb │ ├── codereview_matching.rb │ ├── individual_extension.rb │ ├── lateness_configs │ │ ├── late_per_hour_config.rb │ │ └── late_per_day_config.rb │ ├── settings.rb │ └── term.rb ├── helpers │ ├── main_helper.rb │ ├── terms_helper.rb │ ├── users_helper.rb │ ├── buckets_helper.rb │ ├── sandboxes_helper.rb │ ├── settings_helper.rb │ ├── reg_requests_helper.rb │ ├── registrations_helper.rb │ ├── courses_helper.rb │ ├── teams_helper.rb │ ├── grader_allocations_helper.rb │ ├── link_to_function_helper.rb │ ├── spinner_helper.rb │ └── exams_schema.yaml ├── assets │ ├── stylesheets │ │ ├── assignment.scss │ │ ├── actions.scss │ │ ├── page-header.scss │ │ ├── sandboxes.scss │ │ ├── bootstrap-treeview.scss │ │ ├── footer.scss │ │ ├── button-file-inputs.scss │ │ ├── questions.scss │ │ └── bootstrap-overrides.scss │ ├── images │ │ ├── c-mark.png │ │ ├── rails.png │ │ ├── dolphin.png │ │ ├── null-mark.png │ │ ├── sad-mark.png │ │ ├── site-icon.png │ │ ├── texture.png │ │ ├── wait-mark.gif │ │ ├── check-mark.png │ │ ├── check-plus.png │ │ ├── cminus-mark.png │ │ ├── crash-mark.png │ │ ├── cross-mark.png │ │ ├── flammarion.png │ │ ├── silhouette.jpg │ │ ├── failure-option.jpg │ │ └── question-mark.png │ └── javascripts │ │ ├── nicEditorIcons.gif │ │ ├── 0startup.js.coffee │ │ ├── sandboxes.coffee │ │ ├── settings.js.coffee │ │ ├── main.js.coffee │ │ ├── terms.js.coffee │ │ ├── reg_requests.js.coffee │ │ ├── submissions.js │ │ ├── grades.js │ │ └── teamsets.js.coffee ├── jobs │ ├── application_job.rb │ └── grade_submission_job.rb ├── views │ ├── teamsets │ │ ├── edit.html.erb │ │ ├── investigate.html.erb │ │ ├── _diff_teamsets.html.erb │ │ └── _table.html.erb │ ├── users │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ └── _form.js.erb │ ├── reg_requests │ │ └── new.html.erb │ ├── courses │ │ ├── edit.html.erb │ │ ├── new.html.erb │ │ ├── public.html.erb │ │ ├── index.html.erb │ │ ├── _grading_due.html.erb │ │ ├── _section.html.erb │ │ └── _table.html.erb │ ├── grades │ │ ├── edit_ExamGrader.html.erb │ │ ├── edit_exam_curved_grades.html.erb │ │ ├── _checker_import_schema.html.erb │ │ ├── _junit_import_schema.html.erb │ │ ├── _tap_style_import_schema.html.erb │ │ ├── _orca_output.js.erb │ │ ├── _grades_file_picker.html.erb │ │ ├── _exam_export_schema.html.erb │ │ ├── _show_xunit_tests.html.erb │ │ └── _exam_import_schema.html.erb │ ├── sandboxes │ │ ├── new.html.erb │ │ ├── edit.html.erb │ │ ├── show.html.erb │ │ ├── _form.html.erb │ │ └── index.html.erb │ ├── terms │ │ ├── new.html.erb │ │ ├── edit.html.erb │ │ ├── show.html.erb │ │ ├── _table.html.erb │ │ ├── index.html.erb │ │ └── _form.html.erb │ ├── assignments │ │ ├── new.html.erb │ │ ├── show_user_exam.html.erb │ │ ├── edit.html.erb │ │ ├── _interlock.html.erb │ │ ├── _due_date.html.erb │ │ ├── _submission_enabled_toggles.html.erb │ │ ├── show_files.html.erb │ │ ├── show_user_files.html.erb │ │ ├── show_user_questions.html.erb │ │ ├── _assignment_file_picker.html.erb │ │ └── _form.html.erb │ ├── grading_conflicts │ │ └── new.html.erb │ ├── layouts │ │ ├── _flash.html.erb │ │ ├── course.html.erb │ │ ├── _footer.html.erb │ │ ├── _spinner.html.erb │ │ └── errors.html.erb │ ├── main │ │ ├── about.html.erb │ │ └── status.html.erb │ ├── submissions │ │ ├── show_questions.html.erb │ │ ├── show_codereview.html.erb │ │ ├── _enter_answer_text.html.erb │ │ ├── _show_answer_text.html.erb │ │ ├── _show_answer_code.html.erb │ │ ├── _enter_answer_code.html.erb │ │ ├── _enter_answer_numeric.html.erb │ │ ├── details_codereview.html.erb │ │ ├── _show_answer_numeric.html.erb │ │ ├── _enter_answer_true_false.html.erb │ │ ├── _enter_answer_yes_no.html.erb │ │ ├── _show_answer_true_false.html.erb │ │ ├── _show_answer_yes_no.html.erb │ │ ├── _scoring_common.js.erb │ │ ├── _code_tag.html.erb │ │ └── show_files.html.erb │ ├── graders │ │ ├── _ping_orca.js.erb │ │ ├── _manual_grader.html.erb │ │ ├── _junit_grader.js.erb │ │ ├── _schema_grader.js.erb │ │ ├── _java_style_grader.html.erb │ │ ├── _racket_style_grader.html.erb │ │ ├── _python_style_grader.html.erb │ │ ├── build_log.html.erb │ │ └── _grader_file_picker.html.erb │ ├── errors │ │ ├── not_found.html.erb │ │ └── internal_server_error.html.erb │ ├── notification_mailer │ │ └── got_reg_request.text.erb │ ├── grader_allocations │ │ └── stats.html.erb │ ├── registrations │ │ └── index.html.erb │ ├── reviews │ │ └── show.html.erb │ └── teams │ │ └── show.html.erb ├── controllers │ ├── api │ │ ├── users_controller.rb │ │ ├── graders_controller.rb │ │ ├── courses_controller.rb │ │ └── registrations_controller.rb │ ├── settings_controller.rb │ ├── oauth │ │ ├── authorizations_controller.rb │ │ └── applications_controller.rb │ ├── graders_controller.rb │ ├── files_controller.rb │ ├── errors_controller.rb │ └── teams_controller.rb └── javascript │ └── packs │ └── application.js ├── test ├── fixtures │ ├── .gitkeep │ ├── files │ │ ├── image.jpg │ │ ├── PDFs │ │ │ └── sample.pdf │ │ ├── junit-example.zip │ │ ├── Archive-no-perms.zip │ │ ├── HelloWorld │ │ │ ├── HelloWorld.tgz │ │ │ ├── HelloWorld.zip │ │ │ ├── HelloWorld.tar.gz │ │ │ ├── HelloWorld-john.tar.gz │ │ │ ├── HelloWorld-assign.tar.gz │ │ │ └── HelloWorld-grading.tar.gz │ │ ├── students.csv │ │ ├── HelloSingle │ │ │ ├── HelloSingle-grading.tar.gz │ │ │ └── hello.c │ │ ├── TestScript │ │ │ ├── hello.c │ │ │ └── test.pl │ │ ├── test-exam.yaml │ │ ├── fundies-config.json │ │ ├── Exam │ │ │ ├── exam-v2-error-questions.yaml │ │ │ ├── exam.yaml │ │ │ ├── exam-v2-correct.yaml │ │ │ ├── exam-v2-error-weight.yaml │ │ │ └── exam-incorrect-format.yaml │ │ └── Exam-EC │ │ │ └── exam.yaml │ ├── sandboxes.yml │ ├── grader_configs.yml │ └── inline_comments.yml ├── models │ ├── .gitkeep │ ├── helpers │ │ ├── main_helper_test.rb │ │ ├── terms_helper_test.rb │ │ ├── users_helper_test.rb │ │ ├── answers_helper_test.rb │ │ ├── courses_helper_test.rb │ │ ├── lessons_helper_test.rb │ │ ├── assignments_helper_test.rb │ │ ├── reg_requests_helper_test.rb │ │ ├── submissions_helper_test.rb │ │ └── registrations_helper_test.rb │ ├── bucket_test.rb │ ├── reg_request_test.rb │ ├── team_user_test.rb │ ├── grader_config_test.rb │ ├── registration_test.rb │ ├── inline_comment_test.rb │ ├── user_test.rb │ ├── team_test.rb │ └── sandbox_test.rb ├── integration │ ├── .gitkeep │ ├── login_redirect.rb │ ├── sanity_test.rb │ ├── show_hide_username_test.rb │ └── add_user_test.rb ├── jobs │ └── grade_submission_job_test.rb ├── performance │ └── browsing_test.rb └── controllers │ └── settings_controller_test.rb ├── vendor ├── plugins │ └── .gitkeep └── assets │ ├── javascripts │ └── .gitkeep │ └── stylesheets │ └── .gitkeep ├── .browserslistrc ├── bin ├── start.sh ├── rake ├── bundle ├── rails ├── delayed_job ├── webpack ├── webpack-dev-server ├── yarn ├── update └── setup ├── config ├── webpack │ ├── environment.js │ ├── test.js │ ├── production.js │ └── development.js ├── spring.rb ├── orca.yml ├── initializers │ ├── session_store.rb │ ├── action_mailer.rb │ ├── application_controller_renderer.rb │ ├── cookies_serializer.rb │ ├── mime_types.rb │ ├── permissions_policy.rb │ ├── filter_parameter_logging.rb │ ├── assets.rb │ ├── wrap_parameters.rb │ ├── backtrace_silencers.rb │ ├── inflections.rb │ ├── secret_token.rb │ ├── backburner.rb │ └── content_security_policy.rb ├── boot.rb ├── cable.yml ├── environment.rb ├── ldap.yml ├── schedule.rb ├── locales │ └── en.yml ├── secrets.yml └── storage.yml ├── sandbox ├── examples │ ├── demo │ │ ├── hello.tar.gz │ │ ├── demo-grading.tar.gz │ │ ├── hello │ │ │ ├── Makefile │ │ │ ├── hello.c │ │ │ └── test.pl │ │ └── _grading │ │ │ ├── Makefile │ │ │ ├── test.pl │ │ │ └── grade.rb │ ├── extra │ │ ├── hello.tar.gz │ │ ├── extra-grading.tar.gz │ │ ├── hello │ │ │ ├── Makefile │ │ │ ├── hello.c │ │ │ └── test.pl │ │ ├── _grading │ │ │ ├── Makefile │ │ │ └── grade.rb │ │ └── test.pl │ ├── sleep │ │ ├── sleep.tar.gz │ │ └── _grading │ │ │ └── grade.rb │ └── check.rb ├── start-container.sh └── driver.pl.erb ├── Passengerfile.json ├── db ├── migrate │ ├── 20170506003128_rename_sections.rb │ ├── 20170811161131_grader_extra_upload.rb │ ├── 20180609014742_make_terms_unique.rb │ ├── 20170702154328_require_show_in_lists.rb │ ├── 20180924222312_index_used_subs_on_assignment_id.rb │ ├── 20180301224622_index_inline_comments_on_severity.rb │ ├── 20170705045007_require_assignment_grader_order.rb │ ├── 20190303232336_add_index_to_registrations.rb │ ├── 20240725235205_add_orca_status_to_grader.rb │ ├── 20180211180103_index_inline_comments.rb │ ├── 20201026221030_add_user_index_to_inline_comments.rb │ ├── 20170714150844_rename_grader_allocation_grader.rb │ ├── 20170602143559_rename_team_sets.rb │ ├── 20170827140156_index_codereview_matchings_on_assignment_id.rb │ ├── 20171211221349_add_extra_credit_fields.rb │ ├── 20170504141136_auxiliary.rb │ ├── 20170726215845_change_interlock_constraint_type.rb │ ├── 20170726231116_create_submission_views.rb │ ├── 20170727203859_create_codereview_matchings.rb │ ├── 20180302211623_add_bonus_inline_comment_type.rb │ ├── 20181004011746_create_submission_enabled_toggles.rb │ ├── 20201007115016_add_timestamps_to_submission_views.rb │ ├── 20171103172019_create_team_requests.rb │ ├── 20171218170821_create_individual_extensions.rb │ ├── 20170805001646_drop_delayed_jobs.rb │ ├── 20171121152503_make_crns_nonunique.rb │ ├── 20170718000106_create_review_feedbacks.rb │ ├── 20171210143444_make_matchings_unique.rb │ ├── 20180801182418_add_assignment_to_uploads.rb │ ├── 20170710141402_rename_assignment_types.rb │ ├── 20220224011641_create_grading_conflicts.rb │ ├── 20170713123707_rename_submission_types.rb │ ├── 20220426161610_change_primary_key_to_bigint.rb │ ├── 20170531183453_make_teamsets_mandatory.rb │ ├── 20240828125219_change_orca_status_type.rb │ └── 20180608211724_schematize_terms.rb └── simple-data.rb ├── config.ru ├── postcss.config.js ├── script ├── rails ├── getip.pl ├── update-grade-caches ├── parse-test-output ├── regrade-sub ├── clear_jobs ├── mark-find-submissions ├── assign-tarball ├── assignment_grep.rb ├── regrade-whole-assignment ├── resend-auth-links ├── dredge.pl ├── fix-quoted-names ├── dump_comments.rb ├── bulk-user-upload └── cleanup-db ├── package.json └── flake.lock /lib/tasks/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.0.2 2 | -------------------------------------------------------------------------------- /app/mailers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/plugins/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | defaults 2 | -------------------------------------------------------------------------------- /test/integration/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/helpers/main_helper.rb: -------------------------------------------------------------------------------- 1 | module MainHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/terms_helper.rb: -------------------------------------------------------------------------------- 1 | module TermsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/users_helper.rb: -------------------------------------------------------------------------------- 1 | module UsersHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/buckets_helper.rb: -------------------------------------------------------------------------------- 1 | module BucketsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/assets/stylesheets/assignment.scss: -------------------------------------------------------------------------------- 1 | /* assignments.scss */ 2 | -------------------------------------------------------------------------------- /app/helpers/sandboxes_helper.rb: -------------------------------------------------------------------------------- 1 | module SandboxesHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/settings_helper.rb: -------------------------------------------------------------------------------- 1 | module SettingsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/reg_requests_helper.rb: -------------------------------------------------------------------------------- 1 | module RegRequestsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/registrations_helper.rb: -------------------------------------------------------------------------------- 1 | module RegistrationsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | end 3 | -------------------------------------------------------------------------------- /bin/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ~/.rvm/bin/rvm-shell -c 'bundle exec rake backburner:work' 3 | -------------------------------------------------------------------------------- /app/views/teamsets/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "Edit teamsets" %> 2 | <%= render 'form' %> 3 | -------------------------------------------------------------------------------- /app/assets/images/c-mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/app/assets/images/c-mark.png -------------------------------------------------------------------------------- /app/assets/images/rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/app/assets/images/rails.png -------------------------------------------------------------------------------- /app/views/users/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "Edit User: #{@user.name}" %> 2 | 3 | <%= render 'form' %> 4 | -------------------------------------------------------------------------------- /app/assets/images/dolphin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/app/assets/images/dolphin.png -------------------------------------------------------------------------------- /app/assets/images/null-mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/app/assets/images/null-mark.png -------------------------------------------------------------------------------- /app/assets/images/sad-mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/app/assets/images/sad-mark.png -------------------------------------------------------------------------------- /app/assets/images/site-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/app/assets/images/site-icon.png -------------------------------------------------------------------------------- /app/assets/images/texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/app/assets/images/texture.png -------------------------------------------------------------------------------- /app/assets/images/wait-mark.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/app/assets/images/wait-mark.gif -------------------------------------------------------------------------------- /app/models/team_user.rb: -------------------------------------------------------------------------------- 1 | class TeamUser < ApplicationRecord 2 | belongs_to :user 3 | belongs_to :team 4 | end 5 | -------------------------------------------------------------------------------- /app/views/reg_requests/new.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "New Registration Request" %> 2 | 3 | <%= render 'form' %> 4 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "../config/boot" 3 | require "rake" 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /lib/tasks/demo-images/men.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/lib/tasks/demo-images/men.jpg -------------------------------------------------------------------------------- /lib/tasks/demo-images/women.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/lib/tasks/demo-images/women.jpg -------------------------------------------------------------------------------- /test/fixtures/files/image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/test/fixtures/files/image.jpg -------------------------------------------------------------------------------- /app/assets/images/check-mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/app/assets/images/check-mark.png -------------------------------------------------------------------------------- /app/assets/images/check-plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/app/assets/images/check-plus.png -------------------------------------------------------------------------------- /app/assets/images/cminus-mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/app/assets/images/cminus-mark.png -------------------------------------------------------------------------------- /app/assets/images/crash-mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/app/assets/images/crash-mark.png -------------------------------------------------------------------------------- /app/assets/images/cross-mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/app/assets/images/cross-mark.png -------------------------------------------------------------------------------- /app/assets/images/flammarion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/app/assets/images/flammarion.png -------------------------------------------------------------------------------- /app/assets/images/silhouette.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/app/assets/images/silhouette.jpg -------------------------------------------------------------------------------- /app/helpers/courses_helper.rb: -------------------------------------------------------------------------------- 1 | module CoursesHelper 2 | def num_to_col(n) 3 | ("A".."Z").to_a[n] 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/views/courses/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "#{@course.name} - Edit Course" %> 2 | 3 | <%= render 'form' %> 4 | 5 | -------------------------------------------------------------------------------- /app/views/grades/edit_ExamGrader.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = @grader.display_type %> 2 | 3 | <%= render "exam_form" %> 4 | -------------------------------------------------------------------------------- /config/webpack/environment.js: -------------------------------------------------------------------------------- 1 | const { environment } = require('@rails/webpacker') 2 | 3 | module.exports = environment 4 | -------------------------------------------------------------------------------- /app/assets/images/failure-option.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/app/assets/images/failure-option.jpg -------------------------------------------------------------------------------- /app/assets/images/question-mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/app/assets/images/question-mark.png -------------------------------------------------------------------------------- /sandbox/examples/demo/hello.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/sandbox/examples/demo/hello.tar.gz -------------------------------------------------------------------------------- /sandbox/examples/extra/hello.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/sandbox/examples/extra/hello.tar.gz -------------------------------------------------------------------------------- /sandbox/examples/sleep/sleep.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/sandbox/examples/sleep/sleep.tar.gz -------------------------------------------------------------------------------- /test/fixtures/files/PDFs/sample.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/test/fixtures/files/PDFs/sample.pdf -------------------------------------------------------------------------------- /test/models/helpers/main_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class MainHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/models/helpers/terms_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TermsHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/models/helpers/users_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class UsersHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /Passengerfile.json: -------------------------------------------------------------------------------- 1 | { 2 | "log_file": "log/passenger.log", 3 | "environment": "production", 4 | "min_instances": 2 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/files/junit-example.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/test/fixtures/files/junit-example.zip -------------------------------------------------------------------------------- /test/models/helpers/answers_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AnswersHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/models/helpers/courses_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class CoursesHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/models/helpers/lessons_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class LessonsHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /app/assets/javascripts/nicEditorIcons.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/app/assets/javascripts/nicEditorIcons.gif -------------------------------------------------------------------------------- /app/assets/stylesheets/actions.scss: -------------------------------------------------------------------------------- 1 | .actions { 2 | display: inline-block; 3 | float: right; 4 | position: relative; 5 | } 6 | -------------------------------------------------------------------------------- /app/views/sandboxes/new.html.erb: -------------------------------------------------------------------------------- 1 |

New Sandbox

2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Back', sandboxes_path %> 6 | -------------------------------------------------------------------------------- /app/views/terms/new.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "Add A Term" %> 2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Back', terms_path %> 6 | -------------------------------------------------------------------------------- /sandbox/examples/demo/demo-grading.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/sandbox/examples/demo/demo-grading.tar.gz -------------------------------------------------------------------------------- /test/fixtures/files/Archive-no-perms.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/test/fixtures/files/Archive-no-perms.zip -------------------------------------------------------------------------------- /sandbox/examples/extra/extra-grading.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/sandbox/examples/extra/extra-grading.tar.gz -------------------------------------------------------------------------------- /test/models/helpers/assignments_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AssignmentsHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/models/helpers/reg_requests_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class RegRequestsHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/models/helpers/submissions_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SubmissionsHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /app/models/reg_request_section.rb: -------------------------------------------------------------------------------- 1 | class RegRequestSection < ApplicationRecord 2 | belongs_to :reg_request 3 | belongs_to :section 4 | end 5 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /test/fixtures/files/HelloWorld/HelloWorld.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/test/fixtures/files/HelloWorld/HelloWorld.tgz -------------------------------------------------------------------------------- /test/fixtures/files/HelloWorld/HelloWorld.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/test/fixtures/files/HelloWorld/HelloWorld.zip -------------------------------------------------------------------------------- /test/fixtures/files/students.csv: -------------------------------------------------------------------------------- 1 | Name,Email 2 | Bob Dole,bdole@example.com 3 | Robert Henry,rhenry@example.com 4 | Your Mom,yourmom@example.com 5 | -------------------------------------------------------------------------------- /test/models/helpers/registrations_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class RegistrationsHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | %w( 2 | .ruby-version 3 | .rbenv-vars 4 | tmp/restart.txt 5 | tmp/caching-dev.txt 6 | ).each { |path| Spring.watch(path) } 7 | -------------------------------------------------------------------------------- /test/fixtures/files/HelloWorld/HelloWorld.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/test/fixtures/files/HelloWorld/HelloWorld.tar.gz -------------------------------------------------------------------------------- /app/views/assignments/new.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "New Assignment" %> 2 | 3 |

Create New Assignment

4 | 5 | <%= render 'form' %> 6 | -------------------------------------------------------------------------------- /sandbox/examples/demo/hello/Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | hello: 4 | gcc -o hello hello.c 5 | 6 | test: 7 | perl test.pl 8 | 9 | clean: 10 | rm -f hello 11 | -------------------------------------------------------------------------------- /sandbox/examples/extra/hello/Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | hello: 4 | gcc -o hello hello.c 5 | 6 | test: 7 | perl test.pl 8 | 9 | clean: 10 | rm -f hello 11 | -------------------------------------------------------------------------------- /test/fixtures/files/HelloWorld/HelloWorld-john.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/test/fixtures/files/HelloWorld/HelloWorld-john.tar.gz -------------------------------------------------------------------------------- /app/helpers/teams_helper.rb: -------------------------------------------------------------------------------- 1 | module TeamsHelper 2 | def names_for_select(users) 3 | options_for_select(users.map {|uu| [uu.name, uu.id] }) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/views/courses/new.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "New Course" %> 2 | 3 |

<%= link_to 'Back to Courses List', courses_path %>

4 | 5 | <%= render 'form' %> 6 | -------------------------------------------------------------------------------- /app/views/grading_conflicts/new.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "New Grading Conflict" %> 2 | <% cur_reg = current_user.registration_for(@course) %> 3 | <%= render 'form' %> -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require_relative "../config/boot" 4 | require "rails/commands" 5 | -------------------------------------------------------------------------------- /sandbox/examples/demo/_grading/Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | hello: 4 | gcc -o hello hello.c 5 | 6 | test: 7 | @perl test.pl 8 | 9 | clean: 10 | rm -f hello 11 | -------------------------------------------------------------------------------- /sandbox/examples/extra/_grading/Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | hello: 4 | gcc -o hello hello.c 5 | 6 | test: 7 | @perl test.pl 8 | 9 | clean: 10 | rm -f hello 11 | -------------------------------------------------------------------------------- /test/fixtures/files/HelloWorld/HelloWorld-assign.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/test/fixtures/files/HelloWorld/HelloWorld-assign.tar.gz -------------------------------------------------------------------------------- /test/fixtures/files/HelloWorld/HelloWorld-grading.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/test/fixtures/files/HelloWorld/HelloWorld-grading.tar.gz -------------------------------------------------------------------------------- /app/assets/javascripts/0startup.js.coffee: -------------------------------------------------------------------------------- 1 | 2 | window.run_on_page = (name, func) -> 3 | $(() -> 4 | if name == window.current_page_name 5 | func() 6 | ) 7 | -------------------------------------------------------------------------------- /test/fixtures/files/HelloSingle/HelloSingle-grading.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeGrade/bottlenose/HEAD/test/fixtures/files/HelloSingle/HelloSingle-grading.tar.gz -------------------------------------------------------------------------------- /app/views/sandboxes/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Editing Sandbox

2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Show', @sandbox %> | 6 | <%= link_to 'Back', sandboxes_path %> 7 | -------------------------------------------------------------------------------- /config/orca.yml: -------------------------------------------------------------------------------- 1 | queue: 2 | delay_window_mins: 15 3 | delay_base_mins: 1 4 | 5 | site_url: 6 | development: http://localhost:4000 7 | test: http://orca.com.invalid 8 | -------------------------------------------------------------------------------- /test/models/bucket_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class GradeTypeTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /app/jobs/grade_submission_job.rb: -------------------------------------------------------------------------------- 1 | class GradeSubmissionJob < ApplicationJob 2 | queue_as :default 3 | 4 | def perform(*args) 5 | # Do something later 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: '_bn_session' 4 | -------------------------------------------------------------------------------- /test/fixtures/files/TestScript/hello.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | int 5 | main(int argc, char* argv[]) 6 | { 7 | printf("Hello, World!\n"); 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /test/models/reg_request_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class RegRequestTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/models/team_user_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TeamUserTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /app/models/submission_enabled_toggle.rb: -------------------------------------------------------------------------------- 1 | class SubmissionEnabledToggle < ApplicationRecord 2 | belongs_to :assignment 3 | belongs_to :section 4 | belongs_to :interlock 5 | end 6 | -------------------------------------------------------------------------------- /app/views/terms/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "Edit Term" %> 2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Show', term_path(@term) %> | 6 | <%= link_to 'Back', terms_path %> 7 | -------------------------------------------------------------------------------- /db/migrate/20170506003128_rename_sections.rb: -------------------------------------------------------------------------------- 1 | class RenameSections < ActiveRecord::Migration[5.1] 2 | def change 3 | rename_table :course_sections, :sections 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /sandbox/examples/demo/hello/hello.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | int 5 | main(int _argc, char* _argv[]) 6 | { 7 | printf("Hi!\n"); 8 | return 0; 9 | } 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/fixtures/files/HelloSingle/hello.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | int 5 | main(int argc, char* argv[]) 6 | { 7 | printf("Hello, World!\n"); 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /test/models/grader_config_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class GraderConfigTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/models/registration_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class RegistrationTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative "config/environment" 4 | 5 | run Rails.application 6 | Rails.application.load_server 7 | -------------------------------------------------------------------------------- /lib/assets/dockerfiles/java-grader.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM orca-grader-base:latest 2 | 3 | RUN ["apt", "update"] 4 | RUN ["apt", "install", "openjdk-11-jdk-headless", "-y"] 5 | 6 | USER orca-grader -------------------------------------------------------------------------------- /sandbox/examples/extra/hello/hello.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | int 5 | main(int _argc, char* _argv[]) 6 | { 7 | printf("Hi!\n"); 8 | return 0; 9 | } 10 | 11 | 12 | -------------------------------------------------------------------------------- /sandbox/start-container.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | lxc launch bn-base bn-demo -e \ 3 | -c "limits.cpu.allowance=20ms/60ms" \ 4 | -c "limits.memory=1024MB" \ 5 | -c "limits.processes=64" 6 | -------------------------------------------------------------------------------- /test/models/inline_comment_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class InlineCommentTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /app/controllers/api/users_controller.rb: -------------------------------------------------------------------------------- 1 | module Api 2 | class UsersController < ApiController 3 | def me 4 | render json: serialize_user(current_user) 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/views/grades/edit_exam_curved_grades.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = @grader.display_type %> 2 | 3 |

Enter exam curved grades: <%= @assignment.name %>

4 | <%= render "exam_curve_form" %> 5 | -------------------------------------------------------------------------------- /config/webpack/test.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development' 2 | 3 | const environment = require('./environment') 4 | 5 | module.exports = environment.toWebpackConfig() 6 | -------------------------------------------------------------------------------- /db/migrate/20170811161131_grader_extra_upload.rb: -------------------------------------------------------------------------------- 1 | class GraderExtraUpload < ActiveRecord::Migration[5.1] 2 | def change 3 | add_column :graders, :extra_upload_id, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20180609014742_make_terms_unique.rb: -------------------------------------------------------------------------------- 1 | class MakeTermsUnique < ActiveRecord::Migration[5.2] 2 | def change 3 | add_index :terms, [:semester, :year], unique: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /test/jobs/grade_submission_job_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class GradeSubmissionJobTest < ActiveJob::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /config/webpack/production.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'production' 2 | 3 | const environment = require('./environment') 4 | 5 | module.exports = environment.toWebpackConfig() 6 | -------------------------------------------------------------------------------- /app/views/layouts/_flash.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | <%= bootstrap_flash %> 5 |
6 |
7 |
8 | -------------------------------------------------------------------------------- /app/views/main/about.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "About Bottlenose" %> 2 | 3 |

Bottlenose is a web application to manage assignment submissions and grades 4 | for computer science courses.

5 | 6 | 7 | -------------------------------------------------------------------------------- /config/webpack/development.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development' 2 | 3 | const environment = require('./environment') 4 | 5 | module.exports = environment.toWebpackConfig() 6 | -------------------------------------------------------------------------------- /app/views/layouts/course.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :content do %> 2 | <%= render "layouts/standard_course_info" %> 3 | <%= yield %> 4 | <% end %> 5 | 6 | <%= render template: "layouts/application" %> 7 | -------------------------------------------------------------------------------- /db/migrate/20170702154328_require_show_in_lists.rb: -------------------------------------------------------------------------------- 1 | class RequireShowInLists < ActiveRecord::Migration[5.1] 2 | def change 3 | change_column_null :registrations, :show_in_lists, true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20180924222312_index_used_subs_on_assignment_id.rb: -------------------------------------------------------------------------------- 1 | class IndexUsedSubsOnAssignmentId < ActiveRecord::Migration[5.2] 2 | def change 3 | add_index :used_subs, :assignment_id 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /sandbox/examples/extra/test.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | use 5.16.0; 3 | 4 | use Test::Simple tests => 1; 5 | 6 | my $hello = `./hello`; 7 | chomp $hello; 8 | ok($hello eq "Hi!", "output correct"); 9 | 10 | -------------------------------------------------------------------------------- /db/migrate/20180301224622_index_inline_comments_on_severity.rb: -------------------------------------------------------------------------------- 1 | class IndexInlineCommentsOnSeverity < ActiveRecord::Migration[5.1] 2 | def change 3 | add_index :inline_comments, [:severity] 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /sandbox/examples/demo/hello/test.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | use 5.16.0; 3 | 4 | use Test::Simple tests => 1; 5 | 6 | my $hello = `./hello`; 7 | chomp $hello; 8 | ok($hello eq "Hi!", "output correct"); 9 | 10 | -------------------------------------------------------------------------------- /sandbox/examples/extra/hello/test.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | use 5.16.0; 3 | 4 | use Test::Simple tests => 1; 5 | 6 | my $hello = `./hello`; 7 | chomp $hello; 8 | ok($hello eq "Hi!", "output correct"); 9 | 10 | -------------------------------------------------------------------------------- /sandbox/examples/sleep/_grading/grade.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $stdout.sync = true 4 | $stderr.sync = true 5 | 6 | 0.upto(20) do |ii| 7 | puts "Hello #{ii}" 8 | sleep(5) 9 | end 10 | 11 | 12 | -------------------------------------------------------------------------------- /sandbox/examples/demo/_grading/test.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | use 5.16.0; 3 | 4 | use Test::Simple tests => 1; 5 | 6 | my $hello = `./hello`; 7 | chomp $hello; 8 | ok($hello eq "Hi!", "output correct"); 9 | 10 | -------------------------------------------------------------------------------- /app/assets/stylesheets/page-header.scss: -------------------------------------------------------------------------------- 1 | .page-header { 2 | margin-top: 0; 3 | 4 | h1 { 5 | display: inline-block; 6 | } 7 | 8 | .actions { 9 | margin-top: 25px; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /bin/delayed_job: -------------------------------------------------------------------------------- 1 | #!/home/bottlenose/.rbenv/shims/ruby 2 | 3 | require File.expand_path(File.join(File.dirname(__FILE__), '..', 'config', 'environment')) 4 | require 'delayed/command' 5 | Delayed::Command.new(ARGV).daemonize 6 | -------------------------------------------------------------------------------- /db/migrate/20170705045007_require_assignment_grader_order.rb: -------------------------------------------------------------------------------- 1 | class RequireAssignmentGraderOrder < ActiveRecord::Migration[5.1] 2 | def change 3 | change_column_null :assignment_graders, :order, false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20190303232336_add_index_to_registrations.rb: -------------------------------------------------------------------------------- 1 | class AddIndexToRegistrations < ActiveRecord::Migration[5.2] 2 | def change 3 | add_index :registrations, [:course_id, :user_id], unique: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/assets/stylesheets/sandboxes.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the sandboxes controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /db/migrate/20240725235205_add_orca_status_to_grader.rb: -------------------------------------------------------------------------------- 1 | class AddOrcaStatusToGrader < ActiveRecord::Migration[7.0] 2 | def change 3 | add_column :graders, :orca_status, :boolean, default: false, null: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 2 | 3 | require "bundler/setup" # Set up gems listed in the Gemfile. 4 | require "bootsnap/setup" # Speed up boot time by caching expensive operations. 5 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-Agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /config/initializers/action_mailer.rb: -------------------------------------------------------------------------------- 1 | 2 | # Set default hostname for emailed URLs. 3 | unless Rails.env.test? 4 | ActionMailer::Base.default_url_options[:host] = `hostname -f`.chomp 5 | ActionMailer::Base.delivery_method = :sendmail 6 | end 7 | -------------------------------------------------------------------------------- /lib/tasks/screenshots.rake: -------------------------------------------------------------------------------- 1 | desc "Generates screenshots for the manual" 2 | namespace :manual do 3 | task :screenshots => :environment do 4 | exec("ruby", File.expand_path("../screenshots.rb", __FILE__), *ARGV[1..-1]) 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/fixtures/sandboxes.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | name: MyString 5 | submission_id: 6 | 7 | two: 8 | name: MyString 9 | submission_id: 10 | -------------------------------------------------------------------------------- /app/assets/stylesheets/bootstrap-treeview.scss: -------------------------------------------------------------------------------- 1 | .treeview .list-group-item{cursor:pointer}.treeview span.indent{margin-left:10px;margin-right:10px}.treeview span.icon{width:12px;margin-right:5px}.treeview .node-disabled{color:silver;cursor:not-allowed} -------------------------------------------------------------------------------- /db/migrate/20180211180103_index_inline_comments.rb: -------------------------------------------------------------------------------- 1 | class IndexInlineComments < ActiveRecord::Migration[5.1] 2 | def change 3 | add_index "inline_comments", [:grade_id] 4 | add_index "inline_comments", [:submission_id] 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/fixtures/files/test-exam.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - weight: 6 3 | - weight: 6 4 | name: "Question 2!" 5 | - parts: 6 | - weight: 3 7 | name: "Problem 3a" 8 | - weight: 5 9 | name: "Problem 3b" 10 | - weight: 3 11 | extra: true 12 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: test 6 | 7 | production: 8 | adapter: redis 9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 10 | channel_prefix: bottlenose_production 11 | -------------------------------------------------------------------------------- /app/assets/javascripts/sandboxes.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/settings.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/models/submission_view.rb: -------------------------------------------------------------------------------- 1 | class SubmissionView < ApplicationRecord 2 | belongs_to :user 3 | belongs_to :assignment 4 | belongs_to :team, optional: true 5 | 6 | validates :user_id, presence: true 7 | validates :assignment_id, presence: true 8 | end 9 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative "application" 3 | 4 | def fa_icon(name) 5 | %Q{}.html_safe 6 | end 7 | 8 | # Initialize the Rails application. 9 | Rails.application.initialize! 10 | -------------------------------------------------------------------------------- /db/migrate/20201026221030_add_user_index_to_inline_comments.rb: -------------------------------------------------------------------------------- 1 | class AddUserIndexToInlineComments < ActiveRecord::Migration[5.2] 2 | def change 3 | add_index "inline_comments", ["user_id"], name: "index_inline_comments_on_user_id", using: :btree 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /test/fixtures/files/fundies-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "OneTopLevelClass": {"category": "ignore"}, 3 | "OuterTypeFilename": {"category": "ignore"}, 4 | "AvoidStarImport": {"category": "ignore"}, 5 | "SummaryJavadoc": {"category": "ignore"} 6 | } 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/assets/javascripts/main.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ 4 | -------------------------------------------------------------------------------- /db/migrate/20170714150844_rename_grader_allocation_grader.rb: -------------------------------------------------------------------------------- 1 | class RenameGraderAllocationGrader < ActiveRecord::Migration[5.1] 2 | def change 3 | change_table :grader_allocations do |t| 4 | t.rename :grade_id, :who_grades_id 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/audit.rb: -------------------------------------------------------------------------------- 1 | class Audit 2 | @@log = Logger.new(File.open(Rails.root.join("log", "audit-#{Rails.env}.log"), 3 | File::WRONLY | File::APPEND | File::CREAT)) 4 | def self.log(msg) 5 | @@log.info("#{Time.now}: #{msg}") 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/assets/javascripts/terms.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ 4 | -------------------------------------------------------------------------------- /app/models/assignments/files.rb: -------------------------------------------------------------------------------- 1 | class Files < Assignment 2 | validates :related_assignment_id, :absence => true 3 | 4 | def questions 5 | nil 6 | end 7 | def flattened_questions 8 | nil 9 | end 10 | def sections 11 | nil 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/models/user_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class UserTest < ActiveSupport::TestCase 4 | test "email addresses are forced to lowercase" do 5 | bob = create(:user, email: "Bob@example.com") 6 | assert_equal(bob.email, bob.email.downcase) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/assets/javascripts/reg_requests.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ 4 | -------------------------------------------------------------------------------- /test/fixtures/files/Exam/exam-v2-error-questions.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Matrix transpose" 3 | weight: 11 4 | - name: "Duplicator iterator" 5 | weight: 16 6 | - name: "Charm bracelets" 7 | weight: 12 8 | - name: "Equality/hashCode" 9 | weight: 5 10 | extra: true 11 | ... 12 | -------------------------------------------------------------------------------- /db/migrate/20170602143559_rename_team_sets.rb: -------------------------------------------------------------------------------- 1 | class RenameTeamSets < ActiveRecord::Migration[5.1] 2 | def change 3 | rename_table :team_sets, :teamsets 4 | rename_column :teams, :team_set_id, :teamset_id 5 | rename_column :assignments, :team_set_id, :teamset_id 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ActiveSupport::Reloader.to_prepare do 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: 'example.org', 6 | # https: false 7 | # ) 8 | # end 9 | -------------------------------------------------------------------------------- /db/migrate/20170827140156_index_codereview_matchings_on_assignment_id.rb: -------------------------------------------------------------------------------- 1 | class IndexCodereviewMatchingsOnAssignmentId < ActiveRecord::Migration[5.1] 2 | def change 3 | add_index "codereview_matchings", ["assignment_id"], name: "index_codereview_matchings_on_assignment_id" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('postcss-import'), 4 | require('postcss-flexbugs-fixes'), 5 | require('postcss-preset-env')({ 6 | autoprefixer: { 7 | flexbox: 'no-2009' 8 | }, 9 | stage: 3 10 | }) 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /app/models/user_submission.rb: -------------------------------------------------------------------------------- 1 | class UserSubmission < ApplicationRecord 2 | belongs_to :user 3 | belongs_to :submission 4 | 5 | def self.with_user(user) 6 | where(user_id: user) 7 | end 8 | 9 | def self.with_submission(sub) 10 | where(submission_id: sub) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/views/submissions/show_questions.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "View Submission" %> 2 | <% cur_reg = current_user.registration_for(@course) %> 3 | 4 | <%= render "show_common", cur_reg: cur_reg, kind: "responses", 5 | show_download: false %> 6 | 7 | <%= render "scoring_common", cur_reg: cur_reg %> 8 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | Mime::Type.register "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", :xlsx 6 | -------------------------------------------------------------------------------- /app/views/submissions/show_codereview.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "View Submission" %> 2 | <% cur_reg = current_user.registration_for(@course) %> 3 | 4 | <%= render "show_common", cur_reg: cur_reg, kind: "responses", 5 | show_download: false %> 6 | 7 | <%= render "scoring_common", cur_reg: cur_reg %> 8 | -------------------------------------------------------------------------------- /db/migrate/20171211221349_add_extra_credit_fields.rb: -------------------------------------------------------------------------------- 1 | class AddExtraCreditFields < ActiveRecord::Migration[5.1] 2 | def change 3 | add_column :assignments, :extra_credit, :boolean, null: false, default: false 4 | add_column :graders, :extra_credit, :boolean, null: false, default: false 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/array_split.rb: -------------------------------------------------------------------------------- 1 | 2 | class Array 3 | def split_on(pat) 4 | as = [[]] 5 | ii = 0 6 | 7 | self.each do |xx| 8 | if xx.match(pat) 9 | as << [] 10 | ii += 1 11 | else 12 | as[ii] << xx 13 | end 14 | end 15 | 16 | as 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/fixtures/files/Exam/exam.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Matrix transpose" 3 | weight: 11 4 | - name: "Duplicator iterator" 5 | weight: 16 6 | - name: "Visitors" 7 | weight: 11 8 | - name: "Charm bracelets" 9 | weight: 12 10 | - name: "Equality/hashCode" 11 | weight: 7 12 | extra: true 13 | ... 14 | -------------------------------------------------------------------------------- /app/helpers/grader_allocations_helper.rb: -------------------------------------------------------------------------------- 1 | module GraderAllocationsHelper 2 | def names_for_submissions(subs) 3 | options_for_select(subs.map {|sub| [sub.submission_user_names, sub.id] }) 4 | end 5 | def names_for_graders(graders) 6 | options_for_select(graders.map {|g| [g.name, g.id] }) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/fixtures/files/Exam-EC/exam.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Matrix transpose" 3 | weight: 11 4 | - name: "Duplicator iterator" 5 | weight: 16 6 | - name: "Visitors" 7 | weight: 11 8 | - name: "Charm bracelets" 9 | weight: 12 10 | - name: "Equality/hashCode" 11 | weight: 7 12 | extra: true 13 | ... 14 | -------------------------------------------------------------------------------- /app/models/grading_conflict_audit.rb: -------------------------------------------------------------------------------- 1 | class GradingConflictAudit < ApplicationRecord 2 | enum status: [:active, :inactive, :pending, :rejected] 3 | belongs_to :user 4 | belongs_to :grading_conflict 5 | 6 | def reason=(value) 7 | super(ActionController::Base.helpers.sanitize(value)) 8 | end 9 | 10 | end -------------------------------------------------------------------------------- /db/migrate/20170504141136_auxiliary.rb: -------------------------------------------------------------------------------- 1 | class Auxiliary < ActiveRecord::Migration[4.2] 2 | def self.up 3 | execute "TRUNCATE schema_migrations;" 4 | execute "INSERT INTO schema_migrations VALUES ('20170502174648');" 5 | end 6 | def self.down 7 | raise ActiveRecord::IrreversibleMigration 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/fixtures/files/Exam/exam-v2-correct.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Matrix transpose" 3 | weight: 11 4 | - name: "Duplicator iterator" 5 | weight: 16 6 | - name: "Visitors" 7 | weight: 11 8 | - name: "Charm bracelets" 9 | weight: 13 10 | - name: "Equality/hashCode" 11 | weight: 7 12 | extra: true 13 | ... 14 | -------------------------------------------------------------------------------- /app/views/layouts/_footer.html.erb: -------------------------------------------------------------------------------- 1 |
2 | Bottlenose copyright © 2012-2017 Benjamin Lerner, Nat Tuck. Licensed under the 3 | <%= link_to("GNU Affero GPL", "/agpl-3.0.txt") %> v3 or later. 4 | Source <%= link_to("at GitHub", "http://www.github.com/CodeGrade/bottlenose") %>. 5 |
6 | -------------------------------------------------------------------------------- /script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | APP_PATH = File.expand_path('../../config/application', __FILE__) 5 | require File.expand_path('../../config/boot', __FILE__) 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /test/fixtures/files/Exam/exam-v2-error-weight.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Matrix transpose" 3 | weight: 11 4 | - name: "Duplicator iterator" 5 | weight: 16 6 | - name: "Visitors" 7 | weight: 11 8 | - name: "Charm bracelets" 9 | weight: 1 10 | - name: "Equality/hashCode" 11 | weight: 10 12 | extra: true 13 | ... 14 | -------------------------------------------------------------------------------- /app/views/submissions/_enter_answer_text.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% value = @answers&.dig(sub_id.to_s, index, "main") %> 3 | 5 |
6 | -------------------------------------------------------------------------------- /app/views/submissions/_show_answer_text.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% value = @answers&.dig(sub_id.to_s, index, "main") %> 3 | 5 |
6 | -------------------------------------------------------------------------------- /test/fixtures/files/Exam/exam-incorrect-format.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Matrix transpose" 3 | weight: 11 4 | - name: "Duplicator iterator" 5 | weight: 16 6 | - name: "Visitors" 7 | weight: 11 8 | - name: "Charm bracelets" 9 | weight: 12 10 | - name: "Equality/hashCode" 11 | weight: 7 12 | extra: true 13 | ... 14 | -------------------------------------------------------------------------------- /script/getip.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | use 5.18.0; 3 | use warnings FATAL => 'all'; 4 | 5 | my $route = `ip route get 8.8.8.8`; 6 | $route =~ /dev\s+(\w+?)\s/ or die; 7 | my $iface = $1; 8 | 9 | my $addrs = `ip a show dev "$iface"`; 10 | $addrs =~ /inet\s+(\d+\.\d+\.\d+\.\d+)[\s|\/]/ or die; 11 | 12 | my $ip = $1; 13 | say "$ip"; 14 | -------------------------------------------------------------------------------- /app/views/sandboxes/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 | 3 |

4 | Name: 5 | <%= @sandbox.name %> 6 |

7 | 8 |

9 | Submission: 10 | <%= @sandbox.submission_id %> 11 |

12 | 13 | <%= link_to 'Edit', edit_sandbox_path(@sandbox) %> | 14 | <%= link_to 'Back', sandboxes_path %> 15 | -------------------------------------------------------------------------------- /script/update-grade-caches: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | APP_PATH = File.expand_path('../../config/application', __FILE__) 4 | require File.expand_path('../../config/boot', __FILE__) 5 | require APP_PATH 6 | Rails.application.require_environment! 7 | 8 | Submission.all.each do |sub| 9 | sub.assignment.update_best_sub_for!(sub.user) 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20170726215845_change_interlock_constraint_type.rb: -------------------------------------------------------------------------------- 1 | class ChangeInterlockConstraintType < ActiveRecord::Migration[5.1] 2 | def up 3 | remove_column :interlocks, :constraint 4 | add_column :interlocks, :constraint, :integer, null: false 5 | end 6 | def down 7 | change_column :interlocks, :constraint, :string 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/assets/javascripts/submissions.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | function initFiles() { 3 | $(".symlink-jump").click(function(e) { 4 | e.preventDefault(); 5 | e.stopPropagation(); 6 | selectTreeviewFileByHref($(this).data("root"), $(this).attr("href")); 7 | }); 8 | } 9 | 10 | 11 | run_on_page("submissions/details", initFiles); 12 | })(); 13 | -------------------------------------------------------------------------------- /db/migrate/20170726231116_create_submission_views.rb: -------------------------------------------------------------------------------- 1 | class CreateSubmissionViews < ActiveRecord::Migration[5.1] 2 | def change 3 | create_table :submission_views do |t| 4 | t.integer :user_id, null: false 5 | t.integer :team_id, null: true # teams might be optional 6 | t.integer :assignment_id, null: false 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/views/submissions/_show_answer_code.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% value = @answers&.dig(sub_id.to_s, index, "main") || q["initial"] %> 3 | <%= code_textarea value, 4 | name: "answers[#{sub_id}][#{index}][main]", 5 | id: "answer_#{sub_id}_#{index}", 6 | class: "sourceCode form-control optional", 7 | data: {lang: q["lang"]} %> 8 |
9 | -------------------------------------------------------------------------------- /sandbox/examples/demo/_grading/grade.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'steno' 4 | 5 | fs = ['Makefile', 'test.pl'] 6 | 7 | steno = Steno.new 8 | steno.save_grading_hashes(fs) 9 | steno.unpack 10 | steno.shell("make") 11 | steno.check_grading_hashes 12 | fs.each do |name| 13 | FileUtils.cp("_grading/#{name}", ".") 14 | end 15 | steno.run_tests("make test") 16 | 17 | -------------------------------------------------------------------------------- /app/views/submissions/_enter_answer_code.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% value = @answers&.dig(sub_id.to_s, index, "main") || q["initial"] %> 3 | <%= code_textarea value, 4 | name: "answers[#{sub_id}][#{index}][main]", 5 | id: "answer_#{sub_id}_#{index}", 6 | class: "sourceCode form-control optional", 7 | data: {lang: q["lang"], editable: true} %> 8 |
9 | -------------------------------------------------------------------------------- /app/assets/javascripts/grades.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | function initFiles() { 3 | $(".symlink-jump").click(function(e) { 4 | e.preventDefault(); 5 | e.stopPropagation(); 6 | selectTreeviewFileByHref($(this).data("root"), $(this).attr("href")); 7 | }); 8 | } 9 | 10 | 11 | run_on_page("grades/show", initFiles); 12 | run_on_page("grades/edit", initFiles); 13 | })(); 14 | -------------------------------------------------------------------------------- /script/parse-test-output: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../../config/application', __FILE__) 3 | require File.expand_path('../../config/boot', __FILE__) 4 | require APP_PATH 5 | Rails.application.require_environment! 6 | 7 | results = File.open(ARGV[0]) 8 | 9 | output = results.read 10 | 11 | require 'tap_parser' 12 | parser = TapParser.new(output) 13 | 14 | puts parser.summary 15 | -------------------------------------------------------------------------------- /app/views/assignments/show_user_exam.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "#{@course.name} / #{@gradesheet.assignment.name} / Responses for #{@user.display_name}" %> 2 | 3 | <% cur_reg = current_user.registration_for(@course) %> 4 | <%= render 'assignment_questions_info', gradesheet: @gradesheet, cur_reg: cur_reg %> 5 |

6 | Grades 7 |

8 | <%= render 'user_subs', user: @user, course: @course, gradesheet: @gradesheet %> 9 | -------------------------------------------------------------------------------- /app/views/terms/show.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "View Term" %> 2 | 3 |

<%= notice %>

4 | 5 |

6 | Name: 7 | <%= @term.name %> 8 |

9 | 10 |

11 | Archived: 12 | <%= @term.archived? ? "yes" : "no" %> 13 |

14 | 15 | <% if current_user.site_admin? %> 16 | <%= link_to 'Edit', edit_term_path(@term) %> | 17 | <% end %> 18 | <%= link_to 'Back', terms_path %> 19 | -------------------------------------------------------------------------------- /sandbox/examples/extra/_grading/grade.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'steno' 4 | 5 | fs = ['Makefile', 'test.pl'] 6 | 7 | FileUtils.cp("test.pl", "_grading") 8 | 9 | steno = Steno.new 10 | steno.save_grading_hashes(fs) 11 | steno.unpack 12 | steno.shell("make") 13 | steno.check_grading_hashes 14 | fs.each do |name| 15 | FileUtils.cp("_grading/#{name}", ".") 16 | end 17 | steno.run_tests("perl test.pl") 18 | 19 | -------------------------------------------------------------------------------- /test/fixtures/grader_configs.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | # This model initially had no columns defined. If you add columns to the 4 | # model remove the '{}' from the fixture names and add the columns immediately 5 | # below each fixture, per the syntax in the comments below 6 | # 7 | one: {} 8 | # column: value 9 | # 10 | two: {} 11 | # column: value 12 | -------------------------------------------------------------------------------- /test/fixtures/files/TestScript/test.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | use 5.16.0; 3 | use strict; 4 | use warnings FATAL => 'all'; 5 | 6 | use Test::Simple tests => 2; 7 | 8 | system("rm -f hello"); 9 | system("gcc -o hello hello.c"); 10 | my $rv = $? >> 8; 11 | 12 | ok($rv == 0, "Compile OK"); 13 | 14 | my $hello = `./hello`; 15 | chomp $hello; 16 | 17 | ok($hello eq "Hello, World!", "Hello OK"); 18 | 19 | system("rm -f hello"); 20 | 21 | -------------------------------------------------------------------------------- /app/assets/stylesheets/footer.scss: -------------------------------------------------------------------------------- 1 | $footer-height: 40px; 2 | 3 | html { 4 | position: relative; 5 | min-height: 100%; 6 | } 7 | 8 | body { 9 | margin-bottom: $footer-height; 10 | } 11 | 12 | .footer { 13 | background-color: #f5f5f5; 14 | border-top: thin solid gray; 15 | bottom: 0; 16 | height: $footer-height; 17 | padding: 8px; 18 | position: absolute; 19 | text-align: center; 20 | width: 100%; 21 | } 22 | -------------------------------------------------------------------------------- /db/migrate/20170727203859_create_codereview_matchings.rb: -------------------------------------------------------------------------------- 1 | class CreateCodereviewMatchings < ActiveRecord::Migration[5.1] 2 | def change 3 | create_table :codereview_matchings do |t| 4 | t.integer :assignment_id, null: false 5 | t.integer :user_id 6 | t.integer :team_id 7 | t.integer :target_user_id 8 | t.integer :target_team_id 9 | t.index [:user_id] 10 | t.index [:team_id] 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/performance/browsing_test.rb: -------------------------------------------------------------------------------- 1 | #require 'test_helper' 2 | #require 'rails/performance_test_help' 3 | 4 | #class BrowsingTest < ActionDispatch::PerformanceTest 5 | # Refer to the documentation for all available options 6 | # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory] 7 | # :output => 'tmp/performance', :formats => [:flat] } 8 | 9 | # def test_homepage 10 | # get '/' 11 | # end 12 | #end 13 | -------------------------------------------------------------------------------- /app/controllers/settings_controller.rb: -------------------------------------------------------------------------------- 1 | class SettingsController < ApplicationController 2 | before_action :require_site_admin 3 | 4 | def edit 5 | @cfg = Settings.load_json 6 | end 7 | 8 | def update 9 | @cfg = Settings.defaults 10 | 11 | @cfg.each_key do |kk| 12 | @cfg[kk] = params[kk] 13 | end 14 | 15 | Settings.save_json(@cfg) 16 | 17 | redirect_to edit_settings_path, notice: "Settings Saved" 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/views/graders/_ping_orca.js.erb: -------------------------------------------------------------------------------- 1 | function pingOrca(statusID, url) { 2 | if (!$(`#${statusID}`).length || url === '') { 3 | return; 4 | } 5 | $.ajax({ 6 | url, 7 | success: function (statusMessage) { 8 | $(`#${statusID}`).text(statusMessage); 9 | }, 10 | error: function (err) { 11 | $(`#${statusID}`).text(`Could not get status; ${err.status} ${err.statusMessage}`); 12 | }, 13 | dataType: 'json' 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /app/views/terms/_table.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <% terms.each do |term| %> 10 | 11 | 12 | 13 | 14 | <% end %> 15 |
NameArchived
<%= link_to term.name, term_path(term) %><%= term.archived? ? "archived" : "active" %>
16 | -------------------------------------------------------------------------------- /db/migrate/20180302211623_add_bonus_inline_comment_type.rb: -------------------------------------------------------------------------------- 1 | class AddBonusInlineCommentType < ActiveRecord::Migration[5.1] 2 | def up 3 | InlineComment.where(severity: "info").where("weight < 0").each do |c| 4 | c.update(severity: "bonus", weight: 0 - c.weight) 5 | end 6 | end 7 | def down 8 | InlineComment.where(severity: "bonus").each do |c| 9 | c.update(severity: "info", weight: 0 - c.weight) 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20181004011746_create_submission_enabled_toggles.rb: -------------------------------------------------------------------------------- 1 | class CreateSubmissionEnabledToggles < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :submission_enabled_toggles do |t| 4 | t.integer :section_id, null: false 5 | t.integer :assignment_id, null: false 6 | t.integer :interlock_id, null: false 7 | t.boolean :submissions_allowed, default: false 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /config/initializers/permissions_policy.rb: -------------------------------------------------------------------------------- 1 | # Define an application-wide HTTP permissions policy. For further 2 | # information see https://developers.google.com/web/updates/2018/06/feature-policy 3 | # 4 | # Rails.application.config.permissions_policy do |f| 5 | # f.camera :none 6 | # f.gyroscope :none 7 | # f.microphone :none 8 | # f.usb :none 9 | # f.fullscreen :self 10 | # f.payment :self, "https://secure.example.com" 11 | # end 12 | -------------------------------------------------------------------------------- /app/models/registration_section.rb: -------------------------------------------------------------------------------- 1 | class RegistrationSection < ApplicationRecord 2 | belongs_to :registration 3 | belongs_to :section 4 | 5 | validate do 6 | if self.section.course_id != self.registration.course_id 7 | self.errors.add(:base, "Registration's course is for #{self.registration.course.name} (id #{self.registration.course_id}), but the section belongs to #{self.section.course.name} (id #{self.section.course_id})") 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/models/used_sub.rb: -------------------------------------------------------------------------------- 1 | class UsedSub < ApplicationRecord 2 | belongs_to :submission 3 | belongs_to :user 4 | belongs_to :assignment 5 | 6 | delegate :created_at, to: :submission 7 | delegate :grades, to: :submission 8 | 9 | validate :submission_matches_assignment 10 | 11 | def submission_matches_assignment 12 | if submission.assignment_id != assignment_id 13 | errors.add(:base, "Submission / assignment mismatch.") 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/models/grader_allocation.rb: -------------------------------------------------------------------------------- 1 | class GraderAllocation < ApplicationRecord 2 | belongs_to :assignment 3 | belongs_to :course 4 | belongs_to :submission 5 | has_one :who_grades, class_name: "User", :primary_key => "who_grades_id", :foreign_key => "id" 6 | 7 | def conflict_currently_exists? 8 | GradingConflict.exists?(course: self.course, 9 | staff_id: self.who_grades_id, 10 | student: submission.users, 11 | status: :active) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/assets/stylesheets/button-file-inputs.scss: -------------------------------------------------------------------------------- 1 | .btn-file { 2 | position: relative; 3 | overflow: hidden; 4 | } 5 | .btn-file input[type=file] { 6 | position: absolute; 7 | top: 0; 8 | right: 0; 9 | min-width: 100%; 10 | min-height: 100%; 11 | font-size: 100px; 12 | text-align: right; 13 | filter: alpha(opacity=0); 14 | opacity: 0; 15 | outline: none; 16 | background: white; 17 | cursor: inherit; 18 | display: block; 19 | } 20 | -------------------------------------------------------------------------------- /app/views/grades/_checker_import_schema.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

An archive containing:

4 |
 5 | assignment_<%= @assignment.id %>/
 6 |   <submissionId>.tap: the updated TAP output for each submission
 7 |                       or the string "DELETE" to erase the grade
 8 | 
9 |

The easiest way to generate TAP files of the appropriate form is 10 | simply to use the tester library.

11 |
12 |
13 | -------------------------------------------------------------------------------- /app/views/grades/_junit_import_schema.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

An archive containing:

4 |
 5 | assignment_<%= @assignment.id %>/
 6 |   <submissionId>.tap: the updated TAP output for each submission
 7 |                       or the string "DELETE" to erase the grade
 8 | 
9 |

The easiest way to generate TAP files of the appropriate form is 10 | simply to use the JUnitTap library.

11 |
12 |
13 | -------------------------------------------------------------------------------- /test/fixtures/inline_comments.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | filename: MyString 5 | line: 6 | author: 7 | label: MyString 8 | severity: MyString 9 | comment: MyString 10 | weight: 11 | suppressed: false 12 | 13 | two: 14 | filename: MyString 15 | line: 16 | author: 17 | label: MyString 18 | severity: MyString 19 | comment: MyString 20 | weight: 21 | suppressed: false 22 | -------------------------------------------------------------------------------- /bin/webpack: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" 4 | ENV["NODE_ENV"] ||= "development" 5 | 6 | require "pathname" 7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 8 | Pathname.new(__FILE__).realpath) 9 | 10 | require "bundler/setup" 11 | 12 | require "webpacker" 13 | require "webpacker/webpack_runner" 14 | 15 | APP_ROOT = File.expand_path("..", __dir__) 16 | Dir.chdir(APP_ROOT) do 17 | Webpacker::WebpackRunner.run(ARGV) 18 | end 19 | -------------------------------------------------------------------------------- /db/migrate/20201007115016_add_timestamps_to_submission_views.rb: -------------------------------------------------------------------------------- 1 | class AddTimestampsToSubmissionViews < ActiveRecord::Migration[5.2] 2 | def change 3 | add_timestamps :submission_views, null: true 4 | 5 | timestamp = DateTime.new(2020, 10, 07, 11, 50, 16) 6 | SubmissionView.update_all(created_at: timestamp, updated_at: timestamp) 7 | 8 | change_column_null :submission_views, :created_at, false 9 | change_column_null :submission_views, :updated_at, false 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/views/submissions/_enter_answer_numeric.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% min = q["min"].to_f 3 | max = q["max"].to_f 4 | delta = (max - min) / 20 %> 5 | <% value = @answers&.dig(sub_id.to_s, index, "main")&.to_f %> 6 | <%= spinner_tag "", value || 0.0, text: {name: "answers[#{sub_id}][#{index}][main]", id: "answer_#{sub_id}_#{index}_main"}, 7 | min: min, max: max, delta: delta, class: (if @answers and 8 | value.nil? then 'unanswered' else '' end) %> 9 |
10 | -------------------------------------------------------------------------------- /app/views/submissions/details_codereview.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "View Response" %> 2 | <% cur_reg = current_user.registration_for(@course) %> 3 | <% content_for :wide_content_before do %> 4 | 5 |

6 | <%= link_to "Back to submission", 7 | course_assignment_submission_path(@course, @assignment, @submission) %> 8 |

9 | 10 |

Response

11 | 12 | <%= render 'submissions/sub_info' %> 13 | 14 | <% end %> 15 | 16 | <%= render "submissions/codereview_details", cur_reg: cur_reg %> 17 | -------------------------------------------------------------------------------- /test/integration/login_redirect.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class LoginRedirectTest < ActionDispatch::IntegrationTest 4 | include Devise::Test::IntegrationHelpers 5 | 6 | setup do 7 | make_standard_course 8 | end 9 | 10 | test "redirect to intended page after login" do 11 | get '/users' 12 | assert_redirected_to new_user_session_path 13 | @fred.update_attribute(:sign_in_count, 2) 14 | sign_in @fred 15 | assert_equal users_path, request.fullpath 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/helpers/link_to_function_helper.rb: -------------------------------------------------------------------------------- 1 | module LinkToFunctionHelper 2 | def link_to_function(name, *args, &block) 3 | html_options = args.extract_options!.symbolize_keys 4 | 5 | function = block_given? ? update_page(&block) : args[0] || '' 6 | onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function}; return false;" 7 | href = html_options[:href] || '#' 8 | 9 | content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick)) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/views/errors/not_found.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "So long and thanks..." %> 2 | <% @bgcolor = "#8b7355" %> 3 |
4 | 5 | <%= image_tag "flammarion.png" %> 6 | 7 |

You have passed beyond the edges of this universe.

8 |
So long, and thanks for all the fish.
9 |

 

10 | <%= image_tag "dolphin.png", style: "transform: rotate(20deg);" %> 11 |
12 | -------------------------------------------------------------------------------- /bin/webpack-dev-server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" 4 | ENV["NODE_ENV"] ||= "development" 5 | 6 | require "pathname" 7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 8 | Pathname.new(__FILE__).realpath) 9 | 10 | require "bundler/setup" 11 | 12 | require "webpacker" 13 | require "webpacker/dev_server_runner" 14 | 15 | APP_ROOT = File.expand_path("..", __dir__) 16 | Dir.chdir(APP_ROOT) do 17 | Webpacker::DevServerRunner.run(ARGV) 18 | end 19 | -------------------------------------------------------------------------------- /db/migrate/20171103172019_create_team_requests.rb: -------------------------------------------------------------------------------- 1 | class CreateTeamRequests < ActiveRecord::Migration[5.1] 2 | def change 3 | create_table :team_requests do |t| 4 | t.integer "teamset_id", null: false 5 | t.integer "user_id", null: false 6 | t.string "partner_names", null: false 7 | t.index :teamset_id 8 | t.index :user_id 9 | t.datetime "created_at" 10 | t.datetime "updated_at" 11 | t.index [:teamset_id, :user_id], unique: true 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/models/sandbox.rb: -------------------------------------------------------------------------------- 1 | require 'container' 2 | 3 | class Sandbox < ApplicationRecord 4 | belongs_to :submission 5 | before_destroy :stop_container 6 | 7 | def container 8 | if new_record? 9 | raise Exception.new("Must save sandbox before getting container") 10 | end 11 | 12 | if @container.nil? 13 | @container = Container.new("sandbox-#{id}") 14 | end 15 | @container 16 | end 17 | 18 | def stop_container 19 | @container.force_stop! if @container 20 | end 21 | end 22 | 23 | -------------------------------------------------------------------------------- /app/views/submissions/_show_answer_numeric.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% min = q["min"].to_f 3 | max = q["max"].to_f 4 | delta = (max - min) / 20 %> 5 | <% value = @answers&.dig(sub_id.to_s, index, "main")&.to_f %> 6 | <%= spinner_tag "", value || 0.0, text: {name: "answers[#{sub_id}][#{index}][main]", id: "answer_#{sub_id}_#{index}_main"}, 7 | disabled: 'disabled', min: min, max: max, delta: delta, class: (if @answers and 8 | value.nil? then 'unanswered' else '' end) %> 9 |
10 | -------------------------------------------------------------------------------- /script/regrade-sub: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | APP_PATH = File.expand_path('../../config/application', __FILE__) 4 | require File.expand_path('../../config/boot', __FILE__) 5 | require APP_PATH 6 | Rails.application.require_environment! 7 | 8 | sub = Submission.find(ARGV[0]) 9 | 10 | if sub.nil? 11 | puts "Must provide valid submisison id." 12 | exit(0) 13 | end 14 | 15 | asg = sub.assignment 16 | 17 | asg.grader_configs.each do |gc| 18 | next unless gc.type == "SandboxGrader" 19 | gc.autograde!(asg, sub) 20 | end 21 | -------------------------------------------------------------------------------- /app/views/notification_mailer/got_reg_request.text.erb: -------------------------------------------------------------------------------- 1 | Hi <%= @teacher.name %>, 2 | 3 | A registration request was received for your course 4 | 5 | Course: <%= @course.name %> 6 | 7 | From a prospective student: 8 | 9 | Student: <%= @req.name %> 10 | Email: <%= @req.email %> 11 | 12 | The following message was included: 13 | 14 | <% @req.notes %> 15 | 16 | You can view your registration requests for this course 17 | here: <%= course_url(@course) + '/reg_requests' %> 18 | 19 | Thanks, 20 | 21 | The Bottlenose System 22 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure parameters to be filtered from the log file. Use this to limit dissemination of 4 | # sensitive information. See the ActiveSupport::ParameterFilter documentation for supported 5 | # notations and behaviors. 6 | Rails.application.config.filter_parameters += [ 7 | :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn 8 | ] 9 | 10 | Rails.application.config.filter_parameters += [:password] 11 | -------------------------------------------------------------------------------- /script/clear_jobs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'beaneater' 3 | 4 | bean = Beaneater.new("localhost:11300") 5 | puts bean.stats.inspect 6 | 7 | bean.tubes.each do |tube| 8 | puts "=== Tube: #{tube.name} ===" 9 | 10 | while tube.peek(:ready) 11 | job = tube.reserve 12 | puts "Clearing ready:" 13 | puts job.inspect 14 | puts job.delete 15 | end 16 | while tube.peek(:buried) 17 | job = tube.peek(:buried) 18 | puts "Clearing buried:" 19 | puts job.inspect 20 | puts job.delete 21 | end 22 | end 23 | 24 | -------------------------------------------------------------------------------- /app/views/grades/_tap_style_import_schema.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

An archive (.zip, .tar, .tar.gz or .tgz) containing:

4 |
 5 | assignment_<%= @assignment.id %>/
 6 |   <submissionId>.tap: the updated TAP output for each submission
 7 |                       or the string "DELETE" to erase the grade
 8 |      OR
 9 |   <submissionId>.log: the updated error log file if there is no TAP output
10 | 
11 |
12 |
13 | -------------------------------------------------------------------------------- /app/mailers/notification_mailer.rb: -------------------------------------------------------------------------------- 1 | class NotificationMailer < ActionMailer::Base 2 | default from: Settings['site_email'] 3 | 4 | # Subject can be set in your I18n file at config/locales/en.yml 5 | # with the following lookup: 6 | # 7 | # en.notification_mailer.got_reg_request.subject 8 | # 9 | def got_reg_request(teacher, req, base_url) 10 | @teacher = teacher 11 | @req = req 12 | @course = req.course 13 | @base_url = base_url 14 | 15 | mail(to: @teacher.email, subject: "Got reg request") 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/views/grades/_orca_output.js.erb: -------------------------------------------------------------------------------- 1 | $(function () { 2 | const detailsOnClick = function () { 3 | const chevronElement = $("#details-chevron"); 4 | if (chevronElement.hasClass("glyphicon-chevron-down")) { 5 | chevronElement.removeClass("glyphicon-chevron-down"); 6 | chevronElement.addClass("glyphicon-chevron-up"); 7 | } else { 8 | chevronElement.removeClass("glyphicon-chevron-up"); 9 | chevronElement.addClass("glyphicon-chevron-down"); 10 | } 11 | }; 12 | $("#details-collapse").click(detailsOnClick); 13 | }); 14 | -------------------------------------------------------------------------------- /app/views/terms/index.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "Terms" %> 2 | 3 | 15 | 16 |
17 |
18 | <%= render 'table', terms: @terms %> 19 |
20 |
21 | -------------------------------------------------------------------------------- /lib/assets/dockerfiles/racket-grader.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM orca-grader-base:latest 2 | 3 | RUN apt-get update && apt-get install -y software-properties-common tree xvfb libcairo2 libpango1.0-0 libgtk2.0-0 4 | RUN curl -L -o racket-8.14.sh https://download.racket-lang.org/installers/8.14/racket-8.14-x86_64-linux-cs.sh && \ 5 | sh racket-8.14.sh --unix-style --dest /usr/ --create-dir && rm racket-8.14.sh 6 | 7 | # from https://bugs.launchpad.net/ubuntu/+source/xorg-server/+bug/1059947 8 | RUN sed -i "184s/2>&1//" /usr/bin/xvfb-run 9 | 10 | USER orca-grader 11 | -------------------------------------------------------------------------------- /app/models/review_feedback.rb: -------------------------------------------------------------------------------- 1 | class ReviewFeedback < ApplicationRecord 2 | belongs_to :grade, optional: true 3 | belongs_to :submission 4 | belongs_to :review_submission, class_name: "Submission" 5 | belongs_to :upload # These might be shared with the underlying submission, so don't destroy dependents here 6 | 7 | def censored 8 | (self.score.to_f / self.out_of.to_f) < self.grader.review_threshold.to_f / 100.0 9 | end 10 | 11 | def to_s 12 | "Submission #{submission_id} is reviewed by submission #{review_submission_id}" 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/views/graders/_manual_grader.html.erb: -------------------------------------------------------------------------------- 1 | <%= f.hidden_field :type, value: "ManualGrader" %> 2 | <%= f.hidden_field :order %> 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
<%= f.label "avail_score", "Points available:" %><%= f.spinner "avail_score", f.object.avail_score || 50, :min => 0, :delta => 5 %><%= f.check_box :extra_credit, data: {toggle: "toggle", on: "Extra credit", off: "Regular"} %>
11 | -------------------------------------------------------------------------------- /lib/fake_upload.rb: -------------------------------------------------------------------------------- 1 | 2 | class FakeUpload 3 | def initialize(path, content = nil) 4 | @path = path 5 | @name = File.basename(path) 6 | if content 7 | @content = content 8 | @size = content.length 9 | end 10 | end 11 | 12 | def read 13 | @content || File.read(@path) 14 | end 15 | 16 | def original_filename 17 | @name 18 | end 19 | 20 | def content_type 21 | 'application/octet-stream' 22 | end 23 | 24 | def size 25 | @size || File.size(@path) 26 | end 27 | 28 | def rewind 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = "1.0" 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in the app/assets 11 | # folder are already added. 12 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 13 | -------------------------------------------------------------------------------- /lib/tasks/factory_bot_lint.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | namespace :factory_bot do 4 | desc 'Verify that all FactoryBot factories are valid' 5 | task lint: :environment do 6 | require 'factory_bot_rails' 7 | if Rails.env.test? 8 | conn = ActiveRecord::Base.connection 9 | conn.transaction do 10 | FactoryBot.lint 11 | raise ActiveRecord::Rollback 12 | end 13 | else 14 | system("bundle exec rake factory_bot:lint RAILS_ENV='test'") 15 | fail if $?.exitstatus.nonzero? 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /app/views/graders/_junit_grader.js.erb: -------------------------------------------------------------------------------- 1 | $(function () { 2 | const statusID = '<%= "orca-server-image-status-#{f.object.id}" %>'; 3 | const url = '<%= "#{f.object.becomes(JunitGrader).orca_job_status_url}" %>'; 4 | if (!$(`#${statusID}`).length) { 5 | return; 6 | } 7 | $.ajax({ 8 | url, 9 | success: function (statusMessage) { 10 | $(`#${statusID}`).text(statusMessage); 11 | }, 12 | error: function (err) { 13 | $(`#${statusID}`).text(`Could not get status; ${err.status} ${err.statusMessage}`); 14 | }, 15 | dataType: 'json' 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /app/views/graders/_schema_grader.js.erb: -------------------------------------------------------------------------------- 1 | $(function () { 2 | const statusID = '<%= "orca-server-image-status-#{f.object.id}" %>'; 3 | const url = '<%= "#{f.object.becomes(SandboxGrader).orca_job_status_url}" %>'; 4 | if (!$(`#${statusID}`).length) { 5 | return; 6 | } 7 | $.ajax({ 8 | url, 9 | success: function (statusMessage) { 10 | $(`#${statusID}`).text(statusMessage); 11 | }, 12 | error: function (err) { 13 | $(`#${statusID}`).text(`Could not get status; ${err.status} ${err.statusMessage}`); 14 | }, 15 | dataType: 'json' 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| /my_noisy_library/.match?(line) } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code 7 | # by setting BACKTRACE=1 before calling your invocation, like "BACKTRACE=1 ./bin/rails runner 'MyClass.perform'". 8 | Rails.backtrace_cleaner.remove_silencers! if ENV["BACKTRACE"] 9 | -------------------------------------------------------------------------------- /bin/yarn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_ROOT = File.expand_path('..', __dir__) 3 | Dir.chdir(APP_ROOT) do 4 | yarn = ENV["PATH"].split(File::PATH_SEPARATOR). 5 | select { |dir| File.expand_path(dir) != __dir__ }. 6 | product(["yarn", "yarn.cmd", "yarn.ps1"]). 7 | map { |dir, file| File.expand_path(file, dir) }. 8 | find { |file| File.executable?(file) } 9 | 10 | if yarn 11 | exec yarn, *ARGV 12 | else 13 | $stderr.puts "Yarn executable was not detected in the system." 14 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" 15 | exit 1 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/views/assignments/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "Edit Assignment" %> 2 | 3 |

4 | <%= link_to "Back to Course: #{@course.name}", course_path(@course) %> 5 | <% unless @chapter.nil? %> 6 | | <%= link_to "Back to Chapter: #{@chapter.name}", @chapter %> 7 | <% end %> 8 | | <%= link_to "View Assignment", course_assignment_path(@course, @assignment) %> 9 |

10 | 11 |

Edit Existing <%= @assignment.type.titlecase %> Assignment

12 | 13 |
14 | <%= render "#{@assignment.type.underscore}_form", asgn: @assignment %> 15 |
16 | -------------------------------------------------------------------------------- /script/mark-find-submissions: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | APP_PATH = File.expand_path('../../config/application', __FILE__) 4 | require File.expand_path('../../config/boot', __FILE__) 5 | require APP_PATH 6 | Rails.application.require_environment! 7 | 8 | Course.all.each do |course| 9 | course.chapters.each do |chapter| 10 | chapter.assignments.each do |assgn| 11 | course.student_registrations.map {|reg| reg.user}.each do |student| 12 | subs = assgn.submissions.where(user_id: student.id) 13 | puts "#{assgn.name} / #{student.name} / #{subs.count}" 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/views/courses/public.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = @course.name %> 2 | 3 | <% @course.buckets.each do |bb| %> 4 |

<%= bb.name %>

5 | 6 | <% bb.assignments.each do |aa| %> 7 |

<%= aa.name %>

8 | 9 | <% unless aa.assignment.blank? %> 10 |
11 | <%= raw aa.assignment %> 12 |
13 | <% end %> 14 | 15 | <% unless aa.assignment_file.empty? %> 16 |

Assignment Download: 17 | <%= link_to aa.assignment_file, aa.assignment_file_path %>

18 | <% end %> 19 | 20 | <% end %> 21 | <% end %> 22 | -------------------------------------------------------------------------------- /script/assign-tarball: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../../config/application', __FILE__) 3 | require File.expand_path('../../config/boot', __FILE__) 4 | require APP_PATH 5 | Rails.application.require_environment! 6 | 7 | require 'sub_tarball' 8 | 9 | unless ARGV[0] =~ /^\d+$/ 10 | raise Exception.new("Usage: script/assign-tarball ID") 11 | end 12 | 13 | as_id = ARGV[0].to_i 14 | 15 | puts "Generating tarball of submissions for assignment #{as_id}..." 16 | 17 | assign = Assignment.find(as_id) 18 | puts " name = #{assign.name}" 19 | 20 | tb = SubTarball.new(as_id) 21 | tb.update! 22 | 23 | puts tb.path 24 | -------------------------------------------------------------------------------- /app/views/grades/_grades_file_picker.html.erb: -------------------------------------------------------------------------------- 1 |

2 | 5 | <%= f.hidden_field :removefile, class: "remove-custom-file", value: '' %> 6 | Current file: nothing 7 | 11 |

12 | -------------------------------------------------------------------------------- /app/controllers/api/graders_controller.rb: -------------------------------------------------------------------------------- 1 | module Api 2 | class GradersController < ApiController 3 | skip_before_action :doorkeeper_authorize! 4 | before_action :find_grader 5 | 6 | def orca_response 7 | return head :missing if @grader.nil? 8 | return head :bad_request unless @grader.orca_status 9 | 10 | @grader.handle_image_build_attempt( 11 | params[:logs].map { |l| l.permit!.to_h }, 12 | params[:was_successful] 13 | ) 14 | head :ok 15 | end 16 | 17 | private 18 | 19 | def find_grader 20 | @grader = Grader.find_by_id params[:id] 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/views/users/index.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "Manage Users" %> 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | <% @users.each do |user| %> 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | <% end %> 23 |
NameEmailActions
<%= user.name %><%= user.email %><%= link_to 'Show', user_path(user) %><%= link_to 'Edit', edit_user_path(user) %><%= link_to 'Impersonate', impersonate_user_path(user), method: 'post' %>
24 | -------------------------------------------------------------------------------- /sandbox/examples/check.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | def usage 4 | puts "Usage: ruby check.rb sub.tar.gz grading.tar.gz" 5 | exit(1) 6 | end 7 | 8 | def shell(cmd) 9 | cmd.gsub!(%q{"}, %q{\"}) 10 | system(%Q{bash -c "#{cmd}"}) or 11 | raise Exception.new("Command '#{cmd}' failed: #{$?}") 12 | end 13 | 14 | sub = ARGV.shift or usage 15 | gra = ARGV.shift or usage 16 | dir = "tmp.#{$$}" 17 | 18 | shell(%Q{mkdir -p "#{dir}"}) 19 | shell(%Q{cp "#{sub}" "#{dir}"}) 20 | shell(%Q{(cd "#{dir}" && tar xzvf "../#{gra}")}) 21 | shell(%Q{(cd "#{dir}" && SUB="#{sub}" ruby -I_grading _grading/grade.rb)}) 22 | shell(%Q{rm -r "#{dir}"}) if dir =~ /^tmp/ 23 | 24 | -------------------------------------------------------------------------------- /db/simple-data.rb: -------------------------------------------------------------------------------- 1 | # Run me with "rails runner db/simple-data.rb" 2 | 3 | require 'devise/encryptor' 4 | 5 | def create_user(name, password) 6 | first, last = name.split(/\s+/) 7 | 8 | uu = User.create!( 9 | username: first.downcase, 10 | name: name, 11 | first_name: first, 12 | last_name: last, 13 | nickname: first, 14 | site_admin: false, 15 | ) 16 | 17 | uu.encrypted_password = Devise::Encryptor.digest(uu.class, password) 18 | uu.save! 19 | end 20 | 21 | create_user("Alice Anderson", "alice88") 22 | create_user("Bob Baker", "bob88") 23 | create_user("Carol Cooper", "carol88") 24 | create_user("Dave Dyson", "dave88") 25 | 26 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | def pluralize(count, word, plural = nil) 4 | ActionController::Base.helpers.pluralize(count, word, plural) 5 | end 6 | 7 | def multi_group_by(hash, keys, last_key_unique = false, index = 0) 8 | if index >= keys.count || hash.nil? 9 | hash 10 | elsif index == keys.count - 1 && last_key_unique 11 | Hash[hash.map{|v| [(v[keys[index]] rescue v.__send__(keys[index])), v]}] 12 | else 13 | Hash[hash.group_by(&(keys[index])).map {|k, v| [k, multi_group_by(v, keys, last_key_unique, index + 1)]}] 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/views/grader_allocations/stats.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "Grading statistics" %> 2 |

Grading statistics

3 | 4 | <% people = @course.staff.sort_by(&:name) %> 5 | 6 | <% people.each do |p| %> 7 | <% info = @grader_info[p.id] %> 8 |

9 | <%= p.name %> 10 | <% if info.nil? %> 11 |

    12 |
  • No assignments graded
  • 13 |
14 | <% else %> 15 |
    16 |
  • Incomplete gradings: <%= info[:incomplete] %>
  • 17 |
  • Abandoned gradings: <%= info[:abandoned] %>
  • 18 |
  • Average grading time: <%= "#{'%.01f' % info[:avg_grading_time]} days" %>
  • 19 |
20 | <% end %> 21 |

22 | <% end %> 23 | -------------------------------------------------------------------------------- /script/assignment_grep.rb: -------------------------------------------------------------------------------- 1 | APP_PATH = File.expand_path('../../config/application', __FILE__) 2 | require File.expand_path('../../config/boot', __FILE__) 3 | require APP_PATH 4 | Rails.application.require_environment! 5 | 6 | require 'active_record' 7 | 8 | assn_num = ARGV[0].to_i 9 | grep_cmd = ARGV[1] 10 | 11 | Assignment.find(assn_num.to_i).submissions.each do |sub| 12 | to_run = grep_cmd.gsub("{0}", sub.upload.extracted_path.to_s) 13 | ans = `#{to_run}` 14 | if ans != "" 15 | print "#{sub.id}: #{sub.user.name}" 16 | if sub.team 17 | print " (#{sub.team.to_s})" 18 | end 19 | print ":\n" 20 | print ans 21 | print "\n" 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /script/regrade-whole-assignment: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | APP_PATH = File.expand_path('../../config/application', __FILE__) 4 | require File.expand_path('../../config/boot', __FILE__) 5 | require APP_PATH 6 | Rails.application.require_environment! 7 | 8 | assign = Assignment.find(ARGV[0]) 9 | 10 | if assign.nil? 11 | puts "Usage: script/regrade-whole-assignment submission-id" 12 | exit(0) 13 | end 14 | 15 | puts 16 | puts "Regrading assignment: #{assign.name}" 17 | puts 18 | 19 | assign.submissions.each do |sub| 20 | puts 21 | puts "Regrading submission from: #{sub.user.name}" 22 | puts 23 | system("script/grade-submission #{sub.id}") 24 | end 25 | 26 | -------------------------------------------------------------------------------- /app/views/graders/_java_style_grader.html.erb: -------------------------------------------------------------------------------- 1 | <%= f.hidden_field :type, value: "JavaStyleGrader" %> 2 | <%= f.hidden_field :order %> 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | <%= render "graders/grader_file_picker", f: f, id: f.object.id.to_s, 11 | label: "Custom configuration:", schema: "java-style-schema" %> 12 |
<%= f.label "avail_score", "Points available:" %><%= f.spinner "avail_score", f.object.avail_score || 50, :min => 0, :delta => 5 %><%= f.check_box :extra_credit, data: {toggle: "toggle", on: "Extra credit", off: "Regular"} %>
13 | -------------------------------------------------------------------------------- /app/views/assignments/_interlock.html.erb: -------------------------------------------------------------------------------- 1 |
  • 2 | <% interlocked_assns = @course.assignments.where.not(id: asgn.id) 3 | .order(due_date: :desc) %> 4 | <%= f.select :constraint, 5 | options_for_select(Interlock::constraints.map{|c, cid| [Interlock.constraint_to_s(c), c]}, 6 | interlock&.constraint || :no_submission_unless_submitted) %> 7 | <%= f.select :related_assignment_id, 8 | options_from_collection_for_select(interlocked_assns, "id", "name") %> 9 | <%= link_to_remove_association "Remove", f, class: 'btn btn-danger pull-right', 10 | style: "vertical-align: initial, margin: -6pt -8pt;" %> 11 |
  • 12 | -------------------------------------------------------------------------------- /app/views/registrations/index.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "Registrations" %> 2 | 3 |

    Registrations 4 | 5 | <%= link_to "Register students", new_course_registration_path, class: 'btn 6 | btn-success pull-right' %> 7 |

    8 |
    9 |
    10 | <%= render "reg_requests/table", requests: @requests %> 11 |
    12 |
    13 | 14 |
    15 |
    16 | <%= render "users/table", header: "Students", role: "student", course: @course, users: @students %> 17 |
    18 |
    19 | <%= render "users/table", header: "Staff", role: @role, course: @course, users: @staff %> 20 |
    21 |
    22 | -------------------------------------------------------------------------------- /app/views/users/_form.js.erb: -------------------------------------------------------------------------------- 1 | $(function() { 2 | $("#nick_is_name").change(function() { 3 | $("#user_nickname").prop('readonly', $(this).prop('checked')); 4 | if ($(this).prop('checked')) 5 | $("#user_nickname").val($("#user_first_name").val()); 6 | }); 7 | }); 8 | $("form").submit(ensureValidNumericInputOnSubmit); 9 | $("input#user_profile").change(function(e) { 10 | try { 11 | var img = document.getElementById("profile_preview"); 12 | img.src = window.URL.createObjectURL(this.files[0]); 13 | img.onload = function() { 14 | window.URL.revokeObjectURL(this.src); 15 | } 16 | } catch(exn) { 17 | // may not support the file APIs sufficiently 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /app/views/reviews/show.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "Showing Code-review" %> 2 | 3 | <% content_for :wide_content_before do %> 4 | 5 |

    6 | <%= link_to "< Back to submission", course_assignment_submission_path(@course, @assignment, @submission) %> 7 |

    8 |

    <%= @review_assignment.review_target.capitalize %> Review

    9 | 10 |

    11 | This review of your submission is intended to 12 | be informative, professional and helpful. If 13 | you find something offensive about this review, please email your 14 | professor. 15 |

    16 | 17 | <% end %> 18 | 19 | <%= render "submissions/codereview_details", cur_reg: current_user.registration_for(@course) %> 20 | -------------------------------------------------------------------------------- /config/ldap.yml: -------------------------------------------------------------------------------- 1 | development: 2 | host: ldap.ccs.neu.edu 3 | port: 636 4 | attribute: uid 5 | base: ou=people,dc=ccs,dc=neu,dc=edu 6 | ssl: true 7 | 8 | # TODO: The configurations below are untested. 9 | 10 | test: 11 | host: localhost 12 | port: 3389 13 | attribute: uid 14 | base: ou=people,dc=test,dc=com 15 | admin_user: cn=admin,dc=test,dc=com 16 | admin_password: admin_password 17 | ssl: simple_tls 18 | # <<: *AUTHORIZATIONS 19 | 20 | production: 21 | host: ldap.ccs.neu.edu 22 | port: 636 23 | attribute: uid 24 | base: ou=people,dc=ccs,dc=neu,dc=edu 25 | # admin_user: cn=admin,dc=test,dc=com 26 | # admin_password: admin_password 27 | ssl: true 28 | # <<: *AUTHORIZATIONS 29 | -------------------------------------------------------------------------------- /app/views/assignments/_due_date.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | <%= f.label :due_date do %> 4 | Due date 5 | <% end %> 6 |

    Assignment should not be submitted after this time. Default: one week from today.

    7 |
    8 | <%= f.text_field :due_date, class: 'form-control datetime-picker' %> 9 |
    10 |
    11 |
    12 | <%= f.label :available_date %> 13 |

    Assignment will not be visible until this time. Default: today.

    14 |
    15 | <%= f.text_field :available, class: 'form-control datetime-picker' %> 16 |
    17 |
    18 |
    19 | -------------------------------------------------------------------------------- /app/assets/stylesheets/questions.scss: -------------------------------------------------------------------------------- 1 | .responses input.unanswered, 2 | .responses button.unanswered, 3 | .responses label.unanswered, 4 | .responses textarea.unanswered 5 | { 6 | border: 1px solid #eed3d7; 7 | box-shadow: 0 0 1em #eed3d7; 8 | } 9 | /* Don't override the border on selects, because it doesn't render properly */ 10 | .responses select.unanswered { 11 | box-shadow: 0 0 1em #eed3d7; 12 | } 13 | 14 | input.badAnswer { 15 | box-shadow: 0 0 1.5em #eed3d7; 16 | } 17 | 18 | input.goodAnswer { 19 | box-shadow: 0 0 1.5em #d3eed7; 20 | } 21 | 22 | .responses li.question:not(:last-child) { 23 | margin-bottom: 2em; 24 | } 25 | 26 | .responses li.question:hover { 27 | background-color: #eeeeee; 28 | } 29 | -------------------------------------------------------------------------------- /sandbox/driver.pl.erb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # -*- perl -*- 3 | use 5.18.0; 4 | use warnings FATAL => 'all'; 5 | use IO::Handle; 6 | 7 | open my $shell, "|-", "su -c bash ubuntu"; 8 | while () { 9 | chomp; 10 | $shell->say($_); 11 | } 12 | close $shell; 13 | 14 | __DATA__ 15 | export COOKIE="<%= cookie %>" 16 | export TIMEOUT="<%= timeout %>" 17 | export SUB="<%= sub_name %>" 18 | export GRA="<%= gra_name %>" 19 | 20 | <% if xtr_name %> 21 | export XTR="<%= xtr_name %>" 22 | <% end %> 23 | 24 | cd ~ 25 | wget -O "$SUB" "<%= sub_url %>" 26 | wget -O "$GRA" "<%= gra_url %>" 27 | <% if xtr_name && xtr_url %> 28 | wget -O "$XTR" "<%= xtr_url %>" 29 | <% end %> 30 | tar xzvf "$GRA" 31 | ruby -I_grading _grading/grade.rb 32 | -------------------------------------------------------------------------------- /app/views/courses/index.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "Courses" %> 2 | 3 | 12 | 13 |
    14 |
    15 | <% course_regs = current_user.course_regs_by_term %> 16 | <% @courses_by_term.each do |term, courses| %> 17 | <% next if term.archived? %> 18 |

    <%= term.name %>

    19 | 20 | <%= render 'table', courses: courses, reg_info: (course_regs[term] || {}) %> 21 | <% end %> 22 |
    23 |
    24 | -------------------------------------------------------------------------------- /app/views/sandboxes/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for(@sandbox) do |f| %> 2 | <% if @sandbox.errors.any? %> 3 |
    4 |

    <%= pluralize(@sandbox.errors.count, "error") %> prohibited this sandbox from being saved:

    5 | 6 |
      7 | <% @sandbox.errors.full_messages.each do |message| %> 8 |
    • <%= message %>
    • 9 | <% end %> 10 |
    11 |
    12 | <% end %> 13 | 14 |
    15 | <%= f.label :name %>
    16 | <%= f.text_field :name %> 17 |
    18 |
    19 | <%= f.label :submission_id %>
    20 | <%= f.text_field :submission_id %> 21 |
    22 |
    23 | <%= f.submit %> 24 |
    25 | <% end %> 26 | -------------------------------------------------------------------------------- /lib/devise/strategies/debug_login.rb: -------------------------------------------------------------------------------- 1 | # From https://insights.kyan.com/devise-authentication-strategies-a1a6b4e2b891 2 | module Devise 3 | module Strategies 4 | class DebugLogin < Authenticatable 5 | def authenticate! 6 | user = User.find_by(username: params[:user][:username]) 7 | if user && 8 | !params[:user][:password].blank? && 9 | Devise::Encryptor.compare(user.class, user.encrypted_password, params[:user][:password]) 10 | success!(user) 11 | else 12 | fail("Did not recognize username/password") 13 | end 14 | end 15 | def valid? 16 | params[:user] && params[:user][:username] && params[:user][:password] 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/views/teamsets/investigate.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "#{@course.name}: Investigate partners" %> 2 | 10 |
    11 | 15 | 16 |
    17 | <%= render 'common_partners' %> 18 | <%= render 'diff_teamsets' %> 19 |
    20 |
    21 | 24 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | inflect.irregular 'person', 'people' 10 | inflect.irregular 'is', 'are' 11 | # inflect.uncountable %w( fish sheep ) 12 | end 13 | 14 | # These inflection rules are supported but not enabled by default: 15 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 16 | # inflect.acronym "RESTful" 17 | # end 18 | -------------------------------------------------------------------------------- /app/models/submissions/files_sub.rb: -------------------------------------------------------------------------------- 1 | require 'securerandom' 2 | require 'audit' 3 | 4 | class FilesSub < Submission 5 | validate :ensure_fields_valid 6 | def ensure_fields_valid 7 | if self.assignment.request_time_taken && self.time_taken.to_s.empty? 8 | self.errors.add(:base, "Please specify how long you have worked on this assignment") 9 | elsif self.time_taken && !(Float(self.time_taken) rescue false) 10 | self.errors.add(:base, "Please specify a valid number for how long you have worked on this assignment") 11 | end 12 | if self.upload.nil? || self.upload.new_record? 13 | if @upload_data.nil? 14 | self.errors.add(:base, "You need to submit a file.") 15 | end 16 | end 17 | return self.errors.count == 0 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /script/resend-auth-links: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # Primitive bulk-user-upload script. Requires course_id to be hard-coded, below. 4 | # Reads comma-separated values of time, name, email, section, but ignores all but name and email. 5 | 6 | APP_PATH = File.expand_path('../../config/application', __FILE__) 7 | require File.expand_path('../../config/boot', __FILE__) 8 | require APP_PATH 9 | Rails.application.require_environment! 10 | 11 | course_id = ARGV[0] 12 | 13 | if course_id.nil? 14 | puts "Usage" 15 | puts " #{$0} course_id" 16 | exit(1) 17 | end 18 | 19 | course = Course.find(course_id.to_i) 20 | 21 | course.students.each do |user| 22 | puts "Resending auth link for #{user.name}..." 23 | user.send_auth_link_email!("https://grader.cs.uml.edu/") 24 | end 25 | -------------------------------------------------------------------------------- /app/assets/stylesheets/bootstrap-overrides.scss: -------------------------------------------------------------------------------- 1 | $navbar-inverse-link-color: #bbb; 2 | $navbar-inverse-link-active-color: #fff; 3 | 4 | table.no-first-row > thead > tr:first-child > td, 5 | table.no-first-row > tbody > tr:first-child > td, 6 | table.no-first-row > tfoot > tr:first-child > td { 7 | border-top: 0px solid white; 8 | } 9 | 10 | .modal-dialog-scrollable, 11 | .modal-dialog-scrollable .modal-content { 12 | max-height: calc(100vh - 3.5rem); 13 | display: flex; 14 | flex-direction: column; 15 | } 16 | 17 | .modal-dialog-scrollable .modal-content { 18 | overflow: hidden; 19 | } 20 | 21 | .modal-dialog-scrollable .modal-header { 22 | margin-bottom: 0; 23 | } 24 | 25 | .modal-dialog-scrollable .modal-body { 26 | overflow-y: auto; 27 | flex: 1 1 auto; 28 | } 29 | -------------------------------------------------------------------------------- /db/migrate/20171218170821_create_individual_extensions.rb: -------------------------------------------------------------------------------- 1 | class CreateIndividualExtensions < ActiveRecord::Migration[5.1] 2 | def change 3 | create_table :individual_extensions do |t| 4 | t.integer "assignment_id", null: false 5 | t.integer "user_id" 6 | t.integer "team_id" 7 | t.datetime "due_date", null: false 8 | t.datetime "created_at" 9 | t.datetime "updated_at" 10 | t.index ["assignment_id", "user_id"], name: "unique_assn_user_extension", unique: true, where: "(team_id IS NULL)" 11 | t.index ["assignment_id", "team_id"], name: "unique_assn_team_extension", unique: true, where: "(user_id IS NULL)" 12 | t.index ["assignment_id"] 13 | t.index ["user_id"] 14 | t.index ["team_id"] 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/views/submissions/_enter_answer_true_false.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | <% value = @answers&.dig(sub_id.to_s, index, "main")&.capitalize %> 3 | value="<%= value %>"<% end %>> 5 | 7 | 9 |
    10 | -------------------------------------------------------------------------------- /app/models/bucket.rb: -------------------------------------------------------------------------------- 1 | class Bucket < ApplicationRecord 2 | belongs_to :course 3 | has_many :assignments, dependent: :restrict_with_error 4 | 5 | validates :name, :uniqueness => { :scope => :course_id }, length: { minimum: 2 } 6 | validates :weight, numericality: true 7 | 8 | def points_earned(user) 9 | assignments.reduce(0) do |sum, aa| 10 | sub = aa.used_sub_for(user) 11 | sum + (sub.nil? ? 0 : sub.score) 12 | end 13 | end 14 | 15 | def weight_available 16 | assignments.reduce(0) { |sum, aa| sum + aa.weight } 17 | end 18 | 19 | def points_ratio(user) 20 | return 0 if weight_available.zero? 21 | points_earned(user) / weight_available 22 | end 23 | 24 | def points_percent(user) 25 | (points_ratio(user) * 100).round 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /app/views/courses/_grading_due.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    All pending grading

    4 |
    5 |
    6 | <% @pending_grading.each do |assn_id, sub_grades_groups| %> 7 |

    <%= link_to @assignments[assn_id].name, 8 | course_assignment_path(@course, assn_id) %> 9 | (<%= sub_grades_groups.count %> remaining) 10 |

    11 |
      12 | <% sub_grades_groups.each do |sub_id, gs| %> 13 | <% usernames = gs.values.first.map(&:user_name).to_sentence %> 14 |
    • <%= link_to usernames, 15 | course_assignment_submission_path(@course, assn_id, sub_id) %>
    • 16 | <% end %> 17 |
    18 | <% end %> 19 |
    20 |
    21 | -------------------------------------------------------------------------------- /app/views/sandboxes/index.html.erb: -------------------------------------------------------------------------------- 1 |

    <%= notice %>

    2 | 3 |

    Listing Sandboxes

    4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | <% @sandboxes.each do |sandbox| %> 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | <% end %> 24 | 25 |
    NameSubmission
    <%= sandbox.name %><%= sandbox.submission_id %><%= link_to 'Show', sandbox %><%= link_to 'Edit', edit_sandbox_path(sandbox) %><%= link_to 'Destroy', sandbox, method: :delete, data: { confirm: 'Are you sure?' } %>
    26 | 27 |
    28 | 29 | <%= link_to 'New Sandbox', new_sandbox_path %> 30 | -------------------------------------------------------------------------------- /app/views/submissions/_enter_answer_yes_no.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | <% value = @answers&.dig(sub_id.to_s, index, "main")&.capitalize %> 3 | value="<%= value %>"<% end %>> 5 | 7 | 9 |
    10 | 11 | -------------------------------------------------------------------------------- /db/migrate/20170805001646_drop_delayed_jobs.rb: -------------------------------------------------------------------------------- 1 | class DropDelayedJobs < ActiveRecord::Migration[5.1] 2 | def up 3 | drop_table "delayed_jobs" 4 | end 5 | 6 | def down 7 | create_table "delayed_jobs", force: :cascade do |t| 8 | t.integer "priority", default: 0, null: false 9 | t.integer "attempts", default: 0, null: false 10 | t.text "handler", null: false 11 | t.text "last_error" 12 | t.datetime "run_at" 13 | t.datetime "locked_at" 14 | t.datetime "failed_at" 15 | t.string "locked_by" 16 | t.string "queue" 17 | t.datetime "created_at" 18 | t.datetime "updated_at" 19 | end 20 | 21 | add_index "delayed_jobs", ["priority", "run_at"], name: "delayed_jobs_priority", using: :btree 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/views/submissions/_show_answer_true_false.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | <% value = @answers&.dig(sub_id.to_s, index, "main")&.capitalize %> 3 | value="<%= value %>"<% end %>> 5 | 7 | 9 |
    10 | -------------------------------------------------------------------------------- /app/views/submissions/_show_answer_yes_no.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | <% value = @answers&.dig(sub_id.to_s, index, "main")&.capitalize %> 3 | value="<%= value %>"<% end %>> 5 | 7 | 9 |
    10 | 11 | -------------------------------------------------------------------------------- /app/controllers/api/courses_controller.rb: -------------------------------------------------------------------------------- 1 | module Api 2 | class CoursesController < ApiController 3 | def index 4 | render json: serialize_active_courses(current_user.active_courses) 5 | end 6 | 7 | private 8 | 9 | def serialize_active_courses(active_courses) 10 | active_courses.map do |term, courses| 11 | { 12 | term: { 13 | semester: term.semester, 14 | year: term.year, 15 | archived: term.archived, 16 | }, 17 | courses: courses.map { |c| serialize_course(c) } 18 | } 19 | end 20 | end 21 | 22 | def serialize_course(course) 23 | { 24 | id: course.id, 25 | name: course.name, 26 | prof: current_user.course_professor?(course) 27 | } 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /test/integration/sanity_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SanityTest < ActionDispatch::IntegrationTest 4 | test "correct sandbox scripts installed" do 5 | skip 6 | sandbox = Rails.root.join("sandbox/scripts") 7 | install = Pathname.new("/usr/local/bottlenose/scripts") 8 | 9 | %W{build-assignment.sh teardown-directory.sh grading-prep.sh 10 | setup-directory.sh test-assignment.sh 11 | }.each do |script| 12 | assert File.exist?(install.join(script)), "Script installed?" 13 | ssum = `cat "#{sandbox.join(script)}" | md5sum` 14 | isum = `cat "#{install.join(script)}" | md5sum` 15 | assert_equal ssum, isum, "Installed version should match" 16 | end 17 | end 18 | 19 | test "factory bot lint" do 20 | FactoryBot.lint 21 | assert(true) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /config/schedule.rb: -------------------------------------------------------------------------------- 1 | 2 | # Use this file to easily define all of your cron jobs. 3 | # 4 | # It's helpful, but not entirely necessary to understand cron before proceeding. 5 | # http://en.wikipedia.org/wiki/Cron 6 | 7 | # Pull in the Rails environment for Rails.root 8 | require File.expand_path(File.dirname(__FILE__) + "/environment") 9 | 10 | set :output, "#{Rails.root}/log/cron.log" 11 | 12 | #every :hour do 13 | # rake "backup_and_reap" 14 | #end 15 | 16 | # Example: 17 | # 18 | # set :output, "/path/to/my/cron_log.log" 19 | # 20 | # every 2.hours do 21 | # command "/usr/bin/some_great_command" 22 | # runner "MyModel.some_method" 23 | # rake "some:great:rake:task" 24 | # end 25 | # 26 | # every 4.days do 27 | # runner "AnotherModel.prune_old_records" 28 | # end 29 | 30 | # Learn more: http://github.com/javan/whenever 31 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 17 | 18 | 19 | 20 | 21 |
    22 |

    The change you wanted was rejected.

    23 |

    Maybe you tried to change something you didn't have access to.

    24 |
    25 | 26 | 27 | -------------------------------------------------------------------------------- /app/helpers/spinner_helper.rb: -------------------------------------------------------------------------------- 1 | module SpinnerHelper 2 | def self.included(base) 3 | ActionView::Helpers::FormBuilder.instance_eval do 4 | include FormBuilderMethods 5 | end 6 | end 7 | 8 | module FormBuilderMethods 9 | def spinner(name, value = nil, options = {}) 10 | options[:wrapper] ||= {} 11 | options[:buttons] ||= {} 12 | options[:text] ||= {} 13 | @template.render "layouts/spinner", :name => "#{@object_name}[#{name}]", :value => value, :options => options, :f => @template 14 | end 15 | end 16 | 17 | def spinner_tag(name, value = nil, options = {}) 18 | options[:wrapper] ||= {} 19 | options[:buttons] ||= {} 20 | options[:text] ||= {} 21 | render partial: "layouts/spinner", locals: {name: "#{@object_name}[#{name}]", value: value, options: options} 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | require 'securerandom' 8 | 9 | key_file = File.expand_path("~/.rails_key").to_s 10 | unless File.exist?(key_file) 11 | kk = File.open(key_file, 'wb') 12 | 6.times do 13 | kk.write(SecureRandom.urlsafe_base64) 14 | end 15 | kk.close 16 | end 17 | 18 | if Rails.version.to_f <= 5.0 19 | Bottlenose::Application.config.secret_token = File.open(key_file).read 20 | else 21 | Bottlenose::Application.config.secret_key_base = File.open(key_file).read 22 | end 23 | -------------------------------------------------------------------------------- /script/dredge.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | use 5.10.0; 3 | use Cwd; 4 | 5 | my $string = shift or die "Usage:\n $0 'string'\n"; 6 | 7 | die "Run me in rails root.\n" unless (-d 'public/submissions'); 8 | my $RAILS = getcwd(); 9 | 10 | say "Searching tarballs in submisison directory for string '$string'..."; 11 | 12 | my $files = `find public/submissions -name "*gz"`; 13 | 14 | my $TMP = "/tmp/dredge.$$.tmp"; 15 | mkdir $TMP; 16 | 17 | for my $path (split /\n/, $files) { 18 | $path =~ m{public/submissions/([^/]+)/}; 19 | my $secret = $1; 20 | 21 | mkdir "$TMP/$secret"; 22 | chdir "$TMP/$secret"; 23 | 24 | system(qq{tar xzvf "$RAILS/$path"}); 25 | 26 | my $results = `find . -exec grep -nH "$string" {} \\\;`; 27 | if ($results) { 28 | say "Found something in $path:"; 29 | say $results; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/helpers/exams_schema.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | start: Questions 3 | Questions: 4 | array: Question 5 | Question: 6 | one_of: 7 | - SingleQuestion 8 | - PartQuestion 9 | SingleQuestion: 10 | object: 11 | - key: "name" 12 | value: String 13 | optional: true 14 | - key: "weight" 15 | value: Float 16 | commit: true 17 | - key: "extra" 18 | value: Boolean 19 | optional: true 20 | PartQuestion: 21 | object: 22 | - key: "name" 23 | value: String 24 | optional: true 25 | - key: "parts" 26 | value: Parts 27 | commit: true 28 | Parts: 29 | array: Part 30 | minSize: 1 31 | Part: 32 | object: 33 | - key: "weight" 34 | value: Float 35 | - key: "name" 36 | value: String 37 | optional: true 38 | - key: "extra" 39 | value: Boolean 40 | optional: true 41 | ... 42 | -------------------------------------------------------------------------------- /app/views/graders/_racket_style_grader.html.erb: -------------------------------------------------------------------------------- 1 | <%= f.hidden_field :type, value: "RacketStyleGrader" %> 2 | <%= f.hidden_field :order %> 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | <%= render partial: "graders/enable_orca", locals: { f: f } %> 16 |
    <%= f.label "avail_score", "Points available:" %><%= f.spinner "avail_score", f.object.avail_score || 50, :min => 0, :delta => 5 %><%= f.check_box :extra_credit, data: {toggle: "toggle", on: "Extra credit", off: "Regular"} %>
    <%= f.label "line_length", "Maximum line length:" %><%= f.spinner "line_length", f.object.line_length || f.object.default_line_length, :min => 0 %>
    17 | -------------------------------------------------------------------------------- /app/views/assignments/_submission_enabled_toggles.html.erb: -------------------------------------------------------------------------------- 1 | <% if @toggles && !@toggles.empty? %> 2 |

    Toggle section availability:

    3 | <% @toggles.sort_by(&:first).each do |section_type, toggles| %> 4 |
    5 |

    By <%= section_type %>

    6 | <% toggles.sort_by{|s, _| s.crn}.each do |section, toggle| %> 7 |

    8 | 9 | <%= check_box_tag "allow-#{toggle.id}", nil, 10 | toggle.submissions_allowed, 11 | data: {toggle: "toggle", on: "Enabled", off: "Disabled", stid: toggle.id}, 12 | class: "submission-enabled-toggle" %> 13 | 14 | <%= section.to_s(show_type: false) %> 15 |

    16 | <% end %> 17 |
    18 | <% end %> 19 | 20 | 23 | <% end %> 24 | -------------------------------------------------------------------------------- /app/views/assignments/show_files.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "#{@course.name} / #{@gradesheet.assignment.name}" %> 2 | 3 | <% cur_reg = current_user.registration_for(@course) %> 4 | <% if cur_reg&.professor? %> 5 |

    6 | <%= link_to "Show student access", 7 | audit_access_course_assignment_path(@course, @gradesheet.assignment), class: "btn btn-default" %> 8 | <%= link_to "Manage individual extensions", 9 | edit_course_assignment_extensions_path(@course, @gradesheet.assignment), class: "btn btn-default" %> 10 | <%= link_to "Edit Assignment", 11 | edit_course_assignment_path(@course, @gradesheet.assignment), class: "btn btn-default" %> 12 |

    13 | <% end %> 14 | 15 | <%= render 'assignment_files_info', gradesheet: @gradesheet %> 16 | <%= render 'admin_subs', type: { noun: "submission", verb: "submit", gerund: "submitting" }, locals: { cur_reg: cur_reg } %> 17 | -------------------------------------------------------------------------------- /script/fix-quoted-names: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # Fixes names that imported poorly. 4 | # Only looks at students in a specific course, specified below. 5 | # Changes the format \"Last,First Mid\" to First Last 6 | 7 | APP_PATH = File.expand_path('../../config/application', __FILE__) 8 | require File.expand_path('../../config/boot', __FILE__) 9 | require APP_PATH 10 | Rails.application.require_environment! 11 | 12 | COURSE_ID = 8 13 | 14 | students = Course.find_by_id(COURSE_ID).students 15 | 16 | students.each do |s| 17 | puts "\n**** " + s.name 18 | if s.name =~ /\".*,.*\"/ 19 | name = s.name[1..-2] 20 | (last, first) = name.split(",") 21 | 22 | newname = first + " " + last 23 | puts newname 24 | s.name = newname 25 | 26 | begin 27 | s.save! 28 | puts "saved" 29 | rescue 30 | puts "NOT SAVED" 31 | end 32 | end 33 | end 34 | 35 | exit(0) 36 | 37 | -------------------------------------------------------------------------------- /app/controllers/oauth/authorizations_controller.rb: -------------------------------------------------------------------------------- 1 | module Oauth 2 | class AuthorizationsController < Doorkeeper::AuthorizationsController 3 | # Note: Does NOT allow impersonating users so that logging into Hourglass (or other OAuth apps) is disjoint from 4 | # Bottlenose's impersonation status 5 | 6 | # NOTE: In Rails 7, redirect_to will throw an error if we don't allow_other_host. 7 | # Unfortunately, Doorkeeper 5.4.0 doesn't supply that option right now, but upgrading 8 | # to new versions seems to be backwards incompatible for some users(?) 9 | # For now, monkey-patch this call of redirect_to to add in the necessary flag. 10 | # Once we successfully upgrade Doorkeeper, we can remove this patch. 11 | def redirect_to(options = {}, response_options = {}) 12 | response_options[:allow_other_host] = true; 13 | super(options, response_options) 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /script/dump_comments.rb: -------------------------------------------------------------------------------- 1 | APP_PATH = File.expand_path('../../config/application', __FILE__) 2 | require File.expand_path('../../config/boot', __FILE__) 3 | require APP_PATH 4 | Rails.application.require_environment! 5 | 6 | require 'active_record' 7 | 8 | InlineComment 9 | .joins(:submission).where("submissions.assignment_id": ARGV[0].to_i) 10 | .joins(:grade).where("grades.grader_id": ARGV[1].to_i) 11 | .group_by(&:submission) 12 | .each do |sub, cs| 13 | if ARGV[2] 14 | match = ARGV[2].downcase 15 | cs = cs.keep_if{|c| c.comment.downcase.include?(match)} 16 | end 17 | next if cs.empty? 18 | print "#{sub.team&.to_s || sub.user.name} -- Sub ##{sub.id}:\n" 19 | cs.each do |c| 20 | print "#{c.filename.gsub(Regexp.new('.*extracted/?'), '')}, #{c.line}: #{c.label}, #{c.severity} [#{c.weight}]: #{c.comment}\n" 21 | end 22 | print "==================================================\n\n" 23 | end 24 | -------------------------------------------------------------------------------- /db/migrate/20171121152503_make_crns_nonunique.rb: -------------------------------------------------------------------------------- 1 | class MakeCrnsNonunique < ActiveRecord::Migration[5.1] 2 | def up 3 | remove_index :sections, [:crn] 4 | add_index :sections, [:crn, :course_id], unique: true 5 | # this migration is safe only because all CRNS > 10000, and all ids < 100 (so far) 6 | Section.all.each do |section| 7 | RegistrationSection.where(section_id: section.crn).update_all(section_id: section.id) 8 | RegRequestSection.where(section_id: section.crn).update_all(section_id: section.id) 9 | end 10 | end 11 | def down 12 | remove_index :sections, [:crn, :course_id] 13 | add_index :sections, [:crn], unique: true 14 | Section.all.each do |section| 15 | RegistrationSection.where(section_id: section.id).update_all(section_id: section.crn) 16 | RegRequestSection.where(section_id: section.id).update_all(section_id: section.crn) 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/controllers/settings_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SettingsControllerTest < ActionController::TestCase 4 | setup do 5 | @admin = create(:admin_user) 6 | @prof = create(:user) 7 | end 8 | 9 | test "non-admin should not get settings" do 10 | sign_in @prof 11 | get :edit 12 | assert_response :redirect 13 | end 14 | 15 | test "index should show defaults" do 16 | Settings.clear_test! 17 | 18 | sign_in @admin 19 | get :edit 20 | assert_response :success 21 | assert_match "noreply@example.com", @response.body 22 | end 23 | 24 | test "should save_settings" do 25 | sign_in @admin 26 | post :update, params: { site_email: "somebody@example.com", backup_login: "" } 27 | 28 | assert_response :redirect 29 | assert_match "Settings Saved", flash[:notice] 30 | assert_equal "somebody@example.com", Settings['site_email'] 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /app/javascript/packs/application.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console:0 */ 2 | // This file is automatically compiled by Webpack, along with any other files 3 | // present in this directory. You're encouraged to place your actual application logic in 4 | // a relevant structure within app/javascript and only use these pack files to reference 5 | // that code so it'll be compiled. 6 | // 7 | // To reference this file, add <%= javascript_pack_tag 'application' %> to the appropriate 8 | // layout file, like app/views/layouts/application.html.erb 9 | 10 | 11 | // Uncomment to copy all static images under ../images to the output folder and reference 12 | // them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>) 13 | // or the `imagePath` JavaScript helper below. 14 | // 15 | // const images = require.context('../images', true) 16 | // const imagePath = (name) => images(name, true) 17 | 18 | console.log('Hello World from Webpacker') 19 | -------------------------------------------------------------------------------- /app/views/grades/_exam_export_schema.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    A CSV file with the following columns:

    4 |
     5 | NUID | Last name | First name | Status | Action | <%= @assignment.flattened_questions.map{|q| q["name"]}.join(" | ") %> | Curved
     6 | 
    7 |
      8 |
    • The downloaded file will have column headers as its first row.
    • 9 |
    • Each column header must be quoted, separated by commas, and there 10 | must not be spaces between the column headers.
    • 11 |
    • NOTE: Any students who are missing NUIDs cannot have their grades uploaded.
    • 12 |
    • The Status column may either be blank, Missing, or Dropped.
    • 13 |
    • Columns 6 through <%= @assignment.flattened_questions.count + 5 %> 14 | are the grades of individual question parts.
    • 15 |
    • The Curved grade column may be blank.
    • 16 |
    17 |
    18 |
    19 | -------------------------------------------------------------------------------- /app/views/graders/_python_style_grader.html.erb: -------------------------------------------------------------------------------- 1 | <%= f.hidden_field :type, value: "PythonStyleGrader" %> 2 | <%= f.hidden_field :order %> 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | <%= render "graders/grader_file_picker", f: f, id: f.object.id.to_s, 16 | label: "Custom configuration:", schema: "python-style-schema" %> 17 |
    <%= f.label "avail_score", "Points available:" %><%= f.spinner "avail_score", f.object.avail_score || 50, :min => 0, :delta => 5 %><%= f.check_box :extra_credit, data: {toggle: "toggle", on: "Extra credit", off: "Regular"} %>
    <%= f.label "line_length", "Maximum line length:" %><%= f.spinner "line_length", f.object.line_length || f.object.default_line_length, :min => 0 %>
    18 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | include FileUtils 4 | 5 | # path to your application root. 6 | APP_ROOT = File.expand_path('..', __dir__) 7 | 8 | def system!(*args) 9 | system(*args) || abort("\n== Command #{args} failed ==") 10 | end 11 | 12 | chdir APP_ROOT do 13 | # This script is a way to update your development environment automatically. 14 | # Add necessary update steps to this file. 15 | 16 | puts '== Installing dependencies ==' 17 | system! 'gem install bundler --conservative' 18 | system('bundle check') || system!('bundle install') 19 | 20 | # Install JavaScript dependencies if using Yarn 21 | # system('bin/yarn') 22 | 23 | puts "\n== Updating database ==" 24 | system! 'bin/rails db:migrate' 25 | 26 | puts "\n== Removing old logs and tempfiles ==" 27 | system! 'bin/rails log:clear tmp:clear' 28 | 29 | puts "\n== Restarting application server ==" 30 | system! 'bin/rails restart' 31 | end 32 | -------------------------------------------------------------------------------- /app/views/assignments/show_user_files.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "#{@course.name} / #{@gradesheet.assignment.name} / Submissions for #{@user.display_name}" %> 2 | 3 | <%= render 'assignment_files_info', gradesheet: @gradesheet %> 4 |

    5 | Submissions 6 | <% if @user.id != current_user.id %> 7 | for <%= @user.name %> 8 | <% else %> 9 | (<%= @gradesheet.submissions.count %>) 10 | <% end %> 11 | <% if @user.id == current_user.id %> 12 | <% confirmation = interlock_confirmation(@assignment) %> 13 | <% if true %> 14 | <%= link_to "New Submission", 15 | new_course_assignment_submission_path(@course, @gradesheet.assignment), 16 | class: "btn btn-success", data: confirmation %> 17 | <% else %> 18 |
    Submissions are temporarily disabled; they will resume later today.
    19 | <% end %> 20 | <% end %> 21 |

    22 | <%= render 'user_subs', user: @user, course: @course, gradesheet: @gradesheet %> 23 | -------------------------------------------------------------------------------- /app/views/courses/_section.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%= f.hidden_field :id %> 4 | <%= f.text_field :crn, class: 'form-control numeric' %> 5 | 6 | <%= f.text_field :prof_name, class: 'form-control' %> 7 | 8 | (<%= f.object.instructor&.name || "no one" %>) 9 | 10 | 11 | 12 | <%= f.text_field :meeting_time, class: 'form-control' %> 13 | 14 | 15 | <%= f.select :type, Section.types.to_a.map{|t| [t[0].humanize, t[0]]} %> 16 | 17 | 18 | <% if f.object.nonempty_section? %> 19 | 20 | <% else %> 21 | <%= link_to_remove_association "Remove", f, class: 'btn btn-danger' %> 22 | <% end %> 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/controllers/api/registrations_controller.rb: -------------------------------------------------------------------------------- 1 | module Api 2 | class RegistrationsController < ApiController 3 | before_action :find_course 4 | before_action :require_admin_or_prof 5 | 6 | def index 7 | registrations_by_section = @course.sections.includes(users: [:registrations]).map do |sec| 8 | [ 9 | sec.id, 10 | { 11 | title: sec.to_s, 12 | students: sec.students.map do |s| 13 | serialize_user(s) 14 | end, 15 | graders: sec.graders.map do |g| 16 | serialize_user(g) 17 | end, 18 | assistants: sec.assistants.map do |a| 19 | serialize_user(a) 20 | end, 21 | professors: sec.professors.map do |p| 22 | serialize_user(p) 23 | end 24 | } 25 | ] 26 | end.to_h 27 | render json: registrations_by_section 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /app/views/errors/internal_server_error.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "Well that's odd..." %> 2 | <% @bgcolor = "#cdb38b" %> 3 | <%= render "layouts/navbar" %> 4 | 5 | <%= render "layouts/flash" %> 6 | 7 |
    8 | 9 | <%= image_tag "failure-option.jpg" %> 10 | 11 |

    Something has gone wrong.

    12 |
     
    13 |
    14 |

    15 | If you think you've reached this page in error, please report the 16 | URL of the page you were on, the time you visited it, and what you 17 | were trying to do, and we'll look into it. 18 |

    19 |

    20 | You can resume your work by using the Back button, or by selecting 21 | an option from the menubar above. 22 |

    23 |
    24 | <%= image_tag "dolphin.png", style: "transform: rotate(20deg);" %> 25 |
    26 | <%= render "layouts/footer" %> 27 | -------------------------------------------------------------------------------- /app/views/submissions/_scoring_common.js.erb: -------------------------------------------------------------------------------- 1 | $(function() { 2 | const gradesWithOrcaIDs = JSON.parse('<%= raw grades_with_orca_job_status %>'); 3 | orcaURL = '<%= Grader.orca_config['site_url'][Rails.env] %>'; 4 | gradesWithOrcaIDs.forEach(({gradeStatusDomId, job_status}) => { 5 | const { location, id: orca_id } = job_status; 6 | if (location === "Worker") { 7 | return $(`#${gradeStatusDomId}`).text("This job is currently being graded."); 8 | } 9 | const statusURL = `${orcaURL}/api/v1/${location === "HoldingPen" ? "holding_pen" : "grading_queue"}/${orca_id}/status`; 10 | $.ajax({ 11 | url: statusURL, 12 | type: 'GET', 13 | dataType: 'json', 14 | success: function (data) { 15 | $(`#${gradeStatusDomId}`).text(data); 16 | }, 17 | error: function (err) { 18 | $(`#${gradeStatusDomId}`).text(`Could not fetch orca status; ${err.status} ${err.statusText}`); 19 | } 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /app/views/layouts/_spinner.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_tag :span, { "class" => "input-group spinner" }.update(options[:wrapper].stringify_keys) do %> 2 | <%= text_field_tag name, value, 3 | { "data-min" => options[:min], "data-max" => options[:max], "data-delta" => options[:delta], 4 | "disabled" => options[:disabled], "class" => "form-control"}.update(options[:text].stringify_keys) %> 5 | 6 | <%= content_tag :button, 7 | { "type" => "button", "disabled" => options[:disabled], "tabindex" => "-1", "class" => "btn btn-default" }.update(options[:buttons].stringify_keys) do %> 8 | 9 | <% end %> 10 | <%= content_tag :button, 11 | { "type" => "button", "disabled" => options[:disabled], "tabindex" => "-1", "class" => "btn btn-default" }.update(options[:buttons].stringify_keys) do %> 12 | 13 | <% end %> 14 | 15 | <% end %> 16 | -------------------------------------------------------------------------------- /config/initializers/backburner.rb: -------------------------------------------------------------------------------- 1 | Backburner.configure do |config| 2 | config.beanstalk_url = "beanstalk://127.0.0.1" 3 | config.tube_namespace = "bottlenose.#{Rails.env}" 4 | config.namespace_separator = "." 5 | config.on_error = lambda { |e| puts e } 6 | config.max_job_retries = 1 # default 0 retries 7 | config.retry_delay = 10 # default 5 seconds 8 | config.retry_delay_proc = lambda { |min_retry_delay, num_retries| min_retry_delay + (num_retries ** 3) } 9 | config.default_priority = 1024 10 | config.respond_timeout = 240 11 | config.default_worker = Backburner::Workers::Threading 12 | config.logger = Logger.new(STDOUT) 13 | config.primary_queue = "backburner-jobs" 14 | config.priority_labels = { :high => 50, :low => 1000 } 15 | config.reserve_timeout = nil 16 | config.job_serializer_proc = lambda { |body| JSON.dump(body) } 17 | config.job_parser_proc = lambda { |body| JSON.parse(body) } 18 | end 19 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at https://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /app/assets/javascripts/teamsets.js.coffee: -------------------------------------------------------------------------------- 1 | setup_form = () -> 2 | $('.add-user-btn').click (e) -> 3 | row = $(e.target).closest('tr.user') 4 | opt = $("") 5 | .attr("value", row.data().id) 6 | .text(row.data().name) 7 | $('#users').append(opt) 8 | row.hide() 9 | 10 | $('.remove-users-btn').click (e) -> 11 | $('#users option').filter((_,e) -> e.selected).each (i, opt) -> 12 | opt.selected = false 13 | $(opt).detach() 14 | $("#users [data-id='#{opt.value}']").show() 15 | 16 | $('#submit-btn').click (ev) -> 17 | $('#users option').each (i, opt) -> 18 | opt.selected = true 19 | 20 | $("#existingTS").on("change", () -> 21 | $(".teamset-div").addClass("hidden") 22 | $("#ts_" + $(this).val()).removeClass("hidden") 23 | ).change() 24 | 25 | 26 | 27 | run_on_page "teamsets/create", setup_form 28 | run_on_page "teamsets/new", setup_form 29 | run_on_page "teamsets/edit", setup_form 30 | run_on_page "teamsets/bulk_enter", setup_form 31 | -------------------------------------------------------------------------------- /db/migrate/20170718000106_create_review_feedbacks.rb: -------------------------------------------------------------------------------- 1 | class CreateReviewFeedbacks < ActiveRecord::Migration[5.1] 2 | def change 3 | create_table :review_feedbacks do |t| 4 | t.integer "grade_id" 5 | t.integer "submission_id", null: false 6 | t.integer "review_submission_id", null: false 7 | t.integer "upload_id", null: false 8 | t.float "score" 9 | t.float "out_of" 10 | t.datetime "updated_at" 11 | t.index ["submission_id"], name: "index_review_feedbacks_on_submission_id" 12 | t.index ["review_submission_id"], name: "index_review_feedbacks_on_review_submission_id" 13 | end 14 | create_table :interlocks do |t| 15 | t.integer "assignment_id", null: false 16 | t.integer "related_assignment_id", null: false 17 | t.string "constraint", null: false 18 | t.index ["assignment_id"], name: "index_interlocks_on_assignment_id" 19 | t.index ["related_assignment_id"], name: "index_interlocks_on_related_assignment_id" 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /db/migrate/20171210143444_make_matchings_unique.rb: -------------------------------------------------------------------------------- 1 | class MakeMatchingsUnique < ActiveRecord::Migration[5.1] 2 | def change 3 | add_index "codereview_matchings", [:assignment_id, :user_id, :target_user_id], 4 | where: "(team_id IS NULL AND target_team_id IS NULL)", 5 | unique: true, name: "unique_user_user_matchings" 6 | add_index "codereview_matchings", [:assignment_id, :user_id, :target_team_id], 7 | where: "(team_id IS NULL AND target_user_id IS NULL)", 8 | unique: true, name: "unique_user_team_matchings" 9 | add_index "codereview_matchings", [:assignment_id, :team_id, :target_user_id], 10 | where: "(user_id IS NULL AND target_team_id IS NULL)", 11 | unique: true, name: "unique_team_user_matchings" 12 | add_index "codereview_matchings", [:assignment_id, :team_id, :target_team_id], 13 | where: "(user_id IS NULL AND target_user_id IS NULL)", 14 | unique: true, name: "unique_team_team_matchings" 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/views/assignments/show_user_questions.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "#{@course.name} / #{@gradesheet.assignment.name} / Responses for #{@user.display_name}" %> 2 | <% cur_reg = current_user.registration_for(@course) %> 3 | 4 | <%= render 'assignment_questions_info', gradesheet: @gradesheet, 5 | cur_reg: cur_reg %> 6 |

    7 | Responses 8 | <% if @user.id != current_user.id %> 9 | for <%= @user.name %> 10 | <% else %> 11 | (<%= @gradesheet.submissions.count %>) 12 | <% end %> 13 | <% if @user.id == current_user.id %> 14 | <% confirmation = interlock_confirmation(@assignment) %> 15 | <% if true %> 16 | <%= link_to "New Response", 17 | new_course_assignment_submission_path(@course, @gradesheet.assignment), 18 | class: "btn btn-success", data: confirmation %> 19 | <% else %> 20 |
    Submissions are temporarily disabled; they will resume later today.
    21 | <% end %> 22 | <% end %> 23 |

    24 | <%= render 'user_subs', user: @user, course: @course, gradesheet: @gradesheet %> 25 | -------------------------------------------------------------------------------- /app/views/layouts/errors.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= csrf_meta_tags %> 7 | 8 | 9 | <% default_page_title = "#{params[:controller].humanize} / #{params[:action].humanize}" %> 10 | <%= @page_title || default_page_title %> - Bottlenose Course Manager 11 | 12 | 13 | <%= favicon_link_tag 'site-icon.png', :type => 'image/png' %> 14 | <%= favicon_link_tag 'site-icon.png', :rel => 'apple-touch-icon', :type => 'image/png' %> 15 | 16 | 17 | <%= stylesheet_link_tag "application", :media => "all" %> 18 | <%= javascript_include_tag "application" %> 19 | 20 | 21 | style="background-color: <%= @bgcolor %>;"<% end %>> 22 | <%= content_for?(:content) ? yield(:content) : yield %> 23 | 24 | 25 | -------------------------------------------------------------------------------- /db/migrate/20180801182418_add_assignment_to_uploads.rb: -------------------------------------------------------------------------------- 1 | class AddAssignmentToUploads < ActiveRecord::Migration[5.2] 2 | def up 3 | add_column :uploads, :assignment_id, :integer, default: 0, null: false 4 | auids = Assignment.all.group_by(&:assignment_upload_id) 5 | suids = Submission.all.group_by(&:upload_id) 6 | cuids = Submission.all.group_by(&:comments_upload_id) 7 | guids = Grader.all.group_by(&:upload_id) 8 | 9 | Upload.all.each do |u| 10 | a = auids[u.id] 11 | s = suids[u.id] 12 | c = cuids[u.id] 13 | g = guids[u.id] 14 | if !a.blank? 15 | u.assignment_id = a[0].id 16 | elsif !s.blank? 17 | u.assignment_id = s[0].assignment_id 18 | elsif !c.blank? 19 | u.assignment_id = c[0].assignment_id 20 | elsif !g.blank? 21 | u.assignment_id = g[0].assignment_id 22 | end 23 | u.save 24 | end 25 | add_index :uploads, [:assignment_id], unique: false 26 | end 27 | def down 28 | remove_column :uploads, :assignment_id 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: 65fa08e28cd1695f8d127b9edba91beb42a4b41802426511200a2f045f009ae5a0f9513a2bc15c15c64b4804181b18ea28471960f86e482b6bbc1d99c3562666 15 | 16 | test: 17 | secret_key_base: 69777b738997acec0ee62bd9daeeea236db9f537befd572321f04130eb697892bd192cb1f488c6561b2361aeee6c69bc0e8b3ab40a46da4cf9bf91ea38110109 18 | 19 | # Do not keep production secrets in the repository, 20 | # instead read values from the environment. 21 | production: 22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 23 | -------------------------------------------------------------------------------- /test/integration/show_hide_username_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ShowHideUsernameTest < ActionDispatch::IntegrationTest 4 | include Devise::Test::IntegrationHelpers 5 | setup do 6 | Capybara.default_driver = :selenium_chrome_headless 7 | make_standard_course 8 | @pset = build(:assignment, course: @cs101, name: "Assignment 1", teamset: @ts1, blame: @fred) 9 | @pset.save! 10 | @sub = make_submission(@john, @pset, "sample.rkt") 11 | @sub.set_used_everyone! 12 | end 13 | 14 | test "professor checking if show and hide of username works on assignments page" do 15 | sign_in @fred 16 | visit course_assignment_path(@cs101, @pset) 17 | assert page.has_selector?(:css, '#show_username') 18 | assert_equal 0, find_all(:css, '.username', visible: true).count() 19 | click_button('show_username') 20 | assert_equal 0, find_all(:css, '.username', visible: :hidden).count() 21 | click_button('show_username') 22 | assert_equal 1, find_all(:css, '.username', visible: :hidden).count() 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /db/migrate/20170710141402_rename_assignment_types.rb: -------------------------------------------------------------------------------- 1 | class RenameAssignmentTypes < ActiveRecord::Migration[5.1] 2 | def up 3 | ActiveRecord::Base.connection.execute(%Q{ 4 | update assignments set type = 'Exam' where type = 'exam' 5 | }) 6 | ActiveRecord::Base.connection.execute(%Q{ 7 | update assignments set type = 'Files' where type = 'files' 8 | }) 9 | ActiveRecord::Base.connection.execute(%Q{ 10 | update assignments set type = 'Questions' where type = 'questions' 11 | }) 12 | change_column_default :assignments, :type, nil 13 | end 14 | 15 | def down 16 | ActiveRecord::Base.connection.execute(%Q{ 17 | update assignments set type = 'exam' where type = 'Exam' 18 | }) 19 | ActiveRecord::Base.connection.execute(%Q{ 20 | update assignments set type = 'files' where type = 'Files' 21 | }) 22 | ActiveRecord::Base.connection.execute(%Q{ 23 | update assignments set type = 'questions' where type = 'Questions' 24 | }) 25 | change_column_default :assignments, :type, "files" 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /app/controllers/oauth/applications_controller.rb: -------------------------------------------------------------------------------- 1 | module Oauth 2 | class ApplicationsController < Doorkeeper::ApplicationsController 3 | # Allow an admin to impersonate anyone to audit their OAuth apps 4 | impersonates :user 5 | 6 | def index 7 | @applications = 8 | if current_user.site_admin? 9 | Doorkeeper::Application.all 10 | else 11 | current_user.oauth_applications 12 | end 13 | end 14 | 15 | def create 16 | @application = Doorkeeper::Application.new(application_params) 17 | @application.owner = current_user if Doorkeeper.configuration.confirm_application_owner? 18 | if @application.save 19 | flash[:notice] = I18n.t(:notice, :scope => [:doorkeeper, :flash, :applications, :create]) 20 | redirect_to oauth_application_url(@application) 21 | else 22 | render :new 23 | end 24 | end 25 | 26 | private 27 | 28 | def set_application 29 | @application = current_user.oauth_applications.find(params[:id]) 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /db/migrate/20220224011641_create_grading_conflicts.rb: -------------------------------------------------------------------------------- 1 | class CreateGradingConflicts < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :grading_conflicts do |t| 4 | t.references :staff, null: false 5 | t.references :student, null: false 6 | t.references :course, foreign_key: true, null: false 7 | t.integer :status, null: false, default: 0 8 | t.timestamps 9 | end 10 | 11 | create_table :grading_conflict_audits do |t| 12 | t.references :grading_conflict, null: false, foreign_key: true 13 | t.references :user, null:false, foreign_key: true 14 | t.string :reason, null: true 15 | t.timestamps 16 | t.integer :status, null: false, default: 0 17 | end 18 | 19 | add_foreign_key :grading_conflicts, :users, column: :staff_id, primary_key: :id 20 | add_foreign_key :grading_conflicts, :users, column: :student_id, primary_key: :id 21 | add_index :grading_conflicts, [:staff_id, :student_id, :course_id], unique: true, name: "index_grading_conflict_uniqueness" 22 | 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /test/integration/add_user_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AddUserTest < ActionDispatch::IntegrationTest 4 | setup do 5 | make_standard_course 6 | end 7 | 8 | test "add a student" do 9 | skip 10 | 11 | # Log in as a professor 12 | visit "/main/auth?email=#{@fred.email}&key=#{@fred.auth_key}" 13 | 14 | assert has_content?("Logged in as #{@fred.name}") 15 | 16 | click_link 'Your Courses' 17 | click_link @cs101.name 18 | first(:link, 'Manage Registrations').click 19 | 20 | assert has_content?("Add a Student or Teacher") 21 | 22 | # Add a new student. 23 | fill_in 'Email', :with => 'steve@example.com' 24 | fill_in 'Name', :with => 'Steve McTest' 25 | click_button 'Create Registration' 26 | assert has_content?("Registration was successfully created.") 27 | 28 | # Verify that student was added. 29 | @steve = User.find_by_email('steve@example.com') 30 | assert_not_nil @steve 31 | assert_equal 'Steve McTest', @steve.name 32 | assert_equal 1, @steve.registrations.size 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /db/migrate/20170713123707_rename_submission_types.rb: -------------------------------------------------------------------------------- 1 | class RenameSubmissionTypes < ActiveRecord::Migration[5.1] 2 | def up 3 | ActiveRecord::Base.connection.execute(%Q{ 4 | update submissions set type = 'ExamSub' where type = 'Exam' 5 | }) 6 | ActiveRecord::Base.connection.execute(%Q{ 7 | update submissions set type = 'FilesSub' where type = 'Files' 8 | }) 9 | ActiveRecord::Base.connection.execute(%Q{ 10 | update submissions set type = 'QuestionsSub' where type = 'Questions' 11 | }) 12 | change_column_default :submissions, :type, nil 13 | end 14 | 15 | def down 16 | ActiveRecord::Base.connection.execute(%Q{ 17 | update submissions set type = 'Exam' where type = 'ExamSub' 18 | }) 19 | ActiveRecord::Base.connection.execute(%Q{ 20 | update submissions set type = 'Files' where type = 'FilesSub' 21 | }) 22 | ActiveRecord::Base.connection.execute(%Q{ 23 | update submissions set type = 'Questions' where type = 'QuestionsSub' 24 | }) 25 | change_column_default :submissions, :type, "Files" 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /app/views/graders/build_log.html.erb: -------------------------------------------------------------------------------- 1 | 16 |

    Detailed Orca Build Logs

    17 | <% if @logs.length == 1 && @logs.first.is_a?(String) %> 18 |

    <%= @logs.first %>

    19 | <% else %> 20 |
      21 | <% @logs.each do |log| %> 22 |
    1. 23 | <% if log["error"] %> 24 |
      25 |

      <%= log["step"] %>

      26 |
      <%= log["error"] %>
      27 |
      28 | <% else %> 29 |

      <%= log["step"] %>

      30 |
      <%= log["output"] %>
      31 | <% end %> 32 |
    2. 33 | <% end %> 34 |
    35 | <% end %> 36 | -------------------------------------------------------------------------------- /app/views/submissions/_code_tag.html.erb: -------------------------------------------------------------------------------- 1 |

    2 | File: 12 | Line number: value="<%= part_ans['line'] %>"<% end %>> 16 |

    17 | -------------------------------------------------------------------------------- /app/views/assignments/_assignment_file_picker.html.erb: -------------------------------------------------------------------------------- 1 | <% disabled = false if local_assigns[:disabled].nil? %> 2 |

    3 | 7 | <%= f.hidden_field :removefile, class: "remove-custom-file", value: '' %> 8 | Current file: 9 | <%= asgn.assignment_file.empty? ? "nothing" : 10 | link_to(asgn.assignment_file, asgn.assignment_upload.path) %> 11 | 17 |

    18 | -------------------------------------------------------------------------------- /db/migrate/20220426161610_change_primary_key_to_bigint.rb: -------------------------------------------------------------------------------- 1 | class ChangePrimaryKeyToBigint < ActiveRecord::Migration[5.2] 2 | def change 3 | change_column :assignments, :id, :bigint 4 | change_column :courses, :id, :bigint 5 | change_column :grader_allocations, :id, :bigint 6 | change_column :graders, :id, :bigint 7 | change_column :grades, :id, :bigint 8 | change_column :inline_comments, :id, :bigint 9 | change_column :lateness_configs, :id, :bigint 10 | change_column :reg_requests, :id, :bigint 11 | change_column :registrations, :id, :bigint 12 | change_column :sandboxes, :id, :bigint 13 | change_column :sections, :id, :bigint 14 | change_column :submissions, :id, :bigint 15 | change_column :team_users, :id, :bigint 16 | change_column :teams, :id, :bigint 17 | change_column :teamsets, :id, :bigint 18 | change_column :terms, :id, :bigint 19 | change_column :uploads, :id, :bigint 20 | change_column :used_subs, :id, :bigint 21 | change_column :user_submissions, :id, :bigint 22 | change_column :users, :id, :bigint 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/views/assignments/_form.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | 8 | 9 |
    10 |
    11 | <%= render 'files_form', asgn: @files || @assignment %> 12 |
    13 |
    14 | <%= render 'questions_form', asgn: @quest || @assignment %> 15 |
    16 |
    17 | <%= render 'codereview_form', asgn: @codereview || @assignment %> 18 |
    19 |
    20 | <%= render 'exam_form', asgn: @exam || @assignment %> 21 |
    22 |
    23 | 26 |
    27 | -------------------------------------------------------------------------------- /db/migrate/20170531183453_make_teamsets_mandatory.rb: -------------------------------------------------------------------------------- 1 | class MakeTeamsetsMandatory < ActiveRecord::Migration[5.1] 2 | Assignment.inheritance_column = nil 3 | class TeamSet < ApplicationRecord 4 | belongs_to :course 5 | has_many :teams 6 | has_many :submissions, through: :teams 7 | has_many :assignments 8 | end 9 | def self.up 10 | courses_needing_teamsets = Course.find(Assignment.where(team_set_id: nil).map(&:course_id)) 11 | courses_needing_teamsets.each do |c| 12 | ts = TeamSet.create(course: c, name: "Initial teamset for #{c.name}") 13 | c.assignments.where(team_set_id: nil).each do |a| 14 | a.update_attribute(:team_set_id, ts.id) 15 | end 16 | c.teams.where(team_set_id: nil).each do |t| 17 | t.update_attribute(:team_set_id, ts.id) 18 | end 19 | end 20 | change_column_null :teams, :team_set_id, false 21 | change_column_null :assignments, :team_set_id, false 22 | end 23 | def self.down 24 | change_column_null :teams, :team_set_id, true 25 | change_column_null :assignments, :team_set_id, true 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /app/views/submissions/show_files.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "View Submission" %> 2 | <% cur_reg = current_user.registration_for(@course) %> 3 | 4 | <%= render "show_common", cur_reg: cur_reg, kind: "files", 5 | show_download: true %> 6 | 7 | <%= render "scoring_common", cur_reg: cur_reg %> 8 | 9 | <% show_hidden = (current_user.site_admin? || cur_reg.staff?) 10 | sub_comments = @submission.grade_submission_comments(show_hidden) 11 | if sub_comments.count > 0 %> 12 |

    General comments

    13 |
    14 |
    15 | <% sub_comments.each do |file, comments| %> 16 | <% file = file.gsub(@submission.upload.extracted_path.to_s, "") %> 17 |

    <%= if file == "" then "Overall" else file end %>:

    18 | <% comments.each do |c| %> 19 |
    "> 20 | - <%= to_fixed(c.weight) %> 21 | <%= c.grade&.grader&.display_type %> <%= c.title %>: <%= c.comment %> 22 |
    23 | <% end %> 24 | <% end %> 25 |
    26 |
    27 |

     

    28 | <% end %> 29 | -------------------------------------------------------------------------------- /app/views/teamsets/_diff_teamsets.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |
    5 | 10 | with 11 | 13 |
    14 |
    15 |
    16 |
    17 |
    18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
    Team 1StudentTeam 2
    29 |
    30 |
    31 |
    32 | 34 | -------------------------------------------------------------------------------- /app/models/codereview_matching.rb: -------------------------------------------------------------------------------- 1 | class CodereviewMatching < ApplicationRecord 2 | belongs_to :assignment 3 | belongs_to :user, optional: true 4 | belongs_to :team, optional: true 5 | belongs_to :target_user, class_name: "User", optional: true 6 | belongs_to :target_team, class_name: "Team", optional: true 7 | 8 | validates :assignment, presence: true 9 | validate :some_src 10 | validate :some_target 11 | 12 | def some_src 13 | if self.user_id.nil? && self.team_id.nil? 14 | self.errors.add(:base, "Must specify a reviewer user or team") 15 | end 16 | end 17 | def some_target 18 | if self.target_user_id.nil? && self.target_team_id.nil? 19 | self.errors.add(:base, "Must specify a target user or team") 20 | end 21 | end 22 | 23 | def to_s 24 | ans = "" 25 | if self.user_id 26 | ans += "User #{self.user_id}" 27 | else 28 | ans += "Team #{self.team_id}" 29 | end 30 | ans += " reviews " 31 | if self.target_user_id 32 | ans += "User #{self.target_user_id}" 33 | else 34 | ans += "Team #{self.target_team_id}" 35 | end 36 | ans 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "fileutils" 3 | 4 | # path to your application root. 5 | APP_ROOT = File.expand_path("..", __dir__) 6 | 7 | def system!(*args) 8 | system(*args) || abort("\n== Command #{args} failed ==") 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | # This script is a way to set up or update your development environment automatically. 13 | # This script is idempotent, so that you can run it at any time and get an expectable outcome. 14 | # Add necessary setup steps to this file. 15 | 16 | puts "== Installing dependencies ==" 17 | system! "gem install bundler --conservative" 18 | system("bundle check") || system!("bundle install") 19 | 20 | # puts "\n== Copying sample files ==" 21 | # unless File.exist?("config/database.yml") 22 | # FileUtils.cp "config/database.yml.sample", "config/database.yml" 23 | # end 24 | 25 | puts "\n== Preparing database ==" 26 | system! "bin/rails db:prepare" 27 | 28 | puts "\n== Removing old logs and tempfiles ==" 29 | system! "bin/rails log:clear tmp:clear" 30 | 31 | puts "\n== Restarting application server ==" 32 | system! "bin/rails restart" 33 | end 34 | -------------------------------------------------------------------------------- /app/controllers/graders_controller.rb: -------------------------------------------------------------------------------- 1 | class GradersController < ApplicationController 2 | before_action :require_site_admin 3 | before_action :find_course 4 | before_action :find_assignment 5 | before_action :find_grader 6 | 7 | def build_log 8 | unless @grader.orca_status 9 | return redirect_to course_assignment_path(@course, @assignment), 10 | alert: 'This grader does not use Orca' 11 | end 12 | @logs = @grader.latest_build_logs 13 | if @logs.nil? 14 | return redirect_to course_assignment_path(@course, @assignment), 15 | alert: 'This grader has no build logs from Orca' 16 | end 17 | render action: 'build_log' 18 | end 19 | 20 | def rebuild_orca_image 21 | @grader.send_build_request_to_orca 22 | redirect_to edit_course_assignment_path(@course, @assignment), notice: 'Requested Orca to rebuild this image.' 23 | end 24 | 25 | private 26 | 27 | def find_grader 28 | @grader = Grader.find_by_id params[:id] 29 | return unless @grader.nil? 30 | 31 | redirect_to course_assignment_path(@course, @assignment), alert: 'No such grader' 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /app/models/individual_extension.rb: -------------------------------------------------------------------------------- 1 | class IndividualExtension < ApplicationRecord 2 | belongs_to :user, optional: true 3 | belongs_to :team, optional: true 4 | belongs_to :assignment 5 | 6 | validate :user_or_team 7 | 8 | before_destroy do |ext| 9 | # Find any submissions that might've been impacted by this extension, 10 | # which have already been graded, and recompute their final scores 11 | if ext.user 12 | subs = ext.assignment.all_used_subs.where(user: ext.user).where.not(score: nil) 13 | else 14 | subs = ext.assignment.all_used_subs.where(team: ext.team).where.not(score: nil) 15 | end 16 | subs.each{|s| s.compute_grade!} 17 | end 18 | 19 | def to_s 20 | "User #{self.user_id || ''}/Team #{self.team_id || ''} can work on assignment #{self.assignment_id} until #{self.due_date} (instead of #{self.assignment.due_date})" 21 | end 22 | 23 | private 24 | def user_or_team 25 | if user.nil? && team.nil? 26 | errors.add(:base, "Either user or team must be set") 27 | elsif user && team 28 | errors.add(:base, "Exactly one of user or team must be set") 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bottlenose", 3 | "version": "1.0.0", 4 | "description": "Course hand-in server", 5 | "main": "index.js", 6 | "dependencies": { 7 | "@rails/webpacker": "5.4.3", 8 | "codemirror": "^5.59.1", 9 | "dompurify": "^2.2.6", 10 | "pdfjs-dist": "^2.5.207", 11 | "pyret-codemirror-mode": "git+https://github.com/brownplt/pyret-codemirror-mode.git#master", 12 | "webpack-cli": "^3.3.12" 13 | }, 14 | "devDependencies": { 15 | "@babel/plugin-proposal-private-methods": "^7.18.6", 16 | "@babel/plugin-proposal-private-property-in-object": "^7.21.11", 17 | "babel-preset-env": "^1.7.0", 18 | "webpack": "^4.46.0", 19 | "webpack-dev-server": "^3" 20 | }, 21 | "scripts": { 22 | "test": "echo \"Error: no test specified\" && exit 1" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/CodeGrade/bottlenose.git" 27 | }, 28 | "author": "Benjamin Lerner, Nat Tuck", 29 | "license": "AGPL-3.0", 30 | "bugs": { 31 | "url": "https://github.com/CodeGrade/bottlenose/issues" 32 | }, 33 | "homepage": "https://github.com/CodeGrade/bottlenose#readme" 34 | } 35 | -------------------------------------------------------------------------------- /app/views/graders/_grader_file_picker.html.erb: -------------------------------------------------------------------------------- 1 | 2 | <%= f.label "upload_file", label%> 3 | 4 | 7 | <%= f.hidden_field :removefile, class: "remove-custom-file", value: '' %> 8 | Current file: 9 | <%= f.object.upload_file.nil? ? "nothing" : 10 | link_to(f.object.upload_file, f.object.upload.path) %> 11 | 12 | 13 | 19 | <% if local_assigns[:schema] %> 20 | See schema 22 | <% end %> 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/views/main/status.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "Status" %> 2 | 3 |

    Bottlenose Grading Queue

    4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 35 | 36 |
    Queue length<%= @queue_stats.count %>
    Average wait<%= @avg_wait_msg %>
    Submissions 17 | <% if current_user&.site_admin? && (@queue_stats.count >= 50 || @avg_wait > 30 * 60) %> 18 |

    19 | <%= link_to "Clear all pending jobs", clear_queue_path, method: "patch", 20 | class: 'btn btn-large btn-danger' %> 21 |

    22 | <% end %> 23 | 24 | <% @queue_stats.each do |_, job| %> 25 |

    Grading <%= job[:grader_type] %> for <%= link_to "#{job[:user_name]}", 26 | course_assignment_submission_path(job[:course], job[:assn], job[:sub]) %> 27 | <% priority = job.dig(:opts, :prio) 28 | if priority %> 29 | with priority <%= priority %> 30 | <% end %> 31 | (<%= job[:wait_s] %>) 32 |

    33 | <% end %> 34 |
    37 | -------------------------------------------------------------------------------- /lib/assets/python-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "initial points": 50, 3 | "maximum deductions per file": 50, 4 | "E": { 5 | "description": "Coding", 6 | "deduction": 1, 7 | "severity": "Error", 8 | "maximumDeductions": 1000 9 | }, 10 | "E1": { 11 | "description": "Formatting (Indentation)" 12 | }, 13 | "E2": { 14 | "description": "Formatting (Whitespace)" 15 | }, 16 | "E3": { 17 | "description": "Formatting (Blank line)" 18 | }, 19 | "E4": { 20 | "description": "Coding (Imports)" 21 | }, 22 | "E5": { 23 | "description": "Formatting (Line length)" 24 | }, 25 | "E7": { 26 | "description": "Coding (Statements)" 27 | }, 28 | "E9": {}, 29 | "W": { 30 | "description": "Coding", 31 | "deduction": 1, 32 | "severity": "Warning", 33 | "maximumDeductions": 1000 34 | }, 35 | "W1": { 36 | "description": "Formatting (Indentation)" 37 | }, 38 | "W2": { 39 | "description": "Formatting (Whitespace)" 40 | }, 41 | "W3": { 42 | "description": "Formatting (Blank line)" 43 | }, 44 | "W5": { 45 | "description": "Formatting (Line break)" 46 | }, 47 | "W6": { 48 | "description": "Deprecation" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/views/courses/_table.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <% courses.each do |course| %> 10 | <% info = reg_info[course] %> 11 | 12 | 13 | <% if info %> 14 | <% if info[4] %> 15 | 16 | <% else %> 17 | 18 | <% end %> 19 | <% elsif current_user.reg_requests.map { |r| r.course }.include?(course) %> 20 | 21 | <% else %> 22 | 23 | <% end %> 24 | 25 | <% end %> 26 | 27 |
    Name
    <%= link_to course.name, course_path(course) %>Withdrawn<%= info[3].titleize %>Registration Pending...<%= link_to "Request Registration", new_course_reg_request_path(course), class: "btn btn-success btn-xs pull-right" %>
    28 | -------------------------------------------------------------------------------- /config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | # amazon: 11 | # service: S3 12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 14 | # region: us-east-1 15 | # bucket: your_own_bucket 16 | 17 | # Remember not to checkin your GCS keyfile to a repository 18 | # google: 19 | # service: GCS 20 | # project: your_project 21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 22 | # bucket: your_own_bucket 23 | 24 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 25 | # microsoft: 26 | # service: AzureStorage 27 | # storage_account_name: your_account_name 28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 29 | # container: your_container_name 30 | 31 | # mirror: 32 | # service: Mirror 33 | # primary: local 34 | # mirrors: [ amazon, google, microsoft ] 35 | -------------------------------------------------------------------------------- /db/migrate/20240828125219_change_orca_status_type.rb: -------------------------------------------------------------------------------- 1 | class ChangeOrcaStatusType < ActiveRecord::Migration[7.0] 2 | def up 3 | add_column :graders, :orca_jsonb_status, :jsonb, default: false, null: false 4 | Grader.reset_column_information 5 | Grader.where(orca_status: true).update_all(orca_jsonb_status: { 6 | current_build: { 7 | completed: true, 8 | succesful: true, 9 | build_time: DateTime.now 10 | }, 11 | last_build: nil 12 | }) 13 | remove_column :graders, :orca_status 14 | rename_column :graders, :orca_jsonb_status, :orca_status 15 | end 16 | 17 | def down 18 | rename_column :graders, :orca_status, :orca_jsonb_status 19 | add_column :graders, :orca_status, :boolean, default: false, null: false 20 | Grader.reset_column_information 21 | Grader.where.not(orca_jsonb_status: false).update_all(orca_status: true) 22 | remove_column :graders, :orca_jsonb_status 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/models/lateness_configs/late_per_hour_config.rb: -------------------------------------------------------------------------------- 1 | class LatePerHourConfig < LatenessConfig 2 | validates :days_per_assignment, :numericality => true 3 | validates :frequency, :numericality => true 4 | validates :max_penalty, :numericality => true 5 | validates :percent_off, :numericality => true 6 | 7 | def allow_submission?(assn, sub) 8 | days_late(assn, sub) <= self.days_per_assignment 9 | end 10 | 11 | def late_penalty(assn, sub) 12 | penalty = (self.percent_off || 0).to_f * hours_late(assn, sub).to_f 13 | penalty.clamp(0, self.max_penalty) 14 | end 15 | 16 | def to_s 17 | if self.days_per_assignment.nil? 18 | days_allowed = "unlimited late days" 19 | else 20 | days_allowed = pluralize(self.days_per_assignment, "late day") 21 | end 22 | 23 | "Allow #{days_allowed}, penalizing #{self.percent_off}% each hour up to #{self.max_penalty}%" 24 | end 25 | 26 | def ==(other) 27 | if other.instance_of?(LatePerHourConfig) 28 | self.days_per_assignment == other.days_per_assignment && 29 | self.frequency == other.frequency && 30 | self.max_penalty == other.max_penalty && 31 | self.percent_off == other.percent_off 32 | else 33 | false 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/json_parser.rb: -------------------------------------------------------------------------------- 1 | class JsonParser 2 | attr_reader :json, :test_count, :tests, :commentary, :passed_count, :time_taken, :output 3 | attr_reader :filename 4 | attr_reader :score, :max_score 5 | 6 | def initialize(json, filename=nil) 7 | @filename = filename 8 | @json = json 9 | @test_count = json['tests']&.count 10 | @output = json['output'] 11 | @tests = json['tests']&.map&.with_index do |t, num| 12 | t = t.clone 13 | weight = t.delete('weight') || t.delete('max_score') || t.delete('max-score') 14 | score = t.delete('score') 15 | passed = t.delete('passed') 16 | if weight.nil? && passed.nil? 17 | { 18 | num: num, 19 | score: t['score'].to_f, 20 | **t.symbolize_keys 21 | } 22 | else 23 | weight = weight.to_f if weight.present? 24 | passed = (score == weight) if passed.nil? 25 | { 26 | num: num, 27 | weight: weight, 28 | passed: passed, 29 | score: score, 30 | **t.symbolize_keys 31 | } 32 | end 33 | end 34 | @passed_count = @tests&.count { |t| t[:passed] } || 0 35 | @score = @json['score'] || 0 36 | @max_score = @json['max-score'] || 0 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /test/models/team_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TeamTest < ActiveSupport::TestCase 4 | setup do 5 | make_standard_course 6 | @jane = create(:user, name: "Jane von Classenstein", first_name: "Jane", last_name: "von Classenstein") 7 | @jane_reg = create(:registration, course: @cs101, user: @jane, new_sections: [@section.crn]) 8 | @jane_reg.save_sections 9 | @team = create(:team, teamset: @ts1, course: @cs101, start_date: Date.current) 10 | @team.users = [@jane, @john] 11 | @team.save 12 | end 13 | 14 | test "Dissolve team with nil end_date" do 15 | assert @team.end_date.nil? 16 | @when = Date.current 17 | @team.dissolve(@when) 18 | assert_equal @when.to_date, @team.end_date 19 | end 20 | test "Dissolve team with future end_date" do 21 | @when = Date.current 22 | @team.end_date = (@when + 1.day) 23 | assert !@team.end_date.nil? 24 | @team.dissolve(@when) 25 | assert_equal @when.to_date, @team.end_date 26 | end 27 | test "Dissolve team with expired end_date" do 28 | @when = Date.current 29 | @team.end_date = (@when - 1.day) 30 | assert !@team.end_date.nil? 31 | @team.dissolve(@when) 32 | assert_equal @when.to_date - 1.day, @team.end_date 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy 4 | # For further information see the following documentation 5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy 6 | 7 | # Rails.application.configure do 8 | # config.content_security_policy do |policy| 9 | # policy.default_src :self, :https 10 | # policy.font_src :self, :https, :data 11 | # policy.img_src :self, :https, :data 12 | # policy.object_src :none 13 | # policy.script_src :self, :https 14 | # policy.style_src :self, :https 15 | # # Specify URI for violation reports 16 | # # policy.report_uri "/csp-violation-report-endpoint" 17 | # end 18 | # 19 | # # Generate session nonces for permitted importmap and inline scripts 20 | # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } 21 | # config.content_security_policy_nonce_directives = %w(script-src) 22 | # 23 | # # Report CSP violations to a specified URI. See: 24 | # # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only 25 | # # config.content_security_policy_report_only = true 26 | # end 27 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1637010500, 6 | "narHash": "sha256-CVcZs8QP0Y2NbsizhgI/P42FqdeqXNe4h11W1Wk1aFY=", 7 | "owner": "nixos", 8 | "repo": "nixpkgs", 9 | "rev": "5e15d5da4abb74f0dd76967044735c70e94c5af1", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "nixos", 14 | "repo": "nixpkgs", 15 | "rev": "5e15d5da4abb74f0dd76967044735c70e94c5af1", 16 | "type": "github" 17 | } 18 | }, 19 | "nixpkgs-ruby": { 20 | "locked": { 21 | "lastModified": 1637010500, 22 | "narHash": "sha256-CVcZs8QP0Y2NbsizhgI/P42FqdeqXNe4h11W1Wk1aFY=", 23 | "owner": "nixos", 24 | "repo": "nixpkgs", 25 | "rev": "5e15d5da4abb74f0dd76967044735c70e94c5af1", 26 | "type": "github" 27 | }, 28 | "original": { 29 | "owner": "nixos", 30 | "repo": "nixpkgs", 31 | "rev": "5e15d5da4abb74f0dd76967044735c70e94c5af1", 32 | "type": "github" 33 | } 34 | }, 35 | "root": { 36 | "inputs": { 37 | "nixpkgs": "nixpkgs", 38 | "nixpkgs-ruby": "nixpkgs-ruby" 39 | } 40 | } 41 | }, 42 | "root": "root", 43 | "version": 7 44 | } 45 | -------------------------------------------------------------------------------- /test/models/sandbox_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'fake_upload' 3 | 4 | class SandboxTest < ActiveSupport::TestCase 5 | setup do 6 | make_standard_course 7 | end 8 | 9 | test "run a sandbox grader mode" do 10 | skip unless ENV['TEST_SANDBOX'] 11 | # FIXME: This test design can't work. 12 | # We need to spin up a webserver for the sandbox to pull down the files. 13 | 14 | assign = create(:assignment, type: "Files", course: @cs101, name: "Test Assignment") 15 | 16 | grdtar = Rails.root.join('sandbox', 'examples', 'demo', 'demo-grading.tar.gz') 17 | upload = simulated_upload(@fred, grdtar) 18 | upload.save! 19 | grdcfg = create(:grader, type: "SandboxGrader", upload: upload, params: "", 20 | assignment: assign, order: 1) 21 | 22 | subprg = Rails.root.join('sandbox', 'examples', 'demo', 'hello.tar.gz') 23 | sub = build(:submission, user: @john, assignment: assign, 24 | upload_id: nil, ignore_late_penalty: true) 25 | sub.upload_file = FakeUpload.new(subprg) 26 | sub.save_upload 27 | sub.save! 28 | 29 | puts sub.assignment.graders.inspect 30 | 31 | sub.autograde! 32 | 33 | run_background_jobs 34 | 35 | sub.reload 36 | assert_equal 100, sub.score 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /app/models/lateness_configs/late_per_day_config.rb: -------------------------------------------------------------------------------- 1 | class LatePerDayConfig < LatenessConfig 2 | validates :days_per_assignment, :numericality => true 3 | validates :frequency, :numericality => true 4 | validates :max_penalty, :numericality => true 5 | validates :percent_off, :numericality => true 6 | 7 | def allow_submission?(assn, sub) 8 | days_late(assn, sub) <= self.days_per_assignment 9 | end 10 | 11 | def late_penalty(assn, sub) 12 | penalty = (self.percent_off || 0).to_f * days_late(assn, sub).to_f 13 | penalty.clamp(0, self.max_penalty) 14 | end 15 | 16 | def to_s 17 | if self.days_per_assignment.nil? 18 | days_allowed = "unlimited late days" 19 | else 20 | days_allowed = pluralize(self.days_per_assignment, "late day") 21 | end 22 | 23 | "Allow #{days_allowed}, penalizing #{self.percent_off}% each #{pluralize(self.frequency, 'day')} up to #{self.max_penalty}%" 24 | end 25 | 26 | def ==(other) 27 | if other.instance_of?(LatePerDayConfig) 28 | self.days_per_assignment == other.days_per_assignment && 29 | self.frequency == other.frequency && 30 | self.max_penalty == other.max_penalty && 31 | self.percent_off == other.percent_off 32 | else 33 | false 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /app/views/grades/_show_xunit_tests.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | <% @tests.each do |t| %> 3 | <% if t[:passed] %> 4 |
    5 |

    6 | <% if cur_reg_staff %> 7 | Weight: <%= t[:info]["weight"] || 1 %> 8 | <% end %> 9 | Passed: <%= t[:comment] %> 10 |

    11 |
    12 | <% else %> 13 |
    14 |

    15 | <% if cur_reg_staff %> 16 | Weight: <%= t[:info]["weight"] %> 17 | <% end %> 18 | Failed: <%= t[:info]["header"] %> 19 |

    20 | <% unless t[:info]["message"].to_s.empty? %> 21 |

    Message: <%= t[:info]["message"] %>

    22 | <% end %> 23 | <% if t[:info]["actual"] && t[:info]["expected"] %> 24 | <%= render "actual_expected", :actual => t[:info]["actual"], :expected => t[:info]["expected"] %> 25 | <% end %> 26 | <% if t[:info]["stack"] %> 27 |

    Stack:

    28 |
    <%= t[:info]["stack"].join("\n") %>
    29 | <% end %> 30 |
    31 | <% end %> 32 | <% end %> 33 |
    34 | -------------------------------------------------------------------------------- /script/bulk-user-upload: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # Primitive bulk-user-upload script. Requires course_id to be hard-coded, below. 4 | # Reads comma-separated values of time, name, email, section, but ignores all but name and email. 5 | 6 | APP_PATH = File.expand_path('../../config/application', __FILE__) 7 | require File.expand_path('../../config/boot', __FILE__) 8 | require APP_PATH 9 | Rails.application.require_environment! 10 | 11 | COURSE_ID = 13 12 | 13 | filename = ARGV[0] 14 | 15 | if filename.nil? 16 | puts "Usage" 17 | puts " #{$0} users-to-add.csv" 18 | exit(1) 19 | end 20 | 21 | File.open(filename, 'r') do |f1| 22 | while line = f1.gets 23 | line.chomp! 24 | (name, email) = line.split(/\s*;\s*/) 25 | puts "Adding " + name + " with " + email 26 | new_user = nil 27 | 28 | begin 29 | new_user = User.new(email: email.downcase, name: name) 30 | new_user.save! 31 | rescue 32 | new_user = User.find_by_email(email.downcase) 33 | end 34 | 35 | begin 36 | Registration.new(user_id: new_user.id, course_id: COURSE_ID).save! 37 | rescue 38 | puts "Skip registration" 39 | end 40 | 41 | new_user.send_auth_link_email!("https://grader.cs.uml.edu/") 42 | puts "Sent email to #{new_user.email}" 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /db/migrate/20180608211724_schematize_terms.rb: -------------------------------------------------------------------------------- 1 | class SchematizeTerms < ActiveRecord::Migration[5.2] 2 | def up 3 | add_column :terms, :semester, :integer, default: 0 4 | add_column :terms, :year, :integer, default: 0 5 | Term.all.each do |t| 6 | ym = /(.*) (\d\d\d\d)/.match(t.read_attribute(:name)) 7 | t.year = ym[2] 8 | case ym[1].downcase 9 | when "fall" 10 | t.semester = Term.semesters[:fall] 11 | when "winter" 12 | t.semester = Term.semesters[:winter] 13 | when "spring" 14 | t.semester = Term.semesters[:spring] 15 | when "summer 1", "summer1", "summer i" 16 | t.semester = Term.semesters[:summer_1] 17 | when "summer 2", "summer2", "summer ii" 18 | t.semester = Term.semesters[:summer_2] 19 | when "summer" 20 | t.semester = Term.semesters[:summer] 21 | end 22 | t.save 23 | end 24 | change_column_null :terms, :semester, false 25 | change_column_null :terms, :year, false 26 | remove_column :terms, :name 27 | end 28 | 29 | def down 30 | add_column :terms, :name, :string 31 | Term.all.each do |t| 32 | t.name = "#{t.semester.humanize} #{t.year}" 33 | t.save 34 | end 35 | remove_column :terms, :semester 36 | remove_column :terms, :year 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /app/controllers/files_controller.rb: -------------------------------------------------------------------------------- 1 | class FilesController < ApplicationController 2 | 3 | @@resources_true_dir = "lib/assets" 4 | 5 | def upload 6 | send_file_from_path(Upload.base_upload_dir.join(params[:path])) 7 | end 8 | 9 | def resource 10 | send_file_from_path(Rails.root.join(@@resources_true_dir, params[:path])) 11 | end 12 | 13 | 14 | private 15 | 16 | def send_file_from_path(file_path) 17 | if valid_path_param?(params[:path]) && File.file?(file_path) 18 | disp = get_file_disposition(File.extname(params[:path]).downcase) 19 | mime = ApplicationHelper.mime_type(params[:path]) 20 | 21 | send_file file_path.to_s, 22 | filename: File.basename(params[:path]), 23 | disposition: disp, 24 | type: mime 25 | else 26 | render 'errors/not_found', layout: 'errors', formats: [:html], status: 404 27 | end 28 | end 29 | 30 | def valid_path_param?(path) 31 | !path.include?('../') && !Grader.path_to_grader_secret?(path) 32 | end 33 | 34 | def get_file_disposition(file_ext) 35 | case file_ext 36 | when ".jpg", ".jpeg", ".png", ".gif", ".tap", ".log", ".json" 37 | return "inline" 38 | when ".pdf" 39 | return nil 40 | else 41 | return "attachment" 42 | end 43 | end 44 | 45 | end 46 | -------------------------------------------------------------------------------- /app/models/settings.rb: -------------------------------------------------------------------------------- 1 | class Settings 2 | def self.defaults 3 | { 4 | "site_email" => 'Bottlenose ', 5 | "backup_login" => '', 6 | "site_url" => '', 7 | "orca_api_key" => '', 8 | } 9 | end 10 | 11 | def self.set_site_url!(req) 12 | cfg = load_json 13 | cfg["site_url"] = "#{req.protocol}#{host_primary_ip}:#{req.port}" 14 | save_json(cfg) 15 | end 16 | 17 | def self.host_primary_ip 18 | route = `ip route get 8.8.8.8` 19 | iface = route.match(/dev\s+(\w+?)\s/)[1] 20 | addrs = `ip a show dev "#{iface}"` 21 | addrs.match(/inet\s+(\d+\.\d+\.\d+\.\d+)[\s|\/]/)[1] 22 | end 23 | 24 | def self.clear_test! 25 | FileUtils.rm(File.expand_path("~/.config/bottlenose/test.json"), force: true) 26 | end 27 | 28 | def self.file_path 29 | Pathname.new(File.expand_path("~/.config/bottlenose/#{Rails.env}.json")) 30 | end 31 | 32 | def self.load_json 33 | unless File.exist?(file_path) 34 | return defaults 35 | end 36 | 37 | defaults.merge(JSON.parse(File.read(file_path))) 38 | end 39 | 40 | def self.save_json(cfg) 41 | FileUtils.mkdir_p(file_path.parent) 42 | File.write(file_path, cfg.to_json) 43 | end 44 | 45 | def self.[](key) 46 | cfg = load_json 47 | return cfg[key] 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /app/views/grades/_exam_import_schema.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    A CSV file with the following columns:

    4 |
     5 | NUID | Last name | First name | Status | Action | <%= @assignment.flattened_questions.map{|q| q["name"]}.join(" | ") %> | Curved
     6 | 
    7 |
      8 |
    • The uploaded file must have column headers as its first row.
    • 9 |
    • Each column header must be quoted, separated by commas, and there 10 | must not be spaces between the column headers.
    • 11 |
    • NOTE: Any students who are missing NUIDs cannot have their grades uploaded.
    • 12 |
    • The Last name, First name, and Status columns are ignored upon import.
    • 13 |
    • The Action column may be one of Ignore, Update or Delete: 14 |
        15 |
      • Ignore will not update the scores for this student.
      • 16 |
      • Update will update the scores for this student.
      • 17 |
      • Delete will delete the grades for this student.
      • 18 |
      19 |
    • 20 |
    • Columns 6 through <%= @assignment.flattened_questions.count + 5 %> 21 | are the grades of individual question parts.
    • 22 |
    • The Curved grade column may be left blank, to clear any curved grade for this student.
    • 23 |
    24 |
    25 |
    26 | -------------------------------------------------------------------------------- /app/views/teams/show.html.erb: -------------------------------------------------------------------------------- 1 | <% cur_reg = current_user.registration_for(@course) %> 2 | <% cur_user_site_admin = current_user.site_admin? %> 3 | <% cur_user_prof_ever = current_user.professor_ever? %> 4 | <% @page_title = "#{@course.name} - Team ##{@team.id}" %> 5 | 6 |

    7 | Team starts on 8 | <%= @team.start_date.strftime("%b %d, %Y") %> 9 | <% if @team.end_date %> 10 | and ends the night before 11 | <%= @team.end_date.strftime("%b %d, %Y") %>. 12 | <% else %> 13 | and doesn't end. 14 | <% end %> 15 |

    16 | 17 |

    Status

    18 |

    <%= @team.active? ? "Active" : "Inactive" %>

    19 | 20 |

    21 | <%= 22 | all_emails = @team.users.map{|uu| "#{uu.name} <#{uu.email}>"} 23 | mail_to all_emails.join(","), "@", title: "Email team members" 24 | %> Members 25 |

    26 |
      27 | <% @team.users.each do |uu| %> 28 |
    • <%= maybe_link_user(cur_user_site_admin || cur_user_prof_ever || current_user.id == uu.id, uu) %>
    • 29 | <% end %> 30 |
    31 | 32 |

    Submissions

    33 | <% if @team.used_submissions.empty? %> 34 |

    None

    35 | <% else %> 36 |
      37 | <% @team.used_submissions.each do |s| %> 38 |
    • <%= link_to s.assignment.name, course_assignment_submission_path(@course, s.assignment, s) %>
    • 39 | <% end %> 40 |
    41 | <% end %> 42 | -------------------------------------------------------------------------------- /app/controllers/errors_controller.rb: -------------------------------------------------------------------------------- 1 | require 'audit' 2 | class ErrorsController < ApplicationController 3 | def not_found 4 | respond_to do |format| 5 | format.html { render status: 404 } 6 | format.png { 7 | if params[:any].starts_with? "apple-touch-icon" 8 | render body: nil, status: 410 9 | else 10 | render body: nil, status: 404 11 | end 12 | } 13 | format.js { 14 | Audit.log < resource: #{request.original_url} 16 | Current user (in any): #{current_user&.id} #{current_user&.display_name} 17 | Remote address: #{request.remote_ip} 18 | ERROR 19 | # The explicit content type prevents Rails from thinking this is a CSRF 20 | # attack and producing an incorrect error message 21 | render body: nil, status: 404, content_type: 'text/plain' 22 | } 23 | 24 | format.any { 25 | Audit.log < :restrict_with_error 6 | 7 | validates :semester, inclusion: {in: Term.semesters.keys}, 8 | uniqueness: { 9 | scope: :year, 10 | message: ->(object, data) do 11 | "Terms must be unique, but the semester/year pair #{object.name} already exists" 12 | end} 13 | 14 | def self.all_sorted 15 | terms = Term.all 16 | terms.sort_by {|tt| tt.canonical_name }.reverse 17 | end 18 | 19 | def name 20 | "#{semester.humanize} #{year}" 21 | end 22 | 23 | def canonical_name 24 | season = "#{Term.semesters[semester]}_#{semester}" 25 | 26 | arch = archived? ? "a" : "z" 27 | 28 | "#{arch} #{effective_year} #{season} #{name}" 29 | end 30 | 31 | def query_code 32 | # For use with querying university rosters 33 | "#{effective_year}#{Term.semesters[semester]}" 34 | end 35 | 36 | private 37 | def effective_year 38 | # Fall is part of numerically-next *academic* year 39 | year + ((Term.semesters[semester] < Term.semesters[:spring]) ? 1 : 0) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /app/controllers/teams_controller.rb: -------------------------------------------------------------------------------- 1 | class TeamsController < ApplicationController 2 | layout 'course' 3 | 4 | before_action :find_course 5 | before_action :find_teamset 6 | before_action :find_team 7 | before_action :require_registered_user 8 | before_action :require_admin_or_staff, only: [:dissolve] 9 | 10 | # GET /courses/:course_id/teams/:id 11 | def show 12 | if !(current_user_site_admin? || current_user_staff_for?(@course)) && @team.users.exclude?(current_user) 13 | redirect_to(root_path, alert: "You are not a member of that team.") 14 | end 15 | end 16 | 17 | def dissolve 18 | @team.dissolve(DateTime.current) 19 | redirect_back fallback_location: course_teamset_path(@course, @teamset), 20 | notice: "Team was successfully dissolved" 21 | end 22 | 23 | private 24 | 25 | def find_teamset 26 | @teamset = Teamset.find_by(id: params[:teamset_id]) 27 | if @teamset.nil? 28 | redirect_back fallback_location: course_teamsets_path(@course), 29 | alert: "No such teamset" 30 | return 31 | end 32 | end 33 | def find_team 34 | @team = Team.find_by(id: params[:id]) 35 | if @team.nil? || @team.teamset_id != @teamset.id 36 | redirect_back fallback_location: course_teams_path(@course), 37 | alert: "No such team for this teamset" 38 | return 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /script/cleanup-db: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This script cleans up possible database weirdnesses. 4 | # It should be safe to run at any time. 5 | # 6 | 7 | APP_PATH = File.expand_path('../../config/application', __FILE__) 8 | require File.expand_path('../../config/boot', __FILE__) 9 | require APP_PATH 10 | Rails.application.require_environment! 11 | 12 | 13 | # Make sure all email addresses in the users table are lowercase. 14 | 15 | User.all.each do |user| 16 | if user.email =~ /[A-Z]/ || user.email =~ /\W$/ 17 | puts "Fixing user email: #{user.email}" 18 | 19 | unless user.save 20 | user.email = "invalid-user-#{user.id}@example.com" 21 | user.save! 22 | end 23 | end 24 | end 25 | 26 | # Remove users with invalid emails who have no submissions. 27 | 28 | User.all.each do |user| 29 | if user.email =~ /^invalid-user.*@example.com$/ 30 | sc = user.submissions.count 31 | if sc == 0 32 | user.registrations.each do |reg| 33 | reg.destroy 34 | end 35 | 36 | unless user.destroy 37 | puts "#{user.id} #{user.name}" 38 | puts user.errors.full_messages 39 | end 40 | end 41 | end 42 | end 43 | 44 | # Remove registration requests from invalid users. 45 | 46 | RegRequest.all.each do |rr| 47 | if rr.user.nil? 48 | rr.destroy 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /app/views/terms/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for(@term) do |f| %> 2 | <% if @term.errors.any? %> 3 |
    4 |

    <%= pluralize(@term.errors.count, "error") %> prohibited this term from being saved:

    5 | 6 |
      7 | <% @term.errors.messages.each do |attr, msgs| %> 8 | <% msgs.each do |msg| %> 9 |
    • <%= if (attr == :semester) then msg.html_safe else full_message(attr, msg) end %>
    • 10 | <% end %> 11 | <% end %> 12 |
    13 |
    14 | <% end %> 15 | 16 |
    17 | <%= f.label :semester %> 18 | 19 | 20 | 22 | 23 | 24 |
    <%= f.select :semester, Term.semesters.map{|name, _| [name.humanize, name]}, 21 | selected: @term.semester %><%= f.spinner :year, @term.year, min: Date.today.year - 10, max: Date.today.year + 10 %>
    25 |
    26 |
    27 | <%= f.label :archived %> 28 | <%= f.check_box :archived, class: 'form-control', data: {toggle: "toggle", on: "Yes", off: "No"} %> 29 |
    30 |
    31 | <%= f.submit nil, class: 'btn btn-primary' %> 32 |
    33 | <% end %> 34 | 39 | -------------------------------------------------------------------------------- /app/views/teamsets/_table.html.erb: -------------------------------------------------------------------------------- 1 | <% cur_reg = current_user.registration_for(@course) %> 2 | <% cur_reg_staff = cur_reg&.staff? %> 3 | <% show_all_users = current_user.site_admin? || current_user.professor_ever? %> 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | <% teams.each do |team| %> 16 | <% sections, hazards, row_warnings = @team_info&.dig(team.id) 17 | sec_classes = (sections || []).map{|crn, _| "sec_#{crn}"} 18 | classes = [sec_classes, row_warnings].flatten.uniq.compact.join(" ") %> 19 | 20 | 25 | 30 | 31 | 32 | 33 | 34 | <% end %> 35 | 36 |
    MembersStart DateEnd Date
    21 | <% hazards&.each do |hazard, warning| %> 22 | 23 | <% end %> 24 | 26 | <% if team.active? %> 27 | 28 | <% end %> 29 | <%= maybe_link_team(cur_reg_staff, show_all_users || [current_user.id], team) %><%= team.start_date %><%= team.end_date || "∞" %>
    37 | --------------------------------------------------------------------------------