From 7981de85a8c318ce4c96140ff6fc014bb6719cdb Mon Sep 17 00:00:00 2001 From: Steven Dalamaras Date: Sun, 30 Nov 2025 13:29:30 +1000 Subject: [PATCH 01/22] docs: fix typo in API root, add doc_version The document still said "documentaion". Nuts. I have also added the doc version number. I defaulted to the version of the branch. --- app/api/api_root.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/api/api_root.rb b/app/api/api_root.rb index 983749c551..a5a6f0bdb4 100644 --- a/app/api/api_root.rb +++ b/app/api/api_root.rb @@ -153,10 +153,10 @@ class ApiRoot < Grape::API add_swagger_documentation \ base_path: nil, - api_version: 'v1', + doc_version: 'v10.0.0', hide_documentation_path: true, info: { - title: 'Doubtfire API Documentaion', + title: 'Doubtfire API Documentation', description: 'Doubtfire is a modern, lightweight learning management system.', license: 'AGPL v3.0', license_url: 'https://github.com/doubtfire-lms/doubtfire-api/blob/master/LICENSE' From 1312537c3463deec57b92034a77e39806d072a3f Mon Sep 17 00:00:00 2001 From: Steven Dalamaras Date: Sat, 17 Jan 2026 16:49:53 +1000 Subject: [PATCH 02/22] chore: update gems Also correct the pdf reading test --- Gemfile.lock | 525 +++++++++++++++++++++++++-------------------------- db/schema.rb | 474 +++++++++++++++++++++++----------------------- 2 files changed, 495 insertions(+), 504 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 1149766c40..b473c814a4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,30 +1,32 @@ GEM remote: https://rubygems.org/ specs: - Ascii85 (2.0.1) - actioncable (8.0.2) - actionpack (= 8.0.2) - activesupport (= 8.0.2) + Ascii85 (1.1.1) + action_text-trix (2.1.16) + railties + actioncable (8.1.2) + actionpack (= 8.1.2) + activesupport (= 8.1.2) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (8.0.2) - actionpack (= 8.0.2) - activejob (= 8.0.2) - activerecord (= 8.0.2) - activestorage (= 8.0.2) - activesupport (= 8.0.2) + actionmailbox (8.1.2) + actionpack (= 8.1.2) + activejob (= 8.1.2) + activerecord (= 8.1.2) + activestorage (= 8.1.2) + activesupport (= 8.1.2) mail (>= 2.8.0) - actionmailer (8.0.2) - actionpack (= 8.0.2) - actionview (= 8.0.2) - activejob (= 8.0.2) - activesupport (= 8.0.2) + actionmailer (8.1.2) + actionpack (= 8.1.2) + actionview (= 8.1.2) + activejob (= 8.1.2) + activesupport (= 8.1.2) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (8.0.2) - actionview (= 8.0.2) - activesupport (= 8.0.2) + actionpack (8.1.2) + actionview (= 8.1.2) + activesupport (= 8.1.2) nokogiri (>= 1.8.5) rack (>= 2.2.4) rack-session (>= 1.0.1) @@ -32,72 +34,73 @@ GEM rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) - actiontext (8.0.2) - actionpack (= 8.0.2) - activerecord (= 8.0.2) - activestorage (= 8.0.2) - activesupport (= 8.0.2) + actiontext (8.1.2) + action_text-trix (~> 2.1.15) + actionpack (= 8.1.2) + activerecord (= 8.1.2) + activestorage (= 8.1.2) + activesupport (= 8.1.2) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (8.0.2) - activesupport (= 8.0.2) + actionview (8.1.2) + activesupport (= 8.1.2) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - activejob (8.0.2) - activesupport (= 8.0.2) + activejob (8.1.2) + activesupport (= 8.1.2) globalid (>= 0.3.6) - activemodel (8.0.2) - activesupport (= 8.0.2) - activerecord (8.0.2) - activemodel (= 8.0.2) - activesupport (= 8.0.2) + activemodel (8.1.2) + activesupport (= 8.1.2) + activerecord (8.1.2) + activemodel (= 8.1.2) + activesupport (= 8.1.2) timeout (>= 0.4.0) - activestorage (8.0.2) - actionpack (= 8.0.2) - activejob (= 8.0.2) - activerecord (= 8.0.2) - activesupport (= 8.0.2) + activestorage (8.1.2) + actionpack (= 8.1.2) + activejob (= 8.1.2) + activerecord (= 8.1.2) + activesupport (= 8.1.2) marcel (~> 1.0) - activesupport (8.0.2) + activesupport (8.1.2) base64 - benchmark (>= 0.3) bigdecimal concurrent-ruby (~> 1.0, >= 1.3.1) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) + json logger (>= 1.4.2) minitest (>= 5.1) securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) uri (>= 0.13.1) - addressable (2.8.7) - public_suffix (>= 2.0.2, < 7.0) + addressable (2.8.6) + public_suffix (>= 2.0.2, < 6.0) aes_key_wrap (1.1.0) afm (0.2.2) - amq-protocol (2.3.3) - ast (2.4.3) + amq-protocol (2.3.2) + ast (2.4.2) backport (1.2.0) base64 (0.2.0) bcrypt (3.1.20) - benchmark (0.4.0) + benchmark (0.3.0) better_errors (2.10.1) erubi (>= 1.0.0) rack (>= 0.9.0) rouge (>= 1.0.0) - bigdecimal (3.1.9) + bigdecimal (3.1.8) bindata (2.5.0) - bootsnap (1.18.4) + bootsnap (1.18.3) msgpack (~> 1.2) builder (3.3.0) - bunny (2.24.0) - amq-protocol (~> 2.3) + bunny (2.22.0) + amq-protocol (~> 2.3, >= 2.3.1) sorted_set (~> 1, >= 1.0.2) bunny-pub-sub (0.5.2) bunny (~> 2.14) - byebug (12.0.0) + byebug (11.1.3) chronic_duration (0.10.6) numerizer (~> 0.1.1) ci_reporter (2.1.0) @@ -106,21 +109,18 @@ GEM code_analyzer (0.5.5) sexp_processor coderay (1.1.3) - concurrent-ruby (1.3.5) - connection_pool (2.5.0) + concurrent-ruby (1.3.3) + connection_pool (2.4.1) crack (1.0.0) bigdecimal rexml crass (1.0.6) - cronex (0.15.0) - tzinfo - unicode (>= 0.4.4.5) - csv (3.3.3) - database_cleaner-active_record (2.2.0) + csv (3.3.0) + database_cleaner-active_record (2.1.0) activerecord (>= 5.a) database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) - date (3.4.1) + date (3.5.1) devise (4.9.4) bcrypt (~> 3.0) orm_adapter (~> 0.1) @@ -130,144 +130,139 @@ GEM devise_ldap_authenticatable (0.8.7) devise (>= 3.4.1) net-ldap (>= 0.16.0) - diff-lcs (1.6.1) - docile (1.4.1) + diff-lcs (1.5.1) + docile (1.4.0) domain_name (0.6.20240107) - dotenv (3.1.7) + dotenv (3.1.2) drb (2.2.1) - dry-core (1.1.0) + dry-core (1.0.1) concurrent-ruby (~> 1.0) - logger zeitwerk (~> 2.6) - dry-inflector (1.2.0) - dry-logic (1.6.0) - bigdecimal + dry-inflector (1.0.0) + dry-logic (1.5.0) concurrent-ruby (~> 1.0) - dry-core (~> 1.1) + dry-core (~> 1.0, < 2) zeitwerk (~> 2.6) - dry-types (1.8.2) + dry-types (1.7.2) bigdecimal (~> 3.0) concurrent-ruby (~> 1.0) dry-core (~> 1.0) dry-inflector (~> 1.0) dry-logic (~> 1.4) zeitwerk (~> 2.6) - erubi (1.13.1) + e2mmap (0.1.0) + erubi (1.12.0) erubis (2.7.0) et-orbi (1.2.11) tzinfo ethon (0.16.0) ffi (>= 1.15.0) - factory_bot (6.5.1) - activesupport (>= 6.1.0) - factory_bot_rails (6.4.4) - factory_bot (~> 6.5) + factory_bot (6.4.6) + activesupport (>= 5.0.0) + factory_bot_rails (6.4.3) + factory_bot (~> 6.4) railties (>= 5.0.0) - faker (3.5.1) + faker (3.4.1) i18n (>= 1.8.11, < 2) - faraday (2.12.2) - faraday-net_http (>= 2.0, < 3.5) - json - logger + faraday (2.9.1) + faraday-net_http (>= 2.0, < 3.2) faraday-follow_redirects (0.3.0) faraday (>= 1, < 3) - faraday-net_http (3.4.0) - net-http (>= 0.5.0) - ffi (1.17.1-aarch64-linux-gnu) - ffi (1.17.1-x86_64-linux-gnu) - fugit (1.11.1) + faraday-net_http (3.1.0) + net-http + ffi (1.17.0) + fugit (1.11.0) et-orbi (~> 1, >= 1.2.11) raabro (~> 1.4) globalid (1.2.1) activesupport (>= 6.1) - grape (2.3.0) - activesupport (>= 6) + grape (2.0.0) + activesupport (>= 5) + builder dry-types (>= 1.1) - mustermann-grape (~> 1.1.0) - rack (>= 2) - zeitwerk + mustermann-grape (~> 1.0.0) + rack (>= 1.3.0) + rack-accept grape-entity (1.0.1) activesupport (>= 3.0.0) multi_json (>= 1.3.2) - grape-swagger (2.1.2) + grape-swagger (2.1.0) grape (>= 1.7, < 3.0) rack-test (~> 2) - grape-swagger-rails (0.6.0) - ostruct + grape-swagger-rails (0.5.0) railties (>= 6.0.6.1) - hashdiff (1.1.2) + hashdiff (1.1.0) hashery (2.1.2) - hashie (5.0.0) + hashie (5.1.0) + logger hirb (0.7.3) http-accept (1.7.0) - http-cookie (1.0.8) + http-cookie (1.0.6) domain_name (~> 0.5) - i18n (1.14.7) + i18n (1.14.5) concurrent-ruby (~> 1.0) - icalendar (2.10.3) + icalendar (2.10.1) ice_cube (~> 0.16) - ostruct - ice_cube (0.17.0) - io-console (0.8.0) - irb (1.15.1) - pp (>= 0.6.0) + ice_cube (0.16.4) + io-console (0.7.2) + irb (1.13.1) rdoc (>= 4.0.0) reline (>= 0.4.2) jaro_winkler (1.6.0) - json (2.10.2) - json-jwt (1.16.7) + json (2.7.2) + json-jwt (1.16.6) activesupport (>= 4.2) aes_key_wrap base64 bindata faraday (~> 2.0) faraday-follow_redirects - jwt (2.10.1) + jwt (3.1.2) base64 - kramdown (2.5.1) - rexml (>= 3.3.9) + kramdown (2.4.0) + rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) - language_server-protocol (3.17.0.4) - lint_roller (1.1.0) + language_server-protocol (3.17.0.3) listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) logger (1.7.0) - loofah (2.24.0) + loofah (2.22.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) - mail (2.8.1) + mail (2.9.0) + logger mini_mime (>= 0.1.1) net-imap net-pop net-smtp - marcel (1.0.4) - mime-types (3.6.2) - logger + marcel (1.1.0) + mime-types (3.5.2) mime-types-data (~> 3.2015) - mime-types-data (3.2025.0325) + mime-types-data (3.2024.0604) mini_mime (1.1.5) - minitest (5.25.5) + mini_portile2 (2.8.9) + minitest (5.23.1) minitest-around (0.5.0) minitest (~> 5.0) - minitest-rails (8.0.0) + minitest-rails (8.1.0) minitest (~> 5.20) - railties (>= 8.0.0, < 8.1.0) + railties (>= 8.1.0, < 8.2.0) moss_ruby (1.1.4) tcp_timeout (~> 0.1.1) - msgpack (1.8.0) + msgpack (1.7.2) multi_json (1.15.0) - multi_xml (0.7.1) - bigdecimal (~> 3.1) - mustermann (3.0.3) + multi_xml (0.8.1) + bigdecimal (>= 3.1, < 5) + mustermann (3.0.0) ruby2_keywords (~> 0.0.1) - mustermann-grape (1.1.0) + mustermann-grape (1.0.2) mustermann (>= 1.0.0) mysql2 (0.5.6) - net-http (0.6.0) + net-http (0.4.1) uri - net-imap (0.5.6) + net-imap (0.6.2) date net-protocol net-ldap (0.19.0) @@ -275,79 +270,76 @@ GEM net-protocol net-protocol (0.2.2) timeout - net-smtp (0.5.1) + net-smtp (0.5.0) net-protocol netrc (0.11.0) - nio4r (2.7.4) - nokogiri (1.18.7-aarch64-linux-gnu) - racc (~> 1.4) - nokogiri (1.18.7-x86_64-linux-gnu) + nio4r (2.7.3) + nokogiri (1.16.5) + mini_portile2 (~> 2.8.2) racc (~> 1.4) numerizer (0.1.1) - oauth2 (2.0.9) - faraday (>= 0.17.3, < 3.0) - jwt (>= 1.0, < 3.0) + oauth2 (2.0.18) + faraday (>= 0.17.3, < 4.0) + jwt (>= 1.0, < 4.0) + logger (~> 1.2) multi_xml (~> 0.5) rack (>= 1.2, < 4) - snaky_hash (~> 2.0) - version_gem (~> 1.1) + snaky_hash (~> 2.0, >= 2.0.3) + version_gem (~> 1.1, >= 1.1.9) observer (0.1.2) orm_adapter (0.5.0) - ostruct (0.6.1) - parallel (1.26.3) - parser (3.3.7.4) + parallel (1.25.1) + parser (3.3.2.0) ast (~> 2.4.1) racc - pdf-reader (2.14.1) - Ascii85 (>= 1.0, < 3.0, != 2.0.0) + pdf-reader (2.12.0) + Ascii85 (~> 1.0) afm (~> 0.2.1) hashery (~> 2.0) ruby-rc4 ttfunk - pkg-config (1.6.0) - pp (0.6.2) - prettyprint - prettyprint (0.2.0) - prism (1.4.0) - psych (5.2.3) - date + pkg-config (1.5.6) + prism (0.29.0) + psych (5.1.2) stringio - public_suffix (6.0.1) - puma (6.6.0) + public_suffix (5.0.5) + puma (6.4.2) nio4r (~> 2.0) raabro (1.4.0) - racc (1.8.1) - rack (3.1.12) + racc (1.8.0) + rack (3.0.11) + rack-accept (0.4.5) + rack (>= 0.4) rack-cors (2.0.2) rack (>= 2.0.0) - rack-session (2.1.0) - base64 (>= 0.1.0) + rack-session (2.0.0) rack (>= 3.0.0) - rack-test (2.2.0) + rack-test (2.1.0) rack (>= 1.3) - rackup (2.2.1) + rackup (2.1.0) rack (>= 3) - rails (8.0.2) - actioncable (= 8.0.2) - actionmailbox (= 8.0.2) - actionmailer (= 8.0.2) - actionpack (= 8.0.2) - actiontext (= 8.0.2) - actionview (= 8.0.2) - activejob (= 8.0.2) - activemodel (= 8.0.2) - activerecord (= 8.0.2) - activestorage (= 8.0.2) - activesupport (= 8.0.2) + webrick (~> 1.8) + rails (8.1.2) + actioncable (= 8.1.2) + actionmailbox (= 8.1.2) + actionmailer (= 8.1.2) + actionpack (= 8.1.2) + actiontext (= 8.1.2) + actionview (= 8.1.2) + activejob (= 8.1.2) + activemodel (= 8.1.2) + activerecord (= 8.1.2) + activestorage (= 8.1.2) + activesupport (= 8.1.2) bundler (>= 1.15.0) - railties (= 8.0.2) + railties (= 8.1.2) rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.6.2) + rails-html-sanitizer (1.6.0) loofah (~> 2.21) - nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + nokogiri (~> 1.14) rails-latex (2.3.5) rails (>= 3.0.0, < 9) rails_best_practices (1.23.2) @@ -358,30 +350,30 @@ GEM json require_all (~> 3.0) ruby-progressbar - railties (8.0.2) - actionpack (= 8.0.2) - activesupport (= 8.0.2) + railties (8.1.2) + actionpack (= 8.1.2) + activesupport (= 8.1.2) irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) thor (~> 1.0, >= 1.2.2) + tsort (>= 0.2) zeitwerk (~> 2.6) rainbow (3.1.1) rake (13.2.1) rb-fsevent (0.11.2) rb-inotify (0.11.1) ffi (~> 1.0) - rbs (3.9.2) - logger + rbs (2.8.4) rbtree (0.4.6) - rdoc (6.13.1) + rdoc (6.7.0) psych (>= 4.0.0) - redis (5.4.0) + redis (5.2.0) redis-client (>= 0.22.0) - redis-client (0.24.0) + redis-client (0.22.2) connection_pool - regexp_parser (2.10.0) - reline (0.6.0) + regexp_parser (2.9.2) + reline (0.5.8) io-console (~> 0.5) require_all (3.0.0) responders (3.1.1) @@ -392,172 +384,171 @@ GEM http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) - reverse_markdown (3.0.0) + reverse_markdown (2.1.1) nokogiri - rexml (3.4.1) - rmagick (6.1.1) + rexml (3.2.9) + strscan + rmagick (6.0.1) observer (~> 0.1) pkg-config (~> 1.4) - roo (2.10.1) + roo (2.7.1) nokogiri (~> 1) - rubyzip (>= 1.3.0, < 3.0.0) + rubyzip (~> 1.1, < 2.0.0) roo-xls (1.2.0) nokogiri roo (>= 2.0.0, < 3) spreadsheet (> 0.9.0) - rouge (4.5.1) - rubocop (1.75.1) + rouge (4.2.1) + rubocop (1.64.1) json (~> 2.3) - language_server-protocol (~> 3.17.0.2) - lint_roller (~> 1.1.0) + language_server-protocol (>= 3.17.0) parallel (~> 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.43.0, < 2.0) + regexp_parser (>= 1.8, < 3.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.31.1, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.43.0) - parser (>= 3.3.7.2) - prism (~> 1.4) - rubocop-factory_bot (2.27.1) - lint_roller (~> 1.1) - rubocop (~> 1.72, >= 1.72.1) - rubocop-faker (1.3.0) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.31.3) + parser (>= 3.3.1.0) + rubocop-factory_bot (2.26.1) + rubocop (~> 1.61) + rubocop-faker (1.1.0) faker (>= 2.12.0) - lint_roller (~> 1.1) - rubocop (>= 1.72.1) - rubocop-minitest (0.37.1) - lint_roller (~> 1.1) - rubocop (>= 1.72.1, < 2.0) - rubocop-ast (>= 1.38.0, < 2.0) - rubocop-performance (1.24.0) - lint_roller (~> 1.1) - rubocop (>= 1.72.1, < 2.0) - rubocop-ast (>= 1.38.0, < 2.0) - rubocop-rails (2.30.3) + rubocop (>= 0.82.0) + rubocop-minitest (0.36.0) + rubocop (>= 1.61, < 2.0) + rubocop-ast (>= 1.31.1, < 2.0) + rubocop-performance (1.23.1) + rubocop (>= 1.48.1, < 2.0) + rubocop-ast (>= 1.31.1, < 2.0) + rubocop-rails (2.25.0) activesupport (>= 4.2.0) - lint_roller (~> 1.1) rack (>= 1.1) - rubocop (>= 1.72.1, < 2.0) - rubocop-ast (>= 1.38.0, < 2.0) + rubocop (>= 1.33.0, < 2.0) + rubocop-ast (>= 1.31.1, < 2.0) ruby-filemagic (0.7.3) - ruby-lsp (0.23.13) + ruby-lsp (0.17.2) language_server-protocol (~> 3.17.0) - prism (>= 1.2, < 2.0) - rbs (>= 3, < 4) + prism (>= 0.29.0, < 0.30) sorbet-runtime (>= 0.5.10782) ruby-ole (1.2.13.1) ruby-progressbar (1.13.0) ruby-rc4 (0.1.5) - ruby-saml (1.18.0) - nokogiri (>= 1.13.10) + ruby-saml (1.13.0) + nokogiri (>= 1.10.5) rexml ruby2_keywords (0.0.5) - rubyzip (2.4.1) + rubyzip (1.3.0) securerandom (0.4.1) - set (1.1.1) - sexp_processor (4.17.3) - shellwords (0.2.2) - sidekiq (7.3.9) - base64 + set (1.1.0) + sexp_processor (4.17.1) + shellwords (0.2.0) + sidekiq (7.2.4) + concurrent-ruby (< 2) connection_pool (>= 2.3.0) - logger rack (>= 2.2.4) - redis-client (>= 0.22.2) - sidekiq-cron (2.2.0) - cronex (>= 0.13.0) - fugit (~> 1.8, >= 1.11.1) + redis-client (>= 0.19.0) + sidekiq-cron (1.12.0) + fugit (~> 1.8) globalid (>= 1.0.1) - sidekiq (>= 6.5.0) - sidekiq-status (3.0.3) + sidekiq (>= 6) + sidekiq-status (4.0.0) + base64 chronic_duration - sidekiq (>= 6.0, < 8) - sidekiq-unique-jobs (8.0.10) + logger + sidekiq (>= 7, < 9) + sidekiq-unique-jobs (8.0.13) concurrent-ruby (~> 1.0, >= 1.0.5) - sidekiq (>= 7.0.0, < 8.0.0) + sidekiq (>= 7.0.0, < 9.0.0) thor (>= 1.0, < 3.0) simplecov (0.22.0) docile (~> 1.1) simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) - simplecov-html (0.13.1) + simplecov-html (0.12.3) simplecov_json_formatter (0.1.4) - snaky_hash (2.0.1) - hashie - version_gem (~> 1.1, >= 1.1.1) - solargraph (0.53.4) + snaky_hash (2.0.3) + hashie (>= 0.1.0, < 6) + version_gem (>= 1.1.8, < 3) + solargraph (0.50.0) backport (~> 1.2) benchmark bundler (~> 2.0) diff-lcs (~> 1.4) - jaro_winkler (~> 1.6) + e2mmap + jaro_winkler (~> 1.5) kramdown (~> 2.3) kramdown-parser-gfm (~> 1.1) - logger (~> 1.6) - observer (~> 0.1) - ostruct (~> 0.6) parser (~> 3.0) - rbs (~> 3.3) - reverse_markdown (>= 2.0, < 4) + rbs (~> 2.0) + reverse_markdown (~> 2.0) rubocop (~> 1.38) thor (~> 1.0) tilt (~> 2.0) yard (~> 0.9, >= 0.9.24) - yard-solargraph (~> 0.1) - sorbet-runtime (0.5.11966) + sorbet-runtime (0.5.11422) sorted_set (1.0.3) rbtree set (~> 1.0) - spreadsheet (1.3.4) + spreadsheet (1.3.1) bigdecimal - logger ruby-ole sprockets (4.2.1) concurrent-ruby (~> 1.0) rack (>= 2.2.4, < 4) - sprockets-rails (3.5.2) + sprockets-rails (3.5.1) actionpack (>= 6.1) activesupport (>= 6.1) sprockets (>= 3.0.0) - stringio (3.1.6) + stringio (3.1.0) + strscan (3.1.0) tca_client (1.0.4) typhoeus (~> 1.0, >= 1.0.1) tcp_timeout (0.1.1) - thor (1.3.2) - tilt (2.6.0) - timeout (0.4.3) + thor (1.3.1) + tilt (2.3.0) + timeout (0.4.1) + tsort (0.2.0) ttfunk (1.8.0) bigdecimal (~> 3.1) typhoeus (1.4.1) ethon (>= 0.9.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - unicode (0.4.4.5) - unicode-display_width (3.1.4) - unicode-emoji (~> 4.0, >= 4.0.4) - unicode-emoji (4.0.4) - uri (1.0.3) + unicode-display_width (2.5.0) + uri (1.1.1) useragent (0.16.11) - version_gem (1.1.6) + version_gem (1.1.9) warden (1.2.9) rack (>= 2.0.9) - webmock (3.25.1) + webmock (3.23.1) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - websocket-driver (0.7.7) + webrick (1.8.1) + websocket-driver (0.8.0) base64 websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) - yard (0.9.37) - yard-solargraph (0.1.0) - yard (~> 0.9) - zeitwerk (2.7.2) + yard (0.9.36) + zeitwerk (2.6.15) PLATFORMS - aarch64-linux + aarch64-linux-gnu + aarch64-linux-musl + arm-linux + arm-linux-gnu + arm-linux-musl + arm64-darwin + x86-linux + x86-linux-gnu + x86-linux-musl + x86_64-darwin x86_64-linux + x86_64-linux-gnu + x86_64-linux-musl DEPENDENCIES better_errors @@ -623,7 +614,7 @@ DEPENDENCIES webmock RUBY VERSION - ruby 3.4.2p28 + ruby 3.4.7p58 BUNDLED WITH - 2.6.6 + 2.5.11 diff --git a/db/schema.rb b/db/schema.rb index 0e65657d5d..0f1e6f5f9b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,11 +10,11 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2025_11_02_221253) do +ActiveRecord::Schema[8.1].define(version: 2025_11_02_221253) do create_table "activity_types", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.string "name", null: false t.string "abbreviation", null: false t.datetime "created_at", null: false + t.string "name", null: false t.datetime "updated_at", null: false t.index ["abbreviation"], name: "index_activity_types_on_abbreviation", unique: true t.index ["name"], name: "index_activity_types_on_name", unique: true @@ -22,143 +22,143 @@ create_table "auth_tokens", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.datetime "auth_token_expiry", null: false - t.bigint "user_id" t.string "authentication_token", null: false - t.integer "token_type", default: 0, null: false t.datetime "created_at", null: false + t.integer "token_type", default: 0, null: false t.datetime "updated_at", null: false + t.bigint "user_id" t.index ["token_type"], name: "index_auth_tokens_on_token_type" t.index ["user_id"], name: "index_auth_tokens_on_user_id" end create_table "breaks", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "start_date", null: false + t.datetime "created_at", null: false t.integer "number_of_weeks", null: false + t.datetime "start_date", null: false t.bigint "teaching_period_id" - t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["teaching_period_id"], name: "index_breaks_on_teaching_period_id" end create_table "campuses", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.string "name", null: false - t.integer "mode", null: false t.string "abbreviation", null: false t.boolean "active", null: false t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.integer "mode", null: false + t.string "name", null: false t.string "timezone" + t.datetime "updated_at", null: false t.index ["abbreviation"], name: "index_campuses_on_abbreviation", unique: true t.index ["active"], name: "index_campuses_on_active" t.index ["name"], name: "index_campuses_on_name", unique: true end create_table "chip_usages", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + t.datetime "created_at", null: false t.bigint "feedback_chip_id", null: false t.bigint "tutor_id", null: false - t.integer "usage_count", default: 0, null: false - t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "usage_count", default: 0, null: false t.index ["feedback_chip_id"], name: "index_chip_usages_on_feedback_chip_id" t.index ["tutor_id"], name: "index_chip_usages_on_tutor_id" end create_table "comments_read_receipts", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.bigint "task_comment_id", null: false - t.bigint "user_id", null: false t.datetime "created_at", null: false + t.bigint "task_comment_id", null: false t.datetime "updated_at", null: false + t.bigint "user_id", null: false t.index ["task_comment_id", "user_id"], name: "index_comments_read_receipts_on_task_comment_id_and_user_id", unique: true t.index ["task_comment_id"], name: "index_comments_read_receipts_on_task_comment_id" t.index ["user_id"], name: "index_comments_read_receipts_on_user_id" end create_table "d2l_assessment_mappings", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.bigint "unit_id", null: false - t.string "org_unit_id" - t.integer "grade_object_id" t.datetime "created_at", null: false + t.integer "grade_object_id" + t.string "org_unit_id" + t.bigint "unit_id", null: false t.datetime "updated_at", null: false t.index ["unit_id"], name: "index_d2l_assessment_mappings_on_unit_id", unique: true end create_table "discussion_comments", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "time_started" - t.datetime "time_completed" - t.integer "number_of_prompts" t.datetime "created_at", null: false + t.integer "number_of_prompts" + t.datetime "time_completed" + t.datetime "time_started" t.datetime "updated_at", null: false end create_table "feedback_chips", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.string "type" t.text "chip_text" - t.text "description" t.text "comment_text" - t.text "summary_text" + t.datetime "created_at", null: false + t.text "description" t.bigint "learning_outcome_id", null: false t.bigint "parent_chip_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.text "summary_text" t.string "task_status" + t.string "type" + t.datetime "updated_at", null: false t.index ["learning_outcome_id"], name: "index_feedback_chips_on_learning_outcome_id" t.index ["parent_chip_id"], name: "index_feedback_chips_on_parent_chip_id" end create_table "group_memberships", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.bigint "group_id" - t.bigint "project_id" t.boolean "active", default: true t.datetime "created_at" + t.bigint "group_id" + t.bigint "project_id" t.datetime "updated_at" t.index ["group_id"], name: "index_group_memberships_on_group_id" t.index ["project_id"], name: "index_group_memberships_on_project_id" end create_table "group_sets", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.bigint "unit_id" - t.string "name" t.boolean "allow_students_to_create_groups", default: true t.boolean "allow_students_to_manage_groups", default: true - t.boolean "keep_groups_in_same_class", default: false - t.datetime "created_at" - t.datetime "updated_at" t.integer "capacity" + t.datetime "created_at" + t.boolean "keep_groups_in_same_class", default: false t.boolean "locked", default: false, null: false + t.string "name" + t.bigint "unit_id" + t.datetime "updated_at" t.index ["name", "unit_id"], name: "index_group_sets_on_name_and_unit_id", unique: true t.index ["unit_id"], name: "index_group_sets_on_unit_id" end create_table "group_submissions", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + t.datetime "created_at" t.bigint "group_id" t.string "notes" t.bigint "submitted_by_project_id" - t.datetime "created_at" - t.datetime "updated_at" t.bigint "task_definition_id" + t.datetime "updated_at" t.index ["group_id"], name: "index_group_submissions_on_group_id" t.index ["submitted_by_project_id"], name: "index_group_submissions_on_submitted_by_project_id" t.index ["task_definition_id"], name: "index_group_submissions_on_task_definition_id" end create_table "groups", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + t.integer "capacity_adjustment", default: 0, null: false + t.datetime "created_at" t.bigint "group_set_id" - t.bigint "tutorial_id" + t.boolean "locked", default: false, null: false t.string "name" - t.datetime "created_at" + t.bigint "tutorial_id" t.datetime "updated_at" - t.integer "capacity_adjustment", default: 0, null: false - t.boolean "locked", default: false, null: false t.index ["group_set_id"], name: "index_groups_on_group_set_id" t.index ["name", "group_set_id"], name: "index_groups_on_name_and_group_set_id", unique: true t.index ["tutorial_id"], name: "index_groups_on_tutorial_id" end create_table "learning_outcome_links", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + t.datetime "created_at", null: false + t.string "link_type" t.bigint "source_id", null: false t.bigint "target_id", null: false - t.string "link_type" - t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["source_id", "target_id"], name: "index_learning_outcome_links_on_source_id_and_target_id", unique: true t.index ["source_id"], name: "index_learning_outcome_links_on_source_id" @@ -166,85 +166,85 @@ end create_table "learning_outcomes", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.string "short_description" - t.string "full_outcome_description", limit: 4096 t.string "abbreviation" t.bigint "context_id" t.string "context_type" t.datetime "created_at", null: false + t.string "full_outcome_description", limit: 4096 + t.string "short_description" t.datetime "updated_at", null: false t.index ["abbreviation", "context_type", "context_id"], name: "index_learning_outcomes_on_abbreviation_and_context", unique: true t.index ["context_id", "context_type"], name: "index_learning_outcomes_on_context_id_and_context_type" end create_table "logins", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "timestamp" - t.bigint "user_id" t.datetime "created_at", null: false + t.datetime "timestamp" t.datetime "updated_at", null: false + t.bigint "user_id" t.index ["user_id"], name: "index_logins_on_user_id" end create_table "marking_sessions", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.bigint "user_id", null: false - t.bigint "unit_id", null: false + t.datetime "created_at", null: false + t.boolean "during_tutorial" + t.datetime "end_time" t.string "ip_address" t.datetime "start_time" - t.datetime "end_time" - t.boolean "during_tutorial" - t.datetime "created_at", null: false + t.bigint "unit_id", null: false t.datetime "updated_at", null: false + t.bigint "user_id", null: false t.index ["unit_id"], name: "index_marking_sessions_on_unit_id" t.index ["user_id", "unit_id", "ip_address", "updated_at"], name: "index_marking_sessions_on_user_unit_ip_and_time" t.index ["user_id"], name: "index_marking_sessions_on_user_id" end create_table "overseer_assessments", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.bigint "task_id", null: false - t.string "submission_timestamp", null: false + t.datetime "created_at", null: false t.string "result_task_status" t.integer "status", default: 0, null: false - t.datetime "created_at", null: false + t.string "submission_timestamp", null: false + t.bigint "task_id", null: false t.datetime "updated_at", null: false t.index ["task_id", "submission_timestamp"], name: "index_overseer_assessments_on_task_id_and_submission_timestamp", unique: true t.index ["task_id"], name: "index_overseer_assessments_on_task_id" end create_table "overseer_images", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + t.datetime "created_at", null: false + t.datetime "last_pulled_date" t.string "name", null: false + t.integer "pulled_image_status" + t.text "pulled_image_text" t.string "tag", null: false - t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.text "pulled_image_text" - t.integer "pulled_image_status" - t.datetime "last_pulled_date" t.index ["name"], name: "index_overseer_images_on_name", unique: true t.index ["tag"], name: "index_overseer_images_on_tag", unique: true end create_table "projects", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.bigint "unit_id" - t.string "project_role" + t.bigint "assessor_id" + t.bigint "campus_id" + t.boolean "compile_portfolio", default: false t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.boolean "started" - t.string "progress" - t.string "status" - t.string "task_stats" t.boolean "enrolled", default: true - t.integer "target_grade", default: 0 - t.boolean "compile_portfolio", default: false - t.date "portfolio_production_date" - t.bigint "user_id" t.integer "grade", default: 0 t.string "grade_rationale", limit: 4096 - t.bigint "campus_id" - t.integer "submitted_grade" - t.boolean "uses_draft_learning_summary", default: false, null: false t.boolean "portfolio_auto_generated", default: false, null: false t.integer "portfolio_generation_pid" + t.date "portfolio_production_date" + t.string "progress" + t.string "project_role" t.integer "spec_con_days", default: 0, null: false - t.bigint "assessor_id" + t.boolean "started" + t.string "status" + t.integer "submitted_grade" + t.integer "target_grade", default: 0 + t.string "task_stats" + t.bigint "unit_id" + t.datetime "updated_at", null: false + t.bigint "user_id" + t.boolean "uses_draft_learning_summary", default: false, null: false t.index ["assessor_id"], name: "index_projects_on_assessor_id" t.index ["campus_id"], name: "index_projects_on_campus_id" t.index ["enrolled"], name: "index_projects_on_enrolled" @@ -254,19 +254,19 @@ end create_table "roles", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.string "name" - t.text "description" t.datetime "created_at", null: false + t.text "description" + t.string "name" t.datetime "updated_at", null: false end create_table "session_activities", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.bigint "marking_session_id", null: false t.string "action" + t.datetime "created_at", null: false + t.bigint "marking_session_id", null: false t.bigint "project_id" - t.bigint "task_id" t.bigint "task_definition_id" - t.datetime "created_at", null: false + t.bigint "task_id" t.datetime "updated_at", null: false t.index ["action", "task_id", "created_at"], name: "index_session_activities_on_action_task_created_at" t.index ["marking_session_id"], name: "index_session_activities_on_marking_session_id" @@ -276,13 +276,13 @@ end create_table "staff_notes", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + t.datetime "created_at", null: false t.text "note" t.bigint "project_id", null: false - t.bigint "user_id", null: false - t.bigint "staff_notes_id" t.bigint "reply_to_id" - t.datetime "created_at", null: false + t.bigint "staff_notes_id" t.datetime "updated_at", null: false + t.bigint "user_id", null: false t.index ["project_id"], name: "index_staff_notes_on_project_id" t.index ["reply_to_id"], name: "index_staff_notes_on_reply_to_id" t.index ["staff_notes_id"], name: "index_staff_notes_on_staff_notes_id" @@ -290,27 +290,27 @@ end create_table "task_comments", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.bigint "task_id", null: false - t.bigint "user_id", null: false + t.bigint "assessor_id" + t.string "attachment_extension" t.string "comment", limit: 4096 - t.datetime "created_at", null: false - t.bigint "recipient_id" + t.bigint "commentable_id" + t.string "commentable_type" t.string "content_type" - t.string "attachment_extension" - t.bigint "discussion_comment_id" - t.string "type" - t.datetime "time_discussion_started" - t.datetime "time_discussion_completed" - t.integer "number_of_prompts" + t.datetime "created_at", null: false t.datetime "date_extension_assessed" + t.bigint "discussion_comment_id" t.boolean "extension_granted" - t.bigint "assessor_id" - t.bigint "task_status_id" - t.integer "extension_weeks" t.string "extension_response" + t.integer "extension_weeks" + t.integer "number_of_prompts" + t.bigint "recipient_id" t.bigint "reply_to_id" - t.bigint "commentable_id" - t.string "commentable_type" + t.bigint "task_id", null: false + t.bigint "task_status_id" + t.datetime "time_discussion_completed" + t.datetime "time_discussion_started" + t.string "type" + t.bigint "user_id", null: false t.index ["assessor_id"], name: "index_task_comments_on_assessor_id" t.index ["commentable_type", "commentable_id"], name: "index_task_comments_on_commentable_type_and_commentable_id" t.index ["discussion_comment_id"], name: "index_task_comments_on_discussion_comment_id" @@ -322,38 +322,38 @@ end create_table "task_definitions", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.bigint "unit_id" - t.string "name" - t.string "description", limit: 4096 - t.decimal "weighting", precision: 10 - t.datetime "target_date", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false t.string "abbreviation" - t.string "upload_requirements", limit: 4096 - t.integer "target_grade", default: 0 - t.boolean "restrict_status_updates", default: false - t.string "plagiarism_report_url" - t.boolean "plagiarism_updated", default: false - t.integer "plagiarism_warn_pct", default: 50 - t.bigint "group_set_id" + t.boolean "assess_in_portfolio_only", default: false, null: false + t.boolean "assessment_enabled", default: false + t.datetime "created_at", null: false + t.string "description", limit: 4096 t.datetime "due_date" - t.datetime "start_date", null: false + t.bigint "group_set_id" t.boolean "is_graded", default: false + t.boolean "lock_assessments_to_tutorial_stream", default: false, null: false t.integer "max_quality_pts", default: 0 - t.bigint "tutorial_stream_id" - t.boolean "assessment_enabled", default: false + t.string "name" t.bigint "overseer_image_id" - t.string "tii_group_id" - t.string "similarity_language" - t.boolean "scorm_enabled", default: false + t.string "plagiarism_report_url" + t.boolean "plagiarism_updated", default: false + t.integer "plagiarism_warn_pct", default: 50 + t.boolean "restrict_status_updates", default: false t.boolean "scorm_allow_review", default: false + t.integer "scorm_attempt_limit", default: 0 t.boolean "scorm_bypass_test", default: false + t.boolean "scorm_enabled", default: false t.boolean "scorm_time_delay_enabled", default: false - t.integer "scorm_attempt_limit", default: 0 - t.boolean "assess_in_portfolio_only", default: false, null: false + t.string "similarity_language" + t.datetime "start_date", null: false + t.datetime "target_date", null: false + t.integer "target_grade", default: 0 + t.string "tii_group_id" + t.bigint "tutorial_stream_id" + t.bigint "unit_id" + t.datetime "updated_at", null: false + t.string "upload_requirements", limit: 4096 t.boolean "use_resources_for_jplag_base_code", default: false, null: false - t.boolean "lock_assessments_to_tutorial_stream", default: false, null: false + t.decimal "weighting", precision: 10 t.index ["abbreviation", "unit_id"], name: "index_task_definitions_on_abbreviation_and_unit_id", unique: true t.index ["group_set_id"], name: "index_task_definitions_on_group_set_id" t.index ["name", "unit_id"], name: "index_task_definitions_on_name_and_unit_id", unique: true @@ -363,29 +363,29 @@ end create_table "task_engagements", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "engagement_time" + t.datetime "created_at", null: false t.string "engagement" + t.datetime "engagement_time" t.bigint "task_id" - t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["task_id"], name: "index_task_engagements_on_task_id" end create_table "task_pins", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.bigint "task_id", null: false - t.bigint "user_id", null: false t.datetime "created_at", null: false + t.bigint "task_id", null: false t.datetime "updated_at", null: false + t.bigint "user_id", null: false t.index ["task_id", "user_id"], name: "index_task_pins_on_task_id_and_user_id", unique: true t.index ["task_id"], name: "index_task_pins_on_task_id" t.index ["user_id"], name: "fk_rails_915df186ed" end create_table "task_prerequisites", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.bigint "task_definition_id", null: false + t.datetime "created_at", null: false t.bigint "prerequisite_id", null: false + t.bigint "task_definition_id", null: false t.bigint "task_status_id", null: false - t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["prerequisite_id"], name: "index_task_prerequisites_on_prerequisite_id" t.index ["task_definition_id", "prerequisite_id"], name: "idx_on_task_definition_id_prerequisite_id_90b47ca126", unique: true @@ -394,59 +394,59 @@ end create_table "task_similarities", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.bigint "task_id" + t.datetime "created_at" + t.boolean "flagged", default: false t.bigint "other_task_id" t.integer "pct" - t.datetime "created_at" - t.datetime "updated_at" t.string "plagiarism_report_url" - t.boolean "flagged", default: false - t.string "type" + t.bigint "task_id" t.bigint "tii_submission_id" + t.string "type" + t.datetime "updated_at" t.index ["other_task_id"], name: "index_task_similarities_on_other_task_id" t.index ["task_id"], name: "index_task_similarities_on_task_id" t.index ["tii_submission_id"], name: "index_task_similarities_on_tii_submission_id" end create_table "task_statuses", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.string "name" - t.string "description" t.datetime "created_at", null: false + t.string "description" + t.string "name" t.datetime "updated_at", null: false end create_table "task_submissions", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "submission_time" t.datetime "assessment_time" + t.bigint "assessor_id" + t.datetime "created_at", null: false t.string "outcome" + t.datetime "submission_time" t.bigint "task_id" - t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.bigint "assessor_id" t.index ["assessor_id"], name: "index_task_submissions_on_assessor_id" t.index ["task_id"], name: "index_task_submissions_on_task_id" end create_table "tasks", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.bigint "task_definition_id" - t.bigint "project_id" - t.bigint "task_status_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "assessment_date" t.date "completion_date" - t.string "portfolio_evidence" - t.boolean "include_in_portfolio", default: true - t.datetime "file_uploaded_at" - t.bigint "group_submission_id" t.integer "contribution_pct", default: 100 - t.integer "times_assessed", default: 0 - t.datetime "submission_date" - t.datetime "assessment_date" - t.integer "grade" t.integer "contribution_pts", default: 3 - t.integer "quality_pts", default: -1 + t.datetime "created_at", null: false t.integer "extensions", default: 0, null: false + t.datetime "file_uploaded_at" + t.integer "grade" + t.bigint "group_submission_id" + t.boolean "include_in_portfolio", default: true + t.string "portfolio_evidence" + t.bigint "project_id" + t.integer "quality_pts", default: -1 t.integer "scorm_extensions", default: 0, null: false + t.datetime "submission_date" + t.bigint "task_definition_id" + t.bigint "task_status_id" + t.integer "times_assessed", default: 0 + t.datetime "updated_at", null: false t.index ["group_submission_id"], name: "index_tasks_on_group_submission_id" t.index ["project_id", "task_definition_id"], name: "tasks_uniq_proj_task_def", unique: true t.index ["project_id"], name: "index_tasks_on_project_id" @@ -455,43 +455,43 @@ end create_table "teaching_periods", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.string "period", null: false - t.datetime "start_date", null: false - t.datetime "end_date", null: false - t.integer "year", null: false t.datetime "active_until", null: false t.datetime "created_at", null: false + t.datetime "end_date", null: false + t.string "period", null: false + t.datetime "start_date", null: false t.datetime "updated_at", null: false + t.integer "year", null: false t.index ["period", "year"], name: "index_teaching_periods_on_period_and_year", unique: true end create_table "test_attempts", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.bigint "task_id" t.datetime "attempted_time", null: false - t.boolean "terminated", default: false - t.boolean "completion_status", default: false - t.boolean "success_status", default: false - t.float "score_scaled", default: 0.0 t.text "cmi_datamodel" + t.boolean "completion_status", default: false t.datetime "created_at", null: false + t.float "score_scaled", default: 0.0 + t.boolean "success_status", default: false + t.bigint "task_id" + t.boolean "terminated", default: false t.datetime "updated_at", null: false t.index ["task_id"], name: "index_test_attempts_on_task_id" end create_table "tii_actions", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.string "entity_type" - t.bigint "entity_id" - t.string "type" t.boolean "complete", default: false, null: false - t.integer "retries", default: 0, null: false - t.datetime "last_run" t.datetime "complete_at" - t.boolean "retry", default: true, null: false - t.integer "error_code" + t.datetime "created_at", null: false t.text "custom_error_message" + t.bigint "entity_id" + t.string "entity_type" + t.integer "error_code" + t.datetime "last_run" t.text "log" t.string "params", limit: 1024, default: "{}" - t.datetime "created_at", null: false + t.integer "retries", default: 0, null: false + t.boolean "retry", default: true, null: false + t.string "type" t.datetime "updated_at", null: false t.index ["complete"], name: "index_tii_actions_on_complete" t.index ["entity_type", "entity_id"], name: "index_tii_actions_on_entity" @@ -499,29 +499,29 @@ end create_table "tii_group_attachments", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.bigint "task_definition_id", null: false + t.datetime "created_at", null: false + t.string "file_sha1_digest" t.string "filename", null: false t.string "group_attachment_id" - t.string "file_sha1_digest" t.integer "status", default: 0, null: false - t.datetime "created_at", null: false + t.bigint "task_definition_id", null: false t.datetime "updated_at", null: false t.index ["task_definition_id"], name: "index_tii_group_attachments_on_task_definition_id" end create_table "tii_submissions", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.bigint "task_id", null: false - t.bigint "tii_task_similarity_id" - t.bigint "submitted_by_user_id", null: false + t.datetime "created_at", null: false t.string "filename", null: false t.integer "idx", null: false - t.string "submission_id" + t.integer "overall_match_percentage" t.string "similarity_pdf_id" - t.datetime "submitted_at" t.datetime "similarity_request_at" t.integer "status", default: 0, null: false - t.integer "overall_match_percentage" - t.datetime "created_at", null: false + t.string "submission_id" + t.datetime "submitted_at" + t.bigint "submitted_by_user_id", null: false + t.bigint "task_id", null: false + t.bigint "tii_task_similarity_id" t.datetime "updated_at", null: false t.index ["submitted_by_user_id"], name: "index_tii_submissions_on_submitted_by_user_id" t.index ["task_id"], name: "index_tii_submissions_on_task_id" @@ -530,21 +530,21 @@ create_table "tutorial_enrolments", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.datetime "created_at", null: false - t.datetime "updated_at", null: false t.bigint "project_id", null: false t.bigint "tutorial_id", null: false + t.datetime "updated_at", null: false t.index ["project_id"], name: "index_tutorial_enrolments_on_project_id" t.index ["tutorial_id", "project_id"], name: "index_tutorial_enrolments_on_tutorial_id_and_project_id", unique: true t.index ["tutorial_id"], name: "index_tutorial_enrolments_on_tutorial_id" end create_table "tutorial_streams", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.string "name", null: false t.string "abbreviation", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false t.bigint "activity_type_id", null: false + t.datetime "created_at", null: false + t.string "name", null: false t.bigint "unit_id", null: false + t.datetime "updated_at", null: false t.index ["abbreviation", "unit_id"], name: "index_tutorial_streams_on_abbreviation_and_unit_id", unique: true t.index ["abbreviation"], name: "index_tutorial_streams_on_abbreviation" t.index ["activity_type_id"], name: "fk_rails_14ef80da76" @@ -553,18 +553,18 @@ end create_table "tutorials", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.bigint "unit_id" - t.string "meeting_day" - t.string "meeting_time" - t.string "meeting_location" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.string "code" - t.bigint "unit_role_id" t.string "abbreviation" - t.integer "capacity", default: -1 t.bigint "campus_id" + t.integer "capacity", default: -1 + t.string "code" + t.datetime "created_at", null: false + t.string "meeting_day" + t.string "meeting_location" + t.string "meeting_time" t.bigint "tutorial_stream_id" + t.bigint "unit_id" + t.bigint "unit_role_id" + t.datetime "updated_at", null: false t.index ["abbreviation", "unit_id"], name: "index_tutorials_on_abbreviation_and_unit_id", unique: true t.index ["campus_id"], name: "index_tutorials_on_campus_id" t.index ["tutorial_stream_id"], name: "index_tutorials_on_tutorial_stream_id" @@ -573,13 +573,13 @@ end create_table "unit_roles", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.bigint "user_id" - t.bigint "tutorial_id" t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.boolean "observer_only", default: false t.bigint "role_id" + t.bigint "tutorial_id" t.bigint "unit_id" - t.boolean "observer_only", default: false + t.datetime "updated_at", null: false + t.bigint "user_id" t.index ["role_id"], name: "index_unit_roles_on_role_id" t.index ["tutorial_id"], name: "index_unit_roles_on_tutorial_id" t.index ["unit_id"], name: "index_unit_roles_on_unit_id" @@ -587,33 +587,33 @@ end create_table "units", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.string "name" - t.string "description", limit: 4096 - t.datetime "start_date" - t.datetime "end_date" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.string "code" t.boolean "active", default: true - t.datetime "last_plagarism_scan" - t.bigint "teaching_period_id" - t.bigint "main_convenor_id" + t.boolean "allow_flexible_dates", default: false, null: false + t.boolean "allow_student_change_tutorial", default: true, null: false + t.boolean "allow_student_extension_requests", default: true, null: false + t.boolean "archived", default: false + t.boolean "assessment_enabled", default: true t.boolean "auto_apply_extension_before_deadline", default: true, null: false - t.boolean "send_notifications", default: true, null: false - t.boolean "enable_sync_timetable", default: true, null: false - t.boolean "enable_sync_enrolments", default: true, null: false + t.string "code" + t.datetime "created_at", null: false + t.string "description", limit: 4096 t.bigint "draft_task_definition_id" - t.boolean "allow_student_extension_requests", default: true, null: false + t.boolean "enable_sync_enrolments", default: true, null: false + t.boolean "enable_sync_timetable", default: true, null: false + t.datetime "end_date" t.integer "extension_weeks_on_resubmit_request", default: 1, null: false - t.boolean "allow_student_change_tutorial", default: true, null: false - t.boolean "assessment_enabled", default: true + t.datetime "last_plagarism_scan" + t.bigint "main_convenor_id" + t.boolean "mark_late_submissions_as_assess_in_portfolio", default: false, null: false + t.string "name" t.bigint "overseer_image_id" t.datetime "portfolio_auto_generation_date" - t.string "tii_group_context_id" - t.boolean "archived", default: false - t.boolean "allow_flexible_dates", default: false, null: false t.datetime "portfolio_due_date" - t.boolean "mark_late_submissions_as_assess_in_portfolio", default: false, null: false + t.boolean "send_notifications", default: true, null: false + t.datetime "start_date" + t.bigint "teaching_period_id" + t.string "tii_group_context_id" + t.datetime "updated_at", null: false t.index ["draft_task_definition_id"], name: "index_units_on_draft_task_definition_id" t.index ["main_convenor_id"], name: "index_units_on_main_convenor_id" t.index ["overseer_image_id"], name: "index_units_on_overseer_image_id" @@ -621,53 +621,53 @@ end create_table "user_oauth_states", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.bigint "user_id", null: false - t.string "state" t.datetime "created_at", null: false + t.string "state" t.datetime "updated_at", null: false + t.bigint "user_id", null: false t.index ["state"], name: "index_user_oauth_states_on_state", unique: true t.index ["user_id"], name: "index_user_oauth_states_on_user_id" end create_table "user_oauth_tokens", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.bigint "user_id", null: false + t.datetime "created_at", null: false + t.datetime "expires_at" t.integer "provider", default: 0, null: false t.text "token" - t.datetime "expires_at" - t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.bigint "user_id", null: false t.index ["user_id"], name: "index_user_oauth_tokens_on_user_id" end create_table "users", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false - t.string "reset_password_token" - t.datetime "reset_password_sent_at" - t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0 + t.datetime "created_at", null: false t.datetime "current_sign_in_at" - t.datetime "last_sign_in_at" t.string "current_sign_in_ip" - t.string "last_sign_in_ip" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false t.string "first_name" + t.boolean "has_run_first_time_setup", default: false t.string "last_name" - t.string "username" + t.datetime "last_sign_in_at" + t.string "last_sign_in_ip" + t.string "login_id" t.string "nickname" - t.string "unlock_token" - t.bigint "role_id", default: 0 - t.boolean "receive_task_notifications", default: true + t.boolean "opt_in_to_research" t.boolean "receive_feedback_notifications", default: true t.boolean "receive_portfolio_notifications", default: true - t.boolean "opt_in_to_research" - t.boolean "has_run_first_time_setup", default: false - t.string "login_id" + t.boolean "receive_task_notifications", default: true + t.datetime "remember_created_at" + t.datetime "reset_password_sent_at" + t.string "reset_password_token" + t.bigint "role_id", default: 0 + t.integer "sign_in_count", default: 0 t.string "student_id" - t.string "tii_eula_version" t.datetime "tii_eula_date" + t.string "tii_eula_version" t.boolean "tii_eula_version_confirmed", default: false, null: false + t.string "unlock_token" + t.datetime "updated_at", null: false + t.string "username" t.index ["email"], name: "index_users_on_email", unique: true t.index ["login_id"], name: "index_users_on_login_id", unique: true t.index ["role_id"], name: "index_users_on_role_id" @@ -676,23 +676,23 @@ end create_table "webcal_unit_exclusions", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.bigint "webcal_id", null: false - t.bigint "unit_id", null: false t.datetime "created_at", null: false + t.bigint "unit_id", null: false t.datetime "updated_at", null: false + t.bigint "webcal_id", null: false t.index ["unit_id", "webcal_id"], name: "index_webcal_unit_exclusions_on_unit_id_and_webcal_id", unique: true t.index ["unit_id"], name: "index_webcal_unit_exclusions_on_unit_id" t.index ["webcal_id"], name: "fk_rails_d5fab02cb7" end create_table "webcals", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + t.datetime "created_at", null: false t.string "guid", limit: 36, null: false t.boolean "include_start_dates", default: false, null: false - t.bigint "user_id" t.integer "reminder_time" t.string "reminder_unit" - t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.bigint "user_id" t.index ["guid"], name: "index_webcals_on_guid", unique: true t.index ["user_id"], name: "index_webcals_on_user_id", unique: true end From a90072df86b8a976fe2727c3a7d85c365a55fda0 Mon Sep 17 00:00:00 2001 From: Sahiru Withanage Date: Sun, 6 Apr 2025 18:54:24 +1000 Subject: [PATCH 03/22] feat(api): add staff grant extension endpoint Enable staff to grant extensions to multiple students without formal requests. Reuse existing student extension logic through a new service for consistency. Supports flexible academic support and streamlines staff workflows. Relates to the OnTrack Staff Grant Extension design documentation. --- app/api/api_root.rb | 2 + app/api/staff_grant_extension_api.rb | 126 +++++++++++++ app/models/unit.rb | 1 + app/services/extension_service.rb | 52 ++++++ test/api/staff_grant_extension_test.rb | 249 +++++++++++++++++++++++++ 5 files changed, 430 insertions(+) create mode 100644 app/api/staff_grant_extension_api.rb create mode 100644 app/services/extension_service.rb create mode 100644 test/api/staff_grant_extension_test.rb diff --git a/app/api/api_root.rb b/app/api/api_root.rb index 983749c551..39e5cfabca 100644 --- a/app/api/api_root.rb +++ b/app/api/api_root.rb @@ -65,6 +65,7 @@ class ApiRoot < Grape::API mount LearningOutcomesApi mount ProjectsApi mount SettingsApi + mount StaffGrantExtensionApi mount StudentsApi mount Submission::PortfolioApi mount Submission::PortfolioEvidenceApi @@ -118,6 +119,7 @@ class ApiRoot < Grape::API AuthenticationHelpers.add_auth_to GroupSetsApi AuthenticationHelpers.add_auth_to LearningOutcomesApi AuthenticationHelpers.add_auth_to ProjectsApi + AuthenticationHelpers.add_auth_to StaffGrantExtensionApi AuthenticationHelpers.add_auth_to StudentsApi AuthenticationHelpers.add_auth_to Submission::PortfolioApi AuthenticationHelpers.add_auth_to Submission::PortfolioEvidenceApi diff --git a/app/api/staff_grant_extension_api.rb b/app/api/staff_grant_extension_api.rb new file mode 100644 index 0000000000..9c50aa48bb --- /dev/null +++ b/app/api/staff_grant_extension_api.rb @@ -0,0 +1,126 @@ +require 'grape' + +# +# API endpoint for staff to grant extensions to multiple students at once +# +class StaffGrantExtensionApi < Grape::API + helpers AuthenticationHelpers + helpers AuthorisationHelpers + helpers DbHelpers + + before do + authenticated? + error!({ + error: 'Not authorized to grant extensions', + code: 'UNAUTHORIZED', + details: {} + }, 403) unless current_user.has_tutor_capability? + end + + desc 'Grant extensions to multiple students', + detail: 'This endpoint allows staff to grant extensions to multiple students at once for a specific task. The operation is atomic - either all extensions are granted or none are. Students not found in the unit are automatically skipped without affecting the transaction.', + success: [ + { code: 201, message: 'Extensions granted successfully' } + ], + failure: [ + { code: 400, message: 'Some extensions failed to be granted' }, + { code: 403, message: 'Not authorized to grant extensions for this unit' }, + { code: 404, message: 'Unit or task definition not found' }, + { code: 500, message: 'Internal server error' } + ], + response: { + successful: [ + { + student_id: 'Integer - ID of the student', + project_id: 'Integer - ID of the project', + weeks_requested: 'Integer - Number of weeks extension granted', + extension_response: 'String - Human readable message with new due date', + task_status: 'String - Updated status of the task' + } + ], + failed: [ + { + student_id: 'Integer - ID of the student', + project_id: 'Integer - ID of the project', + error: 'String - Error message explaining why extension failed' + } + ], + skipped: [ + { + student_id: 'Integer - ID of the student', + reason: 'String - Reason why the student was skipped' + } + ] + } + params do + requires :student_ids, type: Array[Integer], desc: 'List of student IDs to grant extensions to' + requires :task_definition_id, type: Integer, desc: 'Task definition ID' + requires :weeks_requested, type: Integer, desc: 'Number of weeks to extend by' + requires :comment, type: String, desc: 'Reason for extension' + end + post '/units/:unit_id/staff-grant-extension' do + unit = Unit.find(params[:unit_id]) + task_definition = unit.task_definitions.find(params[:task_definition_id]) + + # Use transaction to ensure atomic operation + ActiveRecord::Base.transaction do + results = { + successful: [], + failed: [], + skipped: [] + } + + params[:student_ids].each do |student_id| + # Find project for this student in the unit + project = unit.projects.find_by(user_id: student_id) + if project.nil? + results[:skipped] << { + student_id: student_id, + reason: 'Student not found in unit' + } + next + end + + result = ExtensionService.grant_extension( + project.id, + task_definition.id, + current_user, + params[:weeks_requested], + params[:comment], + true # is_staff_grant = true + ) + + if result[:success] + extension_comment = result[:result] + results[:successful] << { + student_id: student_id, + project_id: project.id, + weeks_requested: extension_comment.extension_weeks, + extension_response: extension_comment.extension_response, + task_status: extension_comment.task.status + } + else + results[:failed] << { + student_id: student_id, + project_id: project.id, + error: result[:error] + } + # If it's a validation error (403), raise it immediately + error!({ error: result[:error] }, result[:status]) if result[:status] == 403 + end + end + + # If any extensions failed (but not due to validation), rollback the entire transaction + if results[:failed].any? + error!({ error: 'Some extensions failed to be granted', results: results }, 400) + end + + status 201 + present results, with: Grape::Presenters::Presenter + end + rescue ActiveRecord::RecordNotFound + error!({ error: 'Unit or task definition not found' }, 404) + rescue StandardError + error!({ error: 'An unexpected error occurred' }, 500) + end +end diff --git a/app/models/unit.rb b/app/models/unit.rb index 085e3968bd..6d44ecbd96 100644 --- a/app/models/unit.rb +++ b/app/models/unit.rb @@ -36,6 +36,7 @@ def self.permissions :download_jplag_report, :get_marking_sessions, :get_tutor_times, + :grant_extensions ] # What can convenors do with units? diff --git a/app/services/extension_service.rb b/app/services/extension_service.rb new file mode 100644 index 0000000000..59cc7c4a22 --- /dev/null +++ b/app/services/extension_service.rb @@ -0,0 +1,52 @@ +class ExtensionService + def self.grant_extension(project_id, task_definition_id, user, weeks_requested, comment, is_staff_grant = false) + # Find project and task + project = Project.find(project_id) + task_definition = project.unit.task_definitions.find(task_definition_id) + task = project.task_for_task_definition(task_definition) + + # ===== Common Validation Logic (used by both endpoints) ===== + # Validate extension weeks + return { success: false, error: 'Extension weeks cannot be 0', status: 403 } if weeks_requested == 0 + + # Calculate max duration + max_duration = task.weeks_can_extend + duration = weeks_requested + duration = max_duration unless weeks_requested <= max_duration + + # Check if extension would exceed deadline + return { success: false, error: 'Extensions cannot be granted beyond task deadline', status: 403 } if duration <= 0 + + # ===== Student Request Logic (current endpoint) ===== + unless is_staff_grant + # Check task-level authorization for student requests + unless AuthorisationHelpers.authorise?(user, task, :request_extension) + return { success: false, error: 'Not authorised to request an extension for this task', status: 403 } + end + end + + # ===== Staff Grant Logic (new endpoint) ===== + if is_staff_grant + # Check unit-level authorization for staff grants + unless AuthorisationHelpers.authorise?(user, project.unit, :grant_extensions) + return { success: false, error: 'Not authorised to grant extensions for this unit', status: 403 } + end + end + + # ===== Common Extension Logic ===== + # Apply the extension + result = task.apply_for_extension(user, comment, duration) + + # Auto-approve if it's a staff grant + if is_staff_grant + extension_comment = result.becomes(ExtensionComment) + extension_comment.assess_extension(user, true, true) + end + + { success: true, result: result, status: 201 } + rescue ActiveRecord::RecordNotFound => e + { success: false, error: 'Task or project not found', status: 404 } + rescue StandardError => e + { success: false, error: e.message, status: 500 } + end +end diff --git a/test/api/staff_grant_extension_test.rb b/test/api/staff_grant_extension_test.rb new file mode 100644 index 0000000000..ad48c474f6 --- /dev/null +++ b/test/api/staff_grant_extension_test.rb @@ -0,0 +1,249 @@ +require 'test_helper' + +class StaffGrantExtensionTest < ActiveSupport::TestCase + include Rack::Test::Methods + include TestHelpers::AuthHelper + include TestHelpers::JsonHelper + + def app + Rails.application + end + + def test_staff_grant_extension_success + unit = FactoryBot.create(:unit) + project = unit.projects.first + staff = FactoryBot.create(:user, role: Role.tutor) + unit.employ_staff(staff, Role.tutor) + + td = TaskDefinition.new({ + unit_id: unit.id, + tutorial_stream: unit.tutorial_streams.first, + name: 'Staff Grant Extension Test', + description: 'Test task for staff grant extension', + weighting: 4, + target_grade: 0, + start_date: Time.zone.now - 1.week, + target_date: Time.zone.now + 1.week, + due_date: Time.zone.now + 2.weeks, + abbreviation: 'STAFFGRANTTEST', + restrict_status_updates: false, + upload_requirements: [], + plagiarism_warn_pct: 0.8, + is_graded: false, + max_quality_pts: 0 + }) + td.save! + + data_to_post = { + student_ids: [project.student.id], + task_definition_id: td.id, + weeks_requested: 1, + comment: "Staff granted extension" + } + + add_auth_header_for user: staff + post_json "/api/units/#{unit.id}/staff-grant-extension", data_to_post + assert_equal 201, last_response.status + + response = last_response_body + assert response["successful"].length == 1, "Should have one successful extension" + assert response["failed"].empty?, "Should have no failed extensions" + assert response["successful"][0]["student_id"] == project.student.id, "Should match the student ID" + assert response["successful"][0]["weeks_requested"] == 1, "Should have requested 1 week" + assert response["successful"][0]["extension_response"].present?, "Should have extension response" + assert response["successful"][0]["task_status"].present?, "Should have task status" + + td.destroy! + unit.destroy! + end + + def test_staff_grant_extension_unauthorized + unit = FactoryBot.create(:unit) + project = unit.projects.first + student = project.student # Using student instead of staff + td = unit.task_definitions.first + + data_to_post = { + student_ids: [project.student.id], + task_definition_id: td.id, + weeks_requested: 1, + comment: "Unauthorized attempt" + } + + add_auth_header_for user: student + post_json "/api/units/#{unit.id}/staff-grant-extension", data_to_post + assert_equal 403, last_response.status, "Should not allow non-staff to grant extensions" + end + + def test_staff_grant_extension_invalid_weeks + unit = FactoryBot.create(:unit) + project = unit.projects.first + staff = FactoryBot.create(:user, role: Role.tutor) + unit.employ_staff(staff, Role.tutor) + td = unit.task_definitions.first + + data_to_post = { + student_ids: [project.student.id], + task_definition_id: td.id, + weeks_requested: 0, + comment: "Invalid weeks" + } + + add_auth_header_for user: staff + post_json "/api/units/#{unit.id}/staff-grant-extension", data_to_post + assert_equal 403, last_response.status, "Should not allow 0 weeks extension" + end + + def test_staff_grant_extension_negative_weeks + unit = FactoryBot.create(:unit) + project = unit.projects.first + staff = FactoryBot.create(:user, role: Role.tutor) + unit.employ_staff(staff, Role.tutor) + td = unit.task_definitions.first + + data_to_post = { + student_ids: [project.student.id], + task_definition_id: td.id, + weeks_requested: -1, + comment: "Negative weeks" + } + + add_auth_header_for user: staff + post_json "/api/units/#{unit.id}/staff-grant-extension", data_to_post + assert_equal 403, last_response.status, "Should not allow negative weeks extension" + end + + def test_staff_grant_extension_missing_params + unit = FactoryBot.create(:unit) + staff = FactoryBot.create(:user, role: Role.tutor) + unit.employ_staff(staff, Role.tutor) + + data_to_post = { + student_ids: [1], + # Missing task_definition_id and weeks_requested + comment: "Missing params" + } + + add_auth_header_for user: staff + post_json "/api/units/#{unit.id}/staff-grant-extension", data_to_post + assert_equal 400, last_response.status, "Should require all parameters" + end + + def test_staff_grant_extension_transaction_rollback + unit = FactoryBot.create(:unit) + project = unit.projects.first + staff = FactoryBot.create(:user, role: Role.tutor) + unit.employ_staff(staff, Role.tutor) + td = unit.task_definitions.first + + # Test case 1: One valid student, one skipped student + data_to_post = { + student_ids: [project.student.id, 999999], # One valid, one invalid + task_definition_id: td.id, + weeks_requested: 1, + comment: "Transaction test with skipped student" + } + + add_auth_header_for user: staff + post_json "/api/units/#{unit.id}/staff-grant-extension", data_to_post + assert_equal 201, last_response.status, "Should succeed for valid student" + + response = last_response_body + assert response["successful"].length == 1, "Should have one successful extension" + assert response["skipped"].length == 1, "Should have one skipped student" + assert response["failed"].empty?, "Should have no failed extensions" + assert response["skipped"][0]["student_id"] == 999999, "Should have skipped the invalid student ID" + assert response["skipped"][0]["reason"] == "Student not found in unit", "Should have correct skip reason" + + # Verify only the valid student got an extension + task = project.task_for_task_definition(td) + assert task.extensions == 1, "Should have one extension for the valid student" + + # Test case 2: Test actual transaction rollback + # Create a second project to test with + project2 = unit.projects.create!( + user: FactoryBot.create(:user, role: Role.student), + enrolled: true + ) + + # First, grant extensions to both students + data_to_post = { + student_ids: [project.student.id, project2.student.id], + task_definition_id: td.id, + weeks_requested: 1, + comment: "Initial extensions" + } + + add_auth_header_for user: staff + post_json "/api/units/#{unit.id}/staff-grant-extension", data_to_post + assert_equal 201, last_response.status, "Should succeed for both students" + + # Verify both students got extensions + task1 = project.task_for_task_definition(td) + task2 = project2.task_for_task_definition(td) + assert task1.extensions == 2, "First student should have two extensions" + assert task2.extensions == 1, "Second student should have one extension" + + # Now try to grant extensions with a task that would cause a failure + # Use a task that's past its deadline to force a failure + td.due_date = Time.zone.now - 1.day + td.save! + + data_to_post = { + student_ids: [project.student.id, project2.student.id], + task_definition_id: td.id, + weeks_requested: 1, + comment: "Transaction rollback test" + } + + add_auth_header_for user: staff + post_json "/api/units/#{unit.id}/staff-grant-extension", data_to_post + assert_equal 403, last_response.status, "Should fail with 403 when task is past deadline" + + # Verify neither student got a new extension (transaction rolled back) + task1.reload + task2.reload + assert task1.extensions == 2, "First student should still have two extensions" + assert task2.extensions == 1, "Second student should still have one extension" + + td.destroy! + unit.destroy! + end + + def test_staff_grant_extension_invalid_unit + unit = FactoryBot.create(:unit) + project = unit.projects.first + staff = FactoryBot.create(:user, role: Role.tutor) + unit.employ_staff(staff, Role.tutor) + td = unit.task_definitions.first + + data_to_post = { + student_ids: [project.student.id], + task_definition_id: td.id, + weeks_requested: 1, + comment: "Invalid unit" + } + + add_auth_header_for user: staff + post_json "/api/units/999999/staff-grant-extension", data_to_post + assert_equal 404, last_response.status, "Should return 404 for invalid unit" + end + + def test_staff_grant_extension_invalid_task + unit = FactoryBot.create(:unit) + project = unit.projects.first + staff = FactoryBot.create(:user, role: Role.tutor) + unit.employ_staff(staff, Role.tutor) + + data_to_post = { + student_ids: [project.student.id], + task_definition_id: 999999, + weeks_requested: 1, + comment: "Invalid task" + } + + add_auth_header_for user: staff + post_json "/api/units/#{unit.id}/staff-grant-extension", data_to_post + assert_equal 404, last_response.status, "Should return 404 for invalid task definition" + end +end From d27fd3227df426dff2cd5518ee42ae0c66ed4db3 Mon Sep 17 00:00:00 2001 From: SahiruWithanage Date: Thu, 10 Apr 2025 02:43:54 +1000 Subject: [PATCH 04/22] refactor(tests): replace double quotes with single quotes in non-interpolated strings This aligns the test file with the string formatting convention used in the rest of the codebase. Single quotes are preferred when string interpolation is not needed, improving consistency. Reviewed as part of peer feedback. --- test/api/staff_grant_extension_test.rb | 70 +++++++++++++------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/test/api/staff_grant_extension_test.rb b/test/api/staff_grant_extension_test.rb index ad48c474f6..f03b9a62e1 100644 --- a/test/api/staff_grant_extension_test.rb +++ b/test/api/staff_grant_extension_test.rb @@ -38,7 +38,7 @@ def test_staff_grant_extension_success student_ids: [project.student.id], task_definition_id: td.id, weeks_requested: 1, - comment: "Staff granted extension" + comment: 'Staff granted extension' } add_auth_header_for user: staff @@ -46,12 +46,12 @@ def test_staff_grant_extension_success assert_equal 201, last_response.status response = last_response_body - assert response["successful"].length == 1, "Should have one successful extension" - assert response["failed"].empty?, "Should have no failed extensions" - assert response["successful"][0]["student_id"] == project.student.id, "Should match the student ID" - assert response["successful"][0]["weeks_requested"] == 1, "Should have requested 1 week" - assert response["successful"][0]["extension_response"].present?, "Should have extension response" - assert response["successful"][0]["task_status"].present?, "Should have task status" + assert response["successful"].length == 1, 'Should have one successful extension' + assert response["failed"].empty?, 'Should have no failed extensions' + assert response["successful"][0]["student_id"] == project.student.id, 'Should match the student ID' + assert response["successful"][0]["weeks_requested"] == 1, 'Should have requested 1 week' + assert response["successful"][0]["extension_response"].present?, 'Should have extension response' + assert response["successful"][0]["task_status"].present?, 'Should have task status' td.destroy! unit.destroy! @@ -67,12 +67,12 @@ def test_staff_grant_extension_unauthorized student_ids: [project.student.id], task_definition_id: td.id, weeks_requested: 1, - comment: "Unauthorized attempt" + comment: 'Unauthorized attempt' } add_auth_header_for user: student post_json "/api/units/#{unit.id}/staff-grant-extension", data_to_post - assert_equal 403, last_response.status, "Should not allow non-staff to grant extensions" + assert_equal 403, last_response.status, 'Should not allow non-staff to grant extensions' end def test_staff_grant_extension_invalid_weeks @@ -86,12 +86,12 @@ def test_staff_grant_extension_invalid_weeks student_ids: [project.student.id], task_definition_id: td.id, weeks_requested: 0, - comment: "Invalid weeks" + comment: 'Invalid weeks' } add_auth_header_for user: staff post_json "/api/units/#{unit.id}/staff-grant-extension", data_to_post - assert_equal 403, last_response.status, "Should not allow 0 weeks extension" + assert_equal 403, last_response.status, 'Should not allow 0 weeks extension' end def test_staff_grant_extension_negative_weeks @@ -105,12 +105,12 @@ def test_staff_grant_extension_negative_weeks student_ids: [project.student.id], task_definition_id: td.id, weeks_requested: -1, - comment: "Negative weeks" + comment: 'Negative weeks' } add_auth_header_for user: staff post_json "/api/units/#{unit.id}/staff-grant-extension", data_to_post - assert_equal 403, last_response.status, "Should not allow negative weeks extension" + assert_equal 403, last_response.status, 'Should not allow negative weeks extension' end def test_staff_grant_extension_missing_params @@ -121,12 +121,12 @@ def test_staff_grant_extension_missing_params data_to_post = { student_ids: [1], # Missing task_definition_id and weeks_requested - comment: "Missing params" + comment: 'Missing params' } add_auth_header_for user: staff post_json "/api/units/#{unit.id}/staff-grant-extension", data_to_post - assert_equal 400, last_response.status, "Should require all parameters" + assert_equal 400, last_response.status, 'Should require all parameters' end def test_staff_grant_extension_transaction_rollback @@ -141,23 +141,23 @@ def test_staff_grant_extension_transaction_rollback student_ids: [project.student.id, 999999], # One valid, one invalid task_definition_id: td.id, weeks_requested: 1, - comment: "Transaction test with skipped student" + comment: 'Transaction test with skipped student' } add_auth_header_for user: staff post_json "/api/units/#{unit.id}/staff-grant-extension", data_to_post - assert_equal 201, last_response.status, "Should succeed for valid student" + assert_equal 201, last_response.status, 'Should succeed for valid student' response = last_response_body - assert response["successful"].length == 1, "Should have one successful extension" - assert response["skipped"].length == 1, "Should have one skipped student" - assert response["failed"].empty?, "Should have no failed extensions" - assert response["skipped"][0]["student_id"] == 999999, "Should have skipped the invalid student ID" - assert response["skipped"][0]["reason"] == "Student not found in unit", "Should have correct skip reason" + assert response["successful"].length == 1, 'Should have one successful extension' + assert response["skipped"].length == 1, 'Should have one skipped student' + assert response["failed"].empty?, 'Should have no failed extensions' + assert response["skipped"][0]["student_id"] == 999999, 'Should have skipped the invalid student ID' + assert response["skipped"][0]["reason"] == 'Student not found in unit', 'Should have correct skip reason' # Verify only the valid student got an extension task = project.task_for_task_definition(td) - assert task.extensions == 1, "Should have one extension for the valid student" + assert task.extensions == 1, 'Should have one extension for the valid student' # Test case 2: Test actual transaction rollback # Create a second project to test with @@ -171,18 +171,18 @@ def test_staff_grant_extension_transaction_rollback student_ids: [project.student.id, project2.student.id], task_definition_id: td.id, weeks_requested: 1, - comment: "Initial extensions" + comment: 'Initial extensions' } add_auth_header_for user: staff post_json "/api/units/#{unit.id}/staff-grant-extension", data_to_post - assert_equal 201, last_response.status, "Should succeed for both students" + assert_equal 201, last_response.status, 'Should succeed for both students' # Verify both students got extensions task1 = project.task_for_task_definition(td) task2 = project2.task_for_task_definition(td) - assert task1.extensions == 2, "First student should have two extensions" - assert task2.extensions == 1, "Second student should have one extension" + assert task1.extensions == 2, 'First student should have two extensions' + assert task2.extensions == 1, 'Second student should have one extension' # Now try to grant extensions with a task that would cause a failure # Use a task that's past its deadline to force a failure @@ -193,18 +193,18 @@ def test_staff_grant_extension_transaction_rollback student_ids: [project.student.id, project2.student.id], task_definition_id: td.id, weeks_requested: 1, - comment: "Transaction rollback test" + comment: 'Transaction rollback test' } add_auth_header_for user: staff post_json "/api/units/#{unit.id}/staff-grant-extension", data_to_post - assert_equal 403, last_response.status, "Should fail with 403 when task is past deadline" + assert_equal 403, last_response.status, 'Should fail with 403 when task is past deadline' # Verify neither student got a new extension (transaction rolled back) task1.reload task2.reload - assert task1.extensions == 2, "First student should still have two extensions" - assert task2.extensions == 1, "Second student should still have one extension" + assert task1.extensions == 2, 'First student should still have two extensions' + assert task2.extensions == 1, 'Second student should still have one extension' td.destroy! unit.destroy! @@ -221,12 +221,12 @@ def test_staff_grant_extension_invalid_unit student_ids: [project.student.id], task_definition_id: td.id, weeks_requested: 1, - comment: "Invalid unit" + comment: 'Invalid unit' } add_auth_header_for user: staff post_json "/api/units/999999/staff-grant-extension", data_to_post - assert_equal 404, last_response.status, "Should return 404 for invalid unit" + assert_equal 404, last_response.status, 'Should return 404 for invalid unit' end def test_staff_grant_extension_invalid_task @@ -239,11 +239,11 @@ def test_staff_grant_extension_invalid_task student_ids: [project.student.id], task_definition_id: 999999, weeks_requested: 1, - comment: "Invalid task" + comment: 'Invalid task' } add_auth_header_for user: staff post_json "/api/units/#{unit.id}/staff-grant-extension", data_to_post - assert_equal 404, last_response.status, "Should return 404 for invalid task definition" + assert_equal 404, last_response.status, 'Should return 404 for invalid task definition' end end From fb80917432003cac243404835769eb80e840a41a Mon Sep 17 00:00:00 2001 From: SahiruWithanage Date: Fri, 11 Apr 2025 18:47:06 +1000 Subject: [PATCH 05/22] refactor(api): unify extension handling via shared service Linked extension_comments_api (student-requested extensions) to use the shared ExtensionService, previously set up for staff-granted extensions. This refactor ensures both student and staff extension flows use the same logic, improving consistency and reducing duplication. --- app/api/extension_comments_api.rb | 19 +++++++++++++------ app/services/extension_service.rb | 4 ++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/app/api/extension_comments_api.rb b/app/api/extension_comments_api.rb index 1d510f0799..86acd33f22 100644 --- a/app/api/extension_comments_api.rb +++ b/app/api/extension_comments_api.rb @@ -10,13 +10,20 @@ class ExtensionCommentsApi < Grape::API requires :weeks_requested, type: Integer, desc: 'The details of the request' end post '/projects/:project_id/task_def_id/:task_definition_id/request_extension' do - project = Project.find(params[:project_id]) - task_definition = project.unit.task_definitions.find(params[:task_definition_id]) - task = project.task_for_task_definition(task_definition) + # Use the ExtensionService to handle the extension request + result = ExtensionService.grant_extension( + params[:project_id], + params[:task_definition_id], + current_user, + params[:weeks_requested], + params[:comment] + ) - # check permissions using specific permission has with addition of request extension if allowed in unit - unless authorise? current_user, task, :request_extension, ->(role, perm_hash, other) { task.specific_permission_hash(role, perm_hash, other) } - error!({ error: 'Not authorised to request an extension for this task' }, 403) + # Handle the service response + if result[:success] + present result[:result].serialize(current_user), Grape::Presenters::Presenter + else + error!({ error: result[:error] }, result[:status]) end if project.unit.allow_flexible_dates diff --git a/app/services/extension_service.rb b/app/services/extension_service.rb index 59cc7c4a22..323b430b58 100644 --- a/app/services/extension_service.rb +++ b/app/services/extension_service.rb @@ -19,8 +19,8 @@ def self.grant_extension(project_id, task_definition_id, user, weeks_requested, # ===== Student Request Logic (current endpoint) ===== unless is_staff_grant - # Check task-level authorization for student requests - unless AuthorisationHelpers.authorise?(user, task, :request_extension) + # Check task-level authorization for student requests with specific permission hash + unless AuthorisationHelpers.authorise?(user, task, :request_extension, ->(role, perm_hash, other) { task.specific_permission_hash(role, perm_hash, other) }) return { success: false, error: 'Not authorised to request an extension for this task', status: 403 } end end From ed7ae3def4d6fa39dc5656101e2abdc193d3db95 Mon Sep 17 00:00:00 2001 From: SahiruWithanage Date: Sun, 27 Apr 2025 15:51:11 +1000 Subject: [PATCH 06/22] feat(notifications): send extension notifications Implemented backend logic to send emails to tutor and student when extensions are granted. Also enable it so the front end can use the returned information from the api to display notifications. --- app/api/staff_grant_extension_api.rb | 25 +++ app/mailers/notifications_mailer.rb | 79 +++++++- .../extension_granted.html.erb | 124 +++++++++++++ .../extension_granted.text.erb | 38 ++++ config/environments/test.rb | 3 + test/mailers/notifications_mailer_test.rb | 168 ++++++++++++++++++ 6 files changed, 435 insertions(+), 2 deletions(-) create mode 100644 app/views/notifications_mailer/extension_granted.html.erb create mode 100644 app/views/notifications_mailer/extension_granted.text.erb create mode 100644 test/mailers/notifications_mailer_test.rb diff --git a/app/api/staff_grant_extension_api.rb b/app/api/staff_grant_extension_api.rb index 9c50aa48bb..e3d113b130 100644 --- a/app/api/staff_grant_extension_api.rb +++ b/app/api/staff_grant_extension_api.rb @@ -115,9 +115,34 @@ class StaffGrantExtensionApi < Grape::API error!({ error: 'Some extensions failed to be granted', results: results }, 400) end + # Send notifications only if successful and after processing all students + if results[:successful].any? + successful_extensions = results[:successful].map do |result| + # Re-fetch project within the transaction to ensure consistency + project = Project.find(result[:project_id]) + task = project.task_for_task_definition(task_definition) + # Ensure we get the latest extension comment created within this transaction + task.all_comments.where(content_type: :extension).order(created_at: :desc).first + end + + # Filter out any nil results in case a comment wasn't found (shouldn't happen ideally) + successful_extensions.compact! + + if successful_extensions.any? + NotificationsMailer.extension_granted( + successful_extensions, + current_user, + params[:student_ids].count, + results[:failed], + true # is_staff_grant = true + ).deliver_later + end + end + status 201 present results, with: Grape::Presenters::Presenter end + rescue ActiveRecord::RecordNotFound error!({ error: 'Unit or task definition not found' }, 404) rescue StandardError diff --git a/app/mailers/notifications_mailer.rb b/app/mailers/notifications_mailer.rb index 433f2e7eb7..34c98badff 100644 --- a/app/mailers/notifications_mailer.rb +++ b/app/mailers/notifications_mailer.rb @@ -1,7 +1,20 @@ class NotificationsMailer < ApplicationMailer + + # Load configuration values at class level + def self.doubtfire_host + Doubtfire::Application.config.institution[:host] || 'doubtfire.deakin.edu.au' + end + + def self.doubtfire_product_name + Doubtfire::Application.config.institution[:product_name] || 'Doubtfire' + end + + # Set default from address using class methods + default from: -> { "#{self.class.doubtfire_product_name} <#{@granted_by&.email}>" } + def add_general - @doubtfire_host = Doubtfire::Application.config.institution[:host] - @doubtfire_product_name = Doubtfire::Application.config.institution[:product_name] + @doubtfire_host = self.class.doubtfire_host + @doubtfire_product_name = self.class.doubtfire_product_name @unsubscribe_url = "#{@doubtfire_host}/edit_profile" end @@ -108,6 +121,68 @@ def this_these(num) end end + # Sends a summary email to the staff member who granted the extensions + def extension_granted_summary(extensions, granted_by, total_selected, failed_extensions = []) + @granted_by = granted_by + @extensions = extensions + @total_selected = total_selected + @failed_extensions = failed_extensions + @unit = extensions.any? ? extensions.first.task.unit : nil + @is_tutor = true + + add_general + + email_with_name = %("#{@granted_by.name}" <#{@granted_by.email}>) + mail( + to: email_with_name, + subject: @unit ? "#{@unit.name}: Staff Grant Extensions" : "Staff Grant Extensions", + template_name: 'extension_granted' + ) + end + + # Sends a notification to a student about their granted extension + def extension_granted_notification(extension, granted_by) + @granted_by = granted_by + @extension = extension + @task = extension.task + @student = extension.project.student + @is_tutor = false + + add_general + + email_with_name = %("#{@student.name}" <#{@student.email}>) + tutor_email = %("#{@granted_by.name}" <#{@granted_by.email}>) + + mail( + to: email_with_name, + from: tutor_email, + subject: "#{@task.unit.name}: Extension granted for #{@task.task_definition.name}", + template_name: 'extension_granted' + ) + end + + # Main method to handle extension notifications from staff + def extension_granted(extensions, granted_by, total_selected, failed_extensions = [], is_staff_grant = false) + # Only send notifications for staff-granted bulk extensions + return unless is_staff_grant && (extensions.any? || failed_extensions.any?) + + begin + # Send summary to staff member who granted the extensions + extension_granted_summary(extensions, granted_by, total_selected, failed_extensions).deliver_now + + # Send individual notifications only to students who have enabled email notifications + extensions.each do |extension| + student = extension.project.student + if student.receive_task_notifications + extension_granted_notification(extension, granted_by).deliver_now + end + end + rescue => e + Rails.logger.error "Failed to send extension notifications: #{e.message}" + Rails.logger.error e.backtrace.join("\n") + end + end + helper_method :top_task_desc helper_method :were_was helper_method :are_is diff --git a/app/views/notifications_mailer/extension_granted.html.erb b/app/views/notifications_mailer/extension_granted.html.erb new file mode 100644 index 0000000000..d3eb9a397d --- /dev/null +++ b/app/views/notifications_mailer/extension_granted.html.erb @@ -0,0 +1,124 @@ + + + + + + + +
+

Extension Granted

+
+ +
+ <% if @is_tutor %> +

You have granted extensions for the following students:

+ + + + + + + + + + + <% @extensions.each do |extension| %> + + + + + + <% end %> + +
StudentTaskNew Due Date
<%= extension.project.student.name %><%= extension.task.task_definition.name %><%= extension.task.due_date.strftime("%d %b %Y") %>
+ + <% if @failed_extensions.any? %> +

Failed Extensions

+ + + + + + + + + <% @failed_extensions.each do |failed| %> + + + + + <% end %> + +
Student IDError
<%= failed[:student_id] %><%= failed[:error] %>
+ <% end %> + +

Total students selected: <%= @total_selected %>

+

Successfully granted: <%= @extensions.count %>

+ <% if @failed_extensions.any? %> +

Failed: <%= @failed_extensions.count %>

+ <% end %> + <% else %> +

Dear <%= @student.name %>,

+ +

An extension has been granted for your task: <%= @task.task_definition.name %>

+ +

Details:

+
    +
  • New due date: <%= @task.due_date.strftime("%d %b %Y") %>
  • +
  • Granted by: <%= @granted_by.name %>
  • + <% if @extension.comment.present? %> +
  • Comment: <%= @extension.comment %>
  • + <% end %> +
+ <% end %> +
+ + + + diff --git a/app/views/notifications_mailer/extension_granted.text.erb b/app/views/notifications_mailer/extension_granted.text.erb new file mode 100644 index 0000000000..f1d9864653 --- /dev/null +++ b/app/views/notifications_mailer/extension_granted.text.erb @@ -0,0 +1,38 @@ +<% if @is_tutor %> +You have granted extensions for the following students: + +Extensions granted: +<% @extensions.each do |extension| %> +- <%= extension.project.student.name %>: <%= extension.task.task_definition.name %> + New due date: <%= extension.task.due_date.strftime("%B %d, %Y") %> +<% end %> + +Summary: +- Total selected for extension: <%= @total_selected %> +- Successfully granted: <%= @extensions.count %> +<% if @failed_extensions.present? %> +- Failed to grant: <%= @failed_extensions.count %> + +Failed extensions: +<% @failed_extensions.each do |failed| %> +- Student ID <%= failed[:student_id] %>: <%= failed[:error] %> +<% end %> +<% end %> +<% else %> +Dear <%= @student.name %>, + +An extension has been granted for your task: <%= @task.task_definition.name %> + +Details: +- New due date: <%= @task.due_date.strftime("%B %d, %Y") %> +- Granted by: <%= @granted_by.name %> +<% if @extension.comment.present? %> +- Comment: <%= @extension.comment %> +<% end %> +<% end %> + +Cheers, +The <%= @doubtfire_product_name %> Team + +--- +To unsubscribe from these notifications, visit: <%= @unsubscribe_url %> diff --git a/config/environments/test.rb b/config/environments/test.rb index 24fcb3d4cc..b49259719e 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -27,6 +27,9 @@ # The :test delivery method accumulates sent emails in the # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test + config.action_mailer.perform_deliveries = true + config.action_mailer.raise_delivery_errors = true + config.action_mailer.default_url_options = { host: 'test.host' } # Print deprecation notices to the stderr config.active_support.deprecation = :stderr diff --git a/test/mailers/notifications_mailer_test.rb b/test/mailers/notifications_mailer_test.rb new file mode 100644 index 0000000000..d353839fb4 --- /dev/null +++ b/test/mailers/notifications_mailer_test.rb @@ -0,0 +1,168 @@ +require 'test_helper' + +class NotificationsMailerTest < ActionMailer::TestCase + include TestHelpers::AuthHelper + + def setup + # Mock Doubtfire configuration + Doubtfire::Application.config.institution = { + host: 'doubtfire.deakin.edu.au', + product_name: 'Doubtfire' + } + + # Create unit and staff + @unit = FactoryBot.create(:unit) + @staff = FactoryBot.create(:user, role: Role.tutor) + @unit.employ_staff(@staff, Role.tutor) + + # Create a task definition + @task_definition = @unit.task_definitions.create!({ + tutorial_stream: @unit.tutorial_streams.first, + name: 'Test Task', + description: 'Test task for notifications', + weighting: 4, + target_grade: 0, + start_date: Time.zone.now - 1.week, + target_date: Time.zone.now + 1.week, + due_date: Time.zone.now + 2.weeks, + abbreviation: 'TESTTASK', + restrict_status_updates: false, + upload_requirements: [], + plagiarism_warn_pct: 0.8, + is_graded: false, + max_quality_pts: 0 + }) + + # Create students and projects with notification preferences + @students = [] + @projects = [] + + # Create one student with notifications enabled + student_with_notifications = FactoryBot.create(:user, role: Role.student) + student_with_notifications.update(receive_task_notifications: true) + project = @unit.projects.create!(user: student_with_notifications, enrolled: true) + @students << student_with_notifications + @projects << project + + # Create two students without notifications + 2.times do + student = FactoryBot.create(:user, role: Role.student) + student.update(receive_task_notifications: false) + project = @unit.projects.create!(user: student, enrolled: true) + @students << student + @projects << project + end + + # Clear any existing emails before each test + ActionMailer::Base.deliveries.clear + end + + def teardown + @task_definition.destroy! + @unit.destroy! + ActionMailer::Base.deliveries.clear + end + + test 'creates correct extension summary email' do + # Create extensions + extensions = [] + @projects.each do |project| + task = project.task_for_task_definition(@task_definition) + extension = task.apply_for_extension(@staff, 'Test comment', 1) + extension.assess_extension(@staff, true, true) + extensions << extension + end + + # Get the mail object + mail = NotificationsMailer.extension_granted_summary(extensions, @staff, extensions.count) + + # Verify email properties + assert_equal [@staff.email], mail.to + assert_equal "#{@unit.name}: Staff Grant Extensions", mail.subject + assert_match /You have granted extensions for the following students/, mail.html_part.body.to_s + end + + test 'creates correct extension notification email' do + # Create extension + project = @projects.first + task = project.task_for_task_definition(@task_definition) + extension = task.apply_for_extension(@staff, 'Test comment', 1) + extension.assess_extension(@staff, true, true) + + # Get the mail object + mail = NotificationsMailer.extension_granted_notification(extension, @staff) + + # Verify email properties + assert_equal [@students.first.email], mail.to + assert_equal "#{@unit.name}: Extension granted for #{@task_definition.name}", mail.subject + assert_match /Dear #{@students.first.name}/, mail.html_part.body.to_s + end + + test 'creates correct extension summary with failed extensions' do + # Create successful extensions + successful_extensions = [] + @projects.each do |project| + task = project.task_for_task_definition(@task_definition) + extension = task.apply_for_extension(@staff, 'Test comment', 1) + extension.assess_extension(@staff, true, true) + successful_extensions << extension + end + + # Create failed extensions + failed_extensions = [ + { student_id: 999, error: 'Student not found in unit' }, + { student_id: 1000, error: 'Extension cannot be granted beyond task deadline' } + ] + + # Get the mail object + mail = NotificationsMailer.extension_granted_summary( + successful_extensions, + @staff, + successful_extensions.count, + failed_extensions + ) + + # Verify email includes failed extensions + assert_equal [@staff.email], mail.to + assert_match /Failed Extensions/, mail.html_part.body.to_s + assert_match /999/, mail.html_part.body.to_s + assert_match /1000/, mail.html_part.body.to_s + end + + test 'creates correct extension notification with special characters' do + # Create task with special characters + special_task = @unit.task_definitions.create!({ + tutorial_stream: @unit.tutorial_streams.first, + name: 'Test Task with !@#$%^&*()', + description: 'Test task with special characters', + weighting: 4, + target_grade: 0, + start_date: Time.zone.now - 1.week, + target_date: Time.zone.now + 1.week, + due_date: Time.zone.now + 2.weeks, + abbreviation: 'SPECIAL', + restrict_status_updates: false, + upload_requirements: [], + plagiarism_warn_pct: 0.8, + is_graded: false, + max_quality_pts: 0 + }) + + # Create extension + project = @projects.first + task = project.task_for_task_definition(special_task) + extension = task.apply_for_extension(@staff, 'Special characters test', 1) + extension.assess_extension(@staff, true, true) + + # Get the mail object + mail = NotificationsMailer.extension_granted_notification(extension, @staff) + + # Verify email handles special characters + assert_equal [@students.first.email], mail.to + assert_equal "#{@unit.name}: Extension granted for #{special_task.name}", mail.subject + assert_match /Dear #{@students.first.name}/, mail.html_part.body.to_s + + # Clean up + special_task.destroy! + end +end From c4162a1222332ce63d931826dc1e46e8a3ee6fa7 Mon Sep 17 00:00:00 2001 From: Sahiru Withanage <165999067+SahiruWithanage@users.noreply.github.com> Date: Sun, 11 May 2025 22:40:43 +1000 Subject: [PATCH 07/22] Make a comment line change A comment line change made in the staff grant extension feature branch that hasn't been updated here. Changing to keep the consistency. --- app/services/extension_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/extension_service.rb b/app/services/extension_service.rb index 323b430b58..01af8cc4c4 100644 --- a/app/services/extension_service.rb +++ b/app/services/extension_service.rb @@ -17,7 +17,7 @@ def self.grant_extension(project_id, task_definition_id, user, weeks_requested, # Check if extension would exceed deadline return { success: false, error: 'Extensions cannot be granted beyond task deadline', status: 403 } if duration <= 0 - # ===== Student Request Logic (current endpoint) ===== + # ===== Student-Initiated Extension Logic (current endpoint) ===== unless is_staff_grant # Check task-level authorization for student requests with specific permission hash unless AuthorisationHelpers.authorise?(user, task, :request_extension, ->(role, perm_hash, other) { task.specific_permission_hash(role, perm_hash, other) }) From b885a34a95c10dbf883b73f85e893e02a5bbdac0 Mon Sep 17 00:00:00 2001 From: SahiruWithanage Date: Mon, 19 May 2025 23:04:53 +1000 Subject: [PATCH 08/22] fix(mailer): fix email sender and add error handling --- app/api/staff_grant_extension_api.rb | 20 +++++--- app/mailers/notifications_mailer.rb | 7 +-- test/mailers/notifications_mailer_test.rb | 58 +++++++++++++++++++++++ 3 files changed, 75 insertions(+), 10 deletions(-) diff --git a/app/api/staff_grant_extension_api.rb b/app/api/staff_grant_extension_api.rb index e3d113b130..52ae1d3105 100644 --- a/app/api/staff_grant_extension_api.rb +++ b/app/api/staff_grant_extension_api.rb @@ -129,13 +129,19 @@ class StaffGrantExtensionApi < Grape::API successful_extensions.compact! if successful_extensions.any? - NotificationsMailer.extension_granted( - successful_extensions, - current_user, - params[:student_ids].count, - results[:failed], - true # is_staff_grant = true - ).deliver_later + begin + NotificationsMailer.extension_granted( + successful_extensions, + current_user, + params[:student_ids].count, + results[:failed], + true # is_staff_grant = true + ).deliver_later + rescue => e + # Log error but don't fail the API call, as extensions were successfully granted + Rails.logger.error "Failed to queue extension notification emails: #{e.message}" + Rails.logger.error e.backtrace.join("\n") + end end end diff --git a/app/mailers/notifications_mailer.rb b/app/mailers/notifications_mailer.rb index 34c98badff..389124d481 100644 --- a/app/mailers/notifications_mailer.rb +++ b/app/mailers/notifications_mailer.rb @@ -9,9 +9,6 @@ def self.doubtfire_product_name Doubtfire::Application.config.institution[:product_name] || 'Doubtfire' end - # Set default from address using class methods - default from: -> { "#{self.class.doubtfire_product_name} <#{@granted_by&.email}>" } - def add_general @doubtfire_host = self.class.doubtfire_host @doubtfire_product_name = self.class.doubtfire_product_name @@ -133,8 +130,12 @@ def extension_granted_summary(extensions, granted_by, total_selected, failed_ext add_general email_with_name = %("#{@granted_by.name}" <#{@granted_by.email}>) + # Set explicit from address using product name and a default sender + from_address = %("#{self.class.doubtfire_product_name}" ) + mail( to: email_with_name, + from: from_address, subject: @unit ? "#{@unit.name}: Staff Grant Extensions" : "Staff Grant Extensions", template_name: 'extension_granted' ) diff --git a/test/mailers/notifications_mailer_test.rb b/test/mailers/notifications_mailer_test.rb index d353839fb4..393b380298 100644 --- a/test/mailers/notifications_mailer_test.rb +++ b/test/mailers/notifications_mailer_test.rb @@ -80,6 +80,10 @@ def teardown assert_equal [@staff.email], mail.to assert_equal "#{@unit.name}: Staff Grant Extensions", mail.subject assert_match /You have granted extensions for the following students/, mail.html_part.body.to_s + + # Verify from address contains no-reply + assert_includes mail.from.first, "no-reply@" + assert_includes mail.from.first, NotificationsMailer.doubtfire_host end test 'creates correct extension notification email' do @@ -96,6 +100,9 @@ def teardown assert_equal [@students.first.email], mail.to assert_equal "#{@unit.name}: Extension granted for #{@task_definition.name}", mail.subject assert_match /Dear #{@students.first.name}/, mail.html_part.body.to_s + + # Verify from address contains staff email + assert_includes mail.from.first, @staff.email end test 'creates correct extension summary with failed extensions' do @@ -127,6 +134,10 @@ def teardown assert_match /Failed Extensions/, mail.html_part.body.to_s assert_match /999/, mail.html_part.body.to_s assert_match /1000/, mail.html_part.body.to_s + + # Verify from address contains no-reply + assert_includes mail.from.first, "no-reply@" + assert_includes mail.from.first, NotificationsMailer.doubtfire_host end test 'creates correct extension notification with special characters' do @@ -162,7 +173,54 @@ def teardown assert_equal "#{@unit.name}: Extension granted for #{special_task.name}", mail.subject assert_match /Dear #{@students.first.name}/, mail.html_part.body.to_s + # Verify from address contains staff email + assert_includes mail.from.first, @staff.email + # Clean up special_task.destroy! end + + test 'creates correct weekly staff summary email' do + # Create data for summary stats + summary_stats = { + unit: @unit, + week_start: Time.zone.now - 1.week, + week_end: Time.zone.now, + staff: {} + } + + unit_role = @unit.unit_roles.find_by(user: @staff) + summary_stats[:staff][unit_role] = { data: "test data" } + + # Get the mail object + mail = NotificationsMailer.weekly_staff_summary(unit_role, summary_stats) + + # Verify email properties + assert_equal [@staff.email], mail.to + assert_equal "#{@unit.name}: Weekly Summary", mail.subject + + # Verify from address contains convenor email + assert_includes mail.from.first, @unit.main_convenor_user.email + end + + test 'creates correct weekly student summary email' do + # Create data for summary stats + summary_stats = { + unit: @unit, + week_start: Time.zone.now - 1.week, + week_end: Time.zone.now + } + + project = @projects.first + + # Get the mail object + mail = NotificationsMailer.weekly_student_summary(project, summary_stats, false) + + # Verify email properties + assert_equal [@students.first.email], mail.to + assert_equal "#{@unit.name}: Weekly Summary", mail.subject + + # Verify from address contains tutor email + assert_includes mail.from.first, project.main_convenor_user.email + end end From 82ea701dc79dbd9fb2120a30d96d7fdeeb3c6794 Mon Sep 17 00:00:00 2001 From: samindiii Date: Sun, 18 May 2025 16:56:39 +1000 Subject: [PATCH 09/22] feat: add notification table and model Also, define relation with user --- app/models/notification.rb | 3 +++ app/models/user.rb | 1 + db/migrate/20250518011250_create_notifications.rb | 10 ++++++++++ 3 files changed, 14 insertions(+) create mode 100644 app/models/notification.rb create mode 100644 db/migrate/20250518011250_create_notifications.rb diff --git a/app/models/notification.rb b/app/models/notification.rb new file mode 100644 index 0000000000..c99183b196 --- /dev/null +++ b/app/models/notification.rb @@ -0,0 +1,3 @@ +class Notification < ApplicationRecord + belongs_to :user +end diff --git a/app/models/user.rb b/app/models/user.rb index c360c140ff..07c8b62edb 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -150,6 +150,7 @@ def token_for_text?(a_token, token_type) has_many :chip_usage, dependent: :destroy, inverse_of: :tutor, class_name: 'Feedback::ChipUsage' has_many :marking_sessions, dependent: :destroy + has_many :notifications, dependent: :destroy # Model validations/constraints validates :first_name, presence: true diff --git a/db/migrate/20250518011250_create_notifications.rb b/db/migrate/20250518011250_create_notifications.rb new file mode 100644 index 0000000000..0f83267269 --- /dev/null +++ b/db/migrate/20250518011250_create_notifications.rb @@ -0,0 +1,10 @@ +class CreateNotifications < ActiveRecord::Migration[7.1] + def change + create_table :notifications do |t| + t.integer :user_id + t.string :message + + t.timestamps + end + end +end From 6a310ba764af07d6ec7cab7c3aa8f1120234af4f Mon Sep 17 00:00:00 2001 From: samindiii Date: Sun, 18 May 2025 16:57:45 +1000 Subject: [PATCH 10/22] feat: define notification api to GET and DELETE --- app/api/api_root.rb | 5 +++++ app/api/notifications_api.rb | 29 +++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 app/api/notifications_api.rb diff --git a/app/api/api_root.rb b/app/api/api_root.rb index 39e5cfabca..605ec9a6e9 100644 --- a/app/api/api_root.rb +++ b/app/api/api_root.rb @@ -63,6 +63,11 @@ class ApiRoot < Grape::API mount ScormExtensionCommentsApi mount GroupSetsApi mount LearningOutcomesApi + mount LearningOutcomesApi + # mount LearningAlignmentApi + # the mount above is available in 9.x but has not been ported to `10.0.x` + mount NotificationsApi + mount ProjectsApi mount ProjectsApi mount SettingsApi mount StaffGrantExtensionApi diff --git a/app/api/notifications_api.rb b/app/api/notifications_api.rb new file mode 100644 index 0000000000..371b08c5b9 --- /dev/null +++ b/app/api/notifications_api.rb @@ -0,0 +1,29 @@ +class NotificationsApi < Grape::API + helpers AuthenticationHelpers + helpers AuthorisationHelpers + + before do + authenticated? + end + + desc 'Get current user notifications' + get '/notifications' do + notifications = current_user.notifications.order(created_at: :desc) + # Return array of notifications as JSON (id and message only) + notifications.as_json(only: [:id, :message]) + end + + desc 'Delete user notification by id' + delete '/notifications/:id' do + notification = current_user.notifications.find_by(id: params[:id]) + error!({ error: 'Notification not found' }, 404) unless notification + notification.destroy + status 204 + end + + desc 'Delete all user notifications' + delete '/notifications' do + current_user.notifications.delete_all + status 204 + end +end From 3c996710f9f2278f7806044c92c38600695f0c02 Mon Sep 17 00:00:00 2001 From: samindiii Date: Sun, 18 May 2025 16:58:59 +1000 Subject: [PATCH 11/22] Create in-system notifications for students with successfull extensions --- app/api/staff_grant_extension_api.rb | 29 +++++++++++++++----------- test/api/staff_grant_extension_test.rb | 7 +++++++ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/app/api/staff_grant_extension_api.rb b/app/api/staff_grant_extension_api.rb index 52ae1d3105..254f8b5fbc 100644 --- a/app/api/staff_grant_extension_api.rb +++ b/app/api/staff_grant_extension_api.rb @@ -129,18 +129,23 @@ class StaffGrantExtensionApi < Grape::API successful_extensions.compact! if successful_extensions.any? - begin - NotificationsMailer.extension_granted( - successful_extensions, - current_user, - params[:student_ids].count, - results[:failed], - true # is_staff_grant = true - ).deliver_later - rescue => e - # Log error but don't fail the API call, as extensions were successfully granted - Rails.logger.error "Failed to queue extension notification emails: #{e.message}" - Rails.logger.error e.backtrace.join("\n") + NotificationsMailer.extension_granted( + successful_extensions, + current_user, + params[:student_ids].count, + results[:failed], + true # is_staff_grant = true + ).deliver_later + + # Create in-system notifications for successful extensions + results[:successful].each do |result| + student = User.find_by(id: result[:student_id]) + next unless student + + Notification.create!( + user_id: student.id, + message: "#{unit.name}: You were granted an extension for task '#{task_definition.name}'." + ) end end end diff --git a/test/api/staff_grant_extension_test.rb b/test/api/staff_grant_extension_test.rb index f03b9a62e1..a90d74dcd0 100644 --- a/test/api/staff_grant_extension_test.rb +++ b/test/api/staff_grant_extension_test.rb @@ -53,6 +53,13 @@ def test_staff_grant_extension_success assert response["successful"][0]["extension_response"].present?, 'Should have extension response' assert response["successful"][0]["task_status"].present?, 'Should have task status' + notifications = Notification.where(user_id: project.student.id) + assert_equal 1, notifications.count, 'Should create one notification for the student' + notification = notifications.first + assert_match /You were granted an extension for task/, notification.message + assert_match /#{td.name}/, notification.message + assert_match /#{unit.name}/, notification.message + td.destroy! unit.destroy! end From dca5e00266699749979ff1952ab67b874dda9db1 Mon Sep 17 00:00:00 2001 From: SahiruWithanage Date: Sun, 14 Sep 2025 15:56:37 +1000 Subject: [PATCH 12/22] feat: fix email notifications for SGE feature - Fix delivery method consistency (deliver_now vs deliver_later) - Fix ExtensionComment retrieval logic to prevent race conditions - Add proper error handling for email failures - Fix mailer method calls (class methods vs instance methods) - Add input validation for API parameters - Improve error logging and debugging - Ensure thread-safe extension processing The email notification system now works correctly: - Staff receive summary emails for all granted extensions - Students receive individual notification emails - Proper error handling prevents API failures on email issues - All email delivery methods are consistent across the project --- app/api/staff_grant_extension_api.rb | 46 ++++++++++++++++++---------- app/mailers/notifications_mailer.rb | 4 +-- app/models/unit.rb | 3 +- 3 files changed, 34 insertions(+), 19 deletions(-) diff --git a/app/api/staff_grant_extension_api.rb b/app/api/staff_grant_extension_api.rb index 254f8b5fbc..fe93b3bb30 100644 --- a/app/api/staff_grant_extension_api.rb +++ b/app/api/staff_grant_extension_api.rb @@ -55,8 +55,8 @@ class StaffGrantExtensionApi < Grape::API params do requires :student_ids, type: Array[Integer], desc: 'List of student IDs to grant extensions to' requires :task_definition_id, type: Integer, desc: 'Task definition ID' - requires :weeks_requested, type: Integer, desc: 'Number of weeks to extend by' - requires :comment, type: String, desc: 'Reason for extension' + requires :weeks_requested, type: Integer, desc: 'Number of weeks to extend by (1-4)' + requires :comment, type: String, desc: 'Reason for extension (max 300 characters)' end post '/units/:unit_id/staff-grant-extension' do unit = Unit.find(params[:unit_id]) @@ -97,7 +97,8 @@ class StaffGrantExtensionApi < Grape::API project_id: project.id, weeks_requested: extension_comment.extension_weeks, extension_response: extension_comment.extension_response, - task_status: extension_comment.task.status + task_status: extension_comment.task.status, + extension_comment: extension_comment # Store internally for notifications } else results[:failed] << { @@ -117,25 +118,38 @@ class StaffGrantExtensionApi < Grape::API # Send notifications only if successful and after processing all students if results[:successful].any? + # Use the extension comments directly from the service results (thread-safe) successful_extensions = results[:successful].map do |result| - # Re-fetch project within the transaction to ensure consistency - project = Project.find(result[:project_id]) - task = project.task_for_task_definition(task_definition) - # Ensure we get the latest extension comment created within this transaction - task.all_comments.where(content_type: :extension).order(created_at: :desc).first + extension_comment = result[:extension_comment] + if extension_comment.nil? + Rails.logger.warn "No extension comment found for project #{result[:project_id]}" + nil + else + Rails.logger.debug "Using extension comment: #{extension_comment.id} for project #{result[:project_id]}" + extension_comment + end end - # Filter out any nil results in case a comment wasn't found (shouldn't happen ideally) + # Filter out any nil results in case a comment wasn't found successful_extensions.compact! + Rails.logger.info "Processing #{successful_extensions.count} successful extensions for notifications" if successful_extensions.any? - NotificationsMailer.extension_granted( - successful_extensions, - current_user, - params[:student_ids].count, - results[:failed], - true # is_staff_grant = true - ).deliver_later + begin + Rails.logger.info "Sending extension notifications for #{successful_extensions.count} extensions" + NotificationsMailer.extension_granted( + successful_extensions, + current_user, + params[:student_ids].count, + results[:failed], + true # is_staff_grant = true + ).deliver_now + Rails.logger.info "Extension notifications sent successfully" + rescue => e + Rails.logger.error "Failed to send extension notifications: #{e.message}" + Rails.logger.error e.backtrace.join("\n") + # Don't fail the entire request if email fails, but log the error + end # Create in-system notifications for successful extensions results[:successful].each do |result| diff --git a/app/mailers/notifications_mailer.rb b/app/mailers/notifications_mailer.rb index 389124d481..cc42bc341a 100644 --- a/app/mailers/notifications_mailer.rb +++ b/app/mailers/notifications_mailer.rb @@ -169,13 +169,13 @@ def extension_granted(extensions, granted_by, total_selected, failed_extensions begin # Send summary to staff member who granted the extensions - extension_granted_summary(extensions, granted_by, total_selected, failed_extensions).deliver_now + NotificationsMailer.extension_granted_summary(extensions, granted_by, total_selected, failed_extensions).deliver_now # Send individual notifications only to students who have enabled email notifications extensions.each do |extension| student = extension.project.student if student.receive_task_notifications - extension_granted_notification(extension, granted_by).deliver_now + NotificationsMailer.extension_granted_notification(extension, granted_by).deliver_now end end rescue => e diff --git a/app/models/unit.rb b/app/models/unit.rb index 6d44ecbd96..c0a4e9beab 100644 --- a/app/models/unit.rb +++ b/app/models/unit.rb @@ -65,7 +65,8 @@ def self.permissions :get_tutor_times, :get_tutor_times_summary, :get_marking_sessions, - :upload_grades_csv + :upload_grades_csv, + :grant_extensions ] # What can admin do with units? From 6bee233c71ee392a620d617ea2e6a3aa8b3c8aaa Mon Sep 17 00:00:00 2001 From: SahiruWithanage Date: Sun, 14 Sep 2025 22:09:11 +1000 Subject: [PATCH 13/22] refactor: use unit code instead of unit name in extension notifications - Change notification message from unit.name to unit.code for better consistency - Unit codes are more concise and standardized (e.g., 'SITXXX') - Improves readability of extension grant notifications --- app/api/staff_grant_extension_api.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/api/staff_grant_extension_api.rb b/app/api/staff_grant_extension_api.rb index fe93b3bb30..e63c3360d5 100644 --- a/app/api/staff_grant_extension_api.rb +++ b/app/api/staff_grant_extension_api.rb @@ -158,7 +158,7 @@ class StaffGrantExtensionApi < Grape::API Notification.create!( user_id: student.id, - message: "#{unit.name}: You were granted an extension for task '#{task_definition.name}'." + message: "#{unit.code}: You were granted an extension for task '#{task_definition.name}'." ) end end From 755f467a4b3d6eeb3aa93cf55839bb5d54033fd6 Mon Sep 17 00:00:00 2001 From: Steven Dalamaras Date: Sat, 17 Jan 2026 22:52:54 +1000 Subject: [PATCH 14/22] chore: update gems, regen lockfile, schema version --- Gemfile.lock | 523 ++++++++++++++++++++++---------------------- app/api/api_root.rb | 1 - db/schema.rb | 18 +- 3 files changed, 283 insertions(+), 259 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index b473c814a4..68cf502eb6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,32 +1,30 @@ GEM remote: https://rubygems.org/ specs: - Ascii85 (1.1.1) - action_text-trix (2.1.16) - railties - actioncable (8.1.2) - actionpack (= 8.1.2) - activesupport (= 8.1.2) + Ascii85 (2.0.1) + actioncable (8.0.2) + actionpack (= 8.0.2) + activesupport (= 8.0.2) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (8.1.2) - actionpack (= 8.1.2) - activejob (= 8.1.2) - activerecord (= 8.1.2) - activestorage (= 8.1.2) - activesupport (= 8.1.2) + actionmailbox (8.0.2) + actionpack (= 8.0.2) + activejob (= 8.0.2) + activerecord (= 8.0.2) + activestorage (= 8.0.2) + activesupport (= 8.0.2) mail (>= 2.8.0) - actionmailer (8.1.2) - actionpack (= 8.1.2) - actionview (= 8.1.2) - activejob (= 8.1.2) - activesupport (= 8.1.2) + actionmailer (8.0.2) + actionpack (= 8.0.2) + actionview (= 8.0.2) + activejob (= 8.0.2) + activesupport (= 8.0.2) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (8.1.2) - actionview (= 8.1.2) - activesupport (= 8.1.2) + actionpack (8.0.2) + actionview (= 8.0.2) + activesupport (= 8.0.2) nokogiri (>= 1.8.5) rack (>= 2.2.4) rack-session (>= 1.0.1) @@ -34,73 +32,73 @@ GEM rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) - actiontext (8.1.2) - action_text-trix (~> 2.1.15) - actionpack (= 8.1.2) - activerecord (= 8.1.2) - activestorage (= 8.1.2) - activesupport (= 8.1.2) + actiontext (8.0.2) + actionpack (= 8.0.2) + activerecord (= 8.0.2) + activestorage (= 8.0.2) + activesupport (= 8.0.2) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (8.1.2) - activesupport (= 8.1.2) + actionview (8.0.2) + activesupport (= 8.0.2) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - activejob (8.1.2) - activesupport (= 8.1.2) + activejob (8.0.2) + activesupport (= 8.0.2) globalid (>= 0.3.6) - activemodel (8.1.2) - activesupport (= 8.1.2) - activerecord (8.1.2) - activemodel (= 8.1.2) - activesupport (= 8.1.2) + activemodel (8.0.2) + activesupport (= 8.0.2) + activerecord (8.0.2) + activemodel (= 8.0.2) + activesupport (= 8.0.2) timeout (>= 0.4.0) - activestorage (8.1.2) - actionpack (= 8.1.2) - activejob (= 8.1.2) - activerecord (= 8.1.2) - activesupport (= 8.1.2) + activestorage (8.0.2) + actionpack (= 8.0.2) + activejob (= 8.0.2) + activerecord (= 8.0.2) + activesupport (= 8.0.2) marcel (~> 1.0) - activesupport (8.1.2) + activesupport (8.0.2) base64 + benchmark (>= 0.3) bigdecimal concurrent-ruby (~> 1.0, >= 1.3.1) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) - json logger (>= 1.4.2) minitest (>= 5.1) securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) uri (>= 0.13.1) - addressable (2.8.6) - public_suffix (>= 2.0.2, < 6.0) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) aes_key_wrap (1.1.0) afm (0.2.2) - amq-protocol (2.3.2) - ast (2.4.2) + amq-protocol (2.3.3) + ast (2.4.3) backport (1.2.0) base64 (0.2.0) bcrypt (3.1.20) - benchmark (0.3.0) + benchmark (0.4.0) better_errors (2.10.1) erubi (>= 1.0.0) rack (>= 0.9.0) rouge (>= 1.0.0) - bigdecimal (3.1.8) + bigdecimal (3.1.9) bindata (2.5.0) - bootsnap (1.18.3) + bootsnap (1.18.4) msgpack (~> 1.2) builder (3.3.0) - bunny (2.22.0) - amq-protocol (~> 2.3, >= 2.3.1) + bunny (2.24.0) + amq-protocol (~> 2.3) sorted_set (~> 1, >= 1.0.2) bunny-pub-sub (0.5.2) bunny (~> 2.14) - byebug (11.1.3) + byebug (12.0.0) + cgi (0.4.2) chronic_duration (0.10.6) numerizer (~> 0.1.1) ci_reporter (2.1.0) @@ -109,18 +107,21 @@ GEM code_analyzer (0.5.5) sexp_processor coderay (1.1.3) - concurrent-ruby (1.3.3) - connection_pool (2.4.1) + concurrent-ruby (1.3.5) + connection_pool (2.5.0) crack (1.0.0) bigdecimal rexml crass (1.0.6) - csv (3.3.0) - database_cleaner-active_record (2.1.0) + cronex (0.15.0) + tzinfo + unicode (>= 0.4.4.5) + csv (3.3.3) + database_cleaner-active_record (2.2.0) activerecord (>= 5.a) database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) - date (3.5.1) + date (3.4.1) devise (4.9.4) bcrypt (~> 3.0) orm_adapter (~> 0.1) @@ -130,139 +131,145 @@ GEM devise_ldap_authenticatable (0.8.7) devise (>= 3.4.1) net-ldap (>= 0.16.0) - diff-lcs (1.5.1) - docile (1.4.0) + diff-lcs (1.6.1) + docile (1.4.1) domain_name (0.6.20240107) - dotenv (3.1.2) + dotenv (3.1.7) drb (2.2.1) - dry-core (1.0.1) + dry-core (1.1.0) concurrent-ruby (~> 1.0) + logger zeitwerk (~> 2.6) - dry-inflector (1.0.0) - dry-logic (1.5.0) + dry-inflector (1.2.0) + dry-logic (1.6.0) + bigdecimal concurrent-ruby (~> 1.0) - dry-core (~> 1.0, < 2) + dry-core (~> 1.1) zeitwerk (~> 2.6) - dry-types (1.7.2) + dry-types (1.8.2) bigdecimal (~> 3.0) concurrent-ruby (~> 1.0) dry-core (~> 1.0) dry-inflector (~> 1.0) dry-logic (~> 1.4) zeitwerk (~> 2.6) - e2mmap (0.1.0) - erubi (1.12.0) + erb (4.0.4) + cgi (>= 0.3.3) + erubi (1.13.1) erubis (2.7.0) et-orbi (1.2.11) tzinfo ethon (0.16.0) ffi (>= 1.15.0) - factory_bot (6.4.6) - activesupport (>= 5.0.0) - factory_bot_rails (6.4.3) - factory_bot (~> 6.4) + factory_bot (6.5.1) + activesupport (>= 6.1.0) + factory_bot_rails (6.4.4) + factory_bot (~> 6.5) railties (>= 5.0.0) - faker (3.4.1) + faker (3.5.1) i18n (>= 1.8.11, < 2) - faraday (2.9.1) - faraday-net_http (>= 2.0, < 3.2) + faraday (2.12.2) + faraday-net_http (>= 2.0, < 3.5) + json + logger faraday-follow_redirects (0.3.0) faraday (>= 1, < 3) - faraday-net_http (3.1.0) - net-http - ffi (1.17.0) - fugit (1.11.0) + faraday-net_http (3.4.0) + net-http (>= 0.5.0) + ffi (1.17.1-x86_64-linux-gnu) + fugit (1.11.1) et-orbi (~> 1, >= 1.2.11) raabro (~> 1.4) globalid (1.2.1) activesupport (>= 6.1) - grape (2.0.0) - activesupport (>= 5) - builder + grape (2.3.0) + activesupport (>= 6) dry-types (>= 1.1) - mustermann-grape (~> 1.0.0) - rack (>= 1.3.0) - rack-accept + mustermann-grape (~> 1.1.0) + rack (>= 2) + zeitwerk grape-entity (1.0.1) activesupport (>= 3.0.0) multi_json (>= 1.3.2) - grape-swagger (2.1.0) + grape-swagger (2.1.2) grape (>= 1.7, < 3.0) rack-test (~> 2) - grape-swagger-rails (0.5.0) + grape-swagger-rails (0.6.0) + ostruct railties (>= 6.0.6.1) - hashdiff (1.1.0) + hashdiff (1.1.2) hashery (2.1.2) - hashie (5.1.0) - logger + hashie (5.0.0) hirb (0.7.3) http-accept (1.7.0) - http-cookie (1.0.6) + http-cookie (1.0.8) domain_name (~> 0.5) - i18n (1.14.5) + i18n (1.14.7) concurrent-ruby (~> 1.0) - icalendar (2.10.1) + icalendar (2.10.3) ice_cube (~> 0.16) - ice_cube (0.16.4) - io-console (0.7.2) - irb (1.13.1) + ostruct + ice_cube (0.17.0) + io-console (0.8.1) + irb (1.15.1) + pp (>= 0.6.0) rdoc (>= 4.0.0) reline (>= 0.4.2) jaro_winkler (1.6.0) - json (2.7.2) - json-jwt (1.16.6) + json (2.10.2) + json-jwt (1.16.7) activesupport (>= 4.2) aes_key_wrap base64 bindata faraday (~> 2.0) faraday-follow_redirects - jwt (3.1.2) + jwt (2.10.1) base64 - kramdown (2.4.0) - rexml + kramdown (2.5.1) + rexml (>= 3.3.9) kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) - language_server-protocol (3.17.0.3) + language_server-protocol (3.17.0.4) + lint_roller (1.1.0) listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) logger (1.7.0) - loofah (2.22.0) + loofah (2.24.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) - mail (2.9.0) - logger + mail (2.8.1) mini_mime (>= 0.1.1) net-imap net-pop net-smtp - marcel (1.1.0) - mime-types (3.5.2) + marcel (1.0.4) + mime-types (3.6.2) + logger mime-types-data (~> 3.2015) - mime-types-data (3.2024.0604) + mime-types-data (3.2025.0325) mini_mime (1.1.5) - mini_portile2 (2.8.9) - minitest (5.23.1) + minitest (5.25.5) minitest-around (0.5.0) minitest (~> 5.0) - minitest-rails (8.1.0) + minitest-rails (8.0.0) minitest (~> 5.20) - railties (>= 8.1.0, < 8.2.0) + railties (>= 8.0.0, < 8.1.0) moss_ruby (1.1.4) tcp_timeout (~> 0.1.1) - msgpack (1.7.2) + msgpack (1.8.0) multi_json (1.15.0) - multi_xml (0.8.1) - bigdecimal (>= 3.1, < 5) - mustermann (3.0.0) + multi_xml (0.7.1) + bigdecimal (~> 3.1) + mustermann (3.0.3) ruby2_keywords (~> 0.0.1) - mustermann-grape (1.0.2) + mustermann-grape (1.1.0) mustermann (>= 1.0.0) mysql2 (0.5.6) - net-http (0.4.1) + net-http (0.6.0) uri - net-imap (0.6.2) + net-imap (0.5.6) date net-protocol net-ldap (0.19.0) @@ -270,76 +277,77 @@ GEM net-protocol net-protocol (0.2.2) timeout - net-smtp (0.5.0) + net-smtp (0.5.1) net-protocol netrc (0.11.0) - nio4r (2.7.3) - nokogiri (1.16.5) - mini_portile2 (~> 2.8.2) + nio4r (2.7.4) + nokogiri (1.18.7-x86_64-linux-gnu) racc (~> 1.4) numerizer (0.1.1) - oauth2 (2.0.18) - faraday (>= 0.17.3, < 4.0) - jwt (>= 1.0, < 4.0) - logger (~> 1.2) + oauth2 (2.0.9) + faraday (>= 0.17.3, < 3.0) + jwt (>= 1.0, < 3.0) multi_xml (~> 0.5) rack (>= 1.2, < 4) - snaky_hash (~> 2.0, >= 2.0.3) - version_gem (~> 1.1, >= 1.1.9) + snaky_hash (~> 2.0) + version_gem (~> 1.1) observer (0.1.2) orm_adapter (0.5.0) - parallel (1.25.1) - parser (3.3.2.0) + ostruct (0.6.1) + parallel (1.26.3) + parser (3.3.7.4) ast (~> 2.4.1) racc - pdf-reader (2.12.0) - Ascii85 (~> 1.0) + pdf-reader (2.14.1) + Ascii85 (>= 1.0, < 3.0, != 2.0.0) afm (~> 0.2.1) hashery (~> 2.0) ruby-rc4 ttfunk - pkg-config (1.5.6) - prism (0.29.0) - psych (5.1.2) + pkg-config (1.6.0) + pp (0.6.2) + prettyprint + prettyprint (0.2.0) + prism (1.5.1) + psych (5.2.3) + date stringio - public_suffix (5.0.5) - puma (6.4.2) + public_suffix (6.0.1) + puma (6.6.0) nio4r (~> 2.0) raabro (1.4.0) - racc (1.8.0) - rack (3.0.11) - rack-accept (0.4.5) - rack (>= 0.4) + racc (1.8.1) + rack (3.1.12) rack-cors (2.0.2) rack (>= 2.0.0) - rack-session (2.0.0) + rack-session (2.1.0) + base64 (>= 0.1.0) rack (>= 3.0.0) - rack-test (2.1.0) + rack-test (2.2.0) rack (>= 1.3) - rackup (2.1.0) + rackup (2.2.1) rack (>= 3) - webrick (~> 1.8) - rails (8.1.2) - actioncable (= 8.1.2) - actionmailbox (= 8.1.2) - actionmailer (= 8.1.2) - actionpack (= 8.1.2) - actiontext (= 8.1.2) - actionview (= 8.1.2) - activejob (= 8.1.2) - activemodel (= 8.1.2) - activerecord (= 8.1.2) - activestorage (= 8.1.2) - activesupport (= 8.1.2) + rails (8.0.2) + actioncable (= 8.0.2) + actionmailbox (= 8.0.2) + actionmailer (= 8.0.2) + actionpack (= 8.0.2) + actiontext (= 8.0.2) + actionview (= 8.0.2) + activejob (= 8.0.2) + activemodel (= 8.0.2) + activerecord (= 8.0.2) + activestorage (= 8.0.2) + activesupport (= 8.0.2) bundler (>= 1.15.0) - railties (= 8.1.2) + railties (= 8.0.2) rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.6.0) + rails-html-sanitizer (1.6.2) loofah (~> 2.21) - nokogiri (~> 1.14) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) rails-latex (2.3.5) rails (>= 3.0.0, < 9) rails_best_practices (1.23.2) @@ -350,30 +358,31 @@ GEM json require_all (~> 3.0) ruby-progressbar - railties (8.1.2) - actionpack (= 8.1.2) - activesupport (= 8.1.2) + railties (8.0.2) + actionpack (= 8.0.2) + activesupport (= 8.0.2) irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) thor (~> 1.0, >= 1.2.2) - tsort (>= 0.2) zeitwerk (~> 2.6) rainbow (3.1.1) rake (13.2.1) rb-fsevent (0.11.2) rb-inotify (0.11.1) ffi (~> 1.0) - rbs (2.8.4) + rbs (3.9.2) + logger rbtree (0.4.6) - rdoc (6.7.0) + rdoc (6.14.0) + erb psych (>= 4.0.0) - redis (5.2.0) + redis (5.4.0) redis-client (>= 0.22.0) - redis-client (0.22.2) + redis-client (0.24.0) connection_pool - regexp_parser (2.9.2) - reline (0.5.8) + regexp_parser (2.10.0) + reline (0.6.0) io-console (~> 0.5) require_all (3.0.0) responders (3.1.1) @@ -384,171 +393,171 @@ GEM http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) - reverse_markdown (2.1.1) + reverse_markdown (3.0.0) nokogiri - rexml (3.2.9) - strscan - rmagick (6.0.1) + rexml (3.4.1) + rmagick (6.1.1) observer (~> 0.1) pkg-config (~> 1.4) - roo (2.7.1) + roo (2.10.1) nokogiri (~> 1) - rubyzip (~> 1.1, < 2.0.0) + rubyzip (>= 1.3.0, < 3.0.0) roo-xls (1.2.0) nokogiri roo (>= 2.0.0, < 3) spreadsheet (> 0.9.0) - rouge (4.2.1) - rubocop (1.64.1) + rouge (4.5.1) + rubocop (1.75.1) json (~> 2.3) - language_server-protocol (>= 3.17.0) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) parallel (~> 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8, < 3.0) - rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.31.1, < 2.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.43.0, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.31.3) - parser (>= 3.3.1.0) - rubocop-factory_bot (2.26.1) - rubocop (~> 1.61) - rubocop-faker (1.1.0) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.43.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + rubocop-factory_bot (2.27.1) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + rubocop-faker (1.3.0) faker (>= 2.12.0) - rubocop (>= 0.82.0) - rubocop-minitest (0.36.0) - rubocop (>= 1.61, < 2.0) - rubocop-ast (>= 1.31.1, < 2.0) - rubocop-performance (1.23.1) - rubocop (>= 1.48.1, < 2.0) - rubocop-ast (>= 1.31.1, < 2.0) - rubocop-rails (2.25.0) + lint_roller (~> 1.1) + rubocop (>= 1.72.1) + rubocop-minitest (0.37.1) + lint_roller (~> 1.1) + rubocop (>= 1.72.1, < 2.0) + rubocop-ast (>= 1.38.0, < 2.0) + rubocop-performance (1.24.0) + lint_roller (~> 1.1) + rubocop (>= 1.72.1, < 2.0) + rubocop-ast (>= 1.38.0, < 2.0) + rubocop-rails (2.30.3) activesupport (>= 4.2.0) + lint_roller (~> 1.1) rack (>= 1.1) - rubocop (>= 1.33.0, < 2.0) - rubocop-ast (>= 1.31.1, < 2.0) + rubocop (>= 1.72.1, < 2.0) + rubocop-ast (>= 1.38.0, < 2.0) ruby-filemagic (0.7.3) - ruby-lsp (0.17.2) + ruby-lsp (0.23.13) language_server-protocol (~> 3.17.0) - prism (>= 0.29.0, < 0.30) + prism (>= 1.2, < 2.0) + rbs (>= 3, < 4) sorbet-runtime (>= 0.5.10782) ruby-ole (1.2.13.1) ruby-progressbar (1.13.0) ruby-rc4 (0.1.5) - ruby-saml (1.13.0) - nokogiri (>= 1.10.5) + ruby-saml (1.18.0) + nokogiri (>= 1.13.10) rexml ruby2_keywords (0.0.5) - rubyzip (1.3.0) + rubyzip (2.4.1) securerandom (0.4.1) - set (1.1.0) - sexp_processor (4.17.1) - shellwords (0.2.0) - sidekiq (7.2.4) - concurrent-ruby (< 2) + set (1.1.1) + sexp_processor (4.17.3) + shellwords (0.2.2) + sidekiq (7.3.9) + base64 connection_pool (>= 2.3.0) + logger rack (>= 2.2.4) - redis-client (>= 0.19.0) - sidekiq-cron (1.12.0) - fugit (~> 1.8) + redis-client (>= 0.22.2) + sidekiq-cron (2.2.0) + cronex (>= 0.13.0) + fugit (~> 1.8, >= 1.11.1) globalid (>= 1.0.1) - sidekiq (>= 6) - sidekiq-status (4.0.0) - base64 + sidekiq (>= 6.5.0) + sidekiq-status (3.0.3) chronic_duration - logger - sidekiq (>= 7, < 9) - sidekiq-unique-jobs (8.0.13) + sidekiq (>= 6.0, < 8) + sidekiq-unique-jobs (8.0.10) concurrent-ruby (~> 1.0, >= 1.0.5) - sidekiq (>= 7.0.0, < 9.0.0) + sidekiq (>= 7.0.0, < 8.0.0) thor (>= 1.0, < 3.0) simplecov (0.22.0) docile (~> 1.1) simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) - simplecov-html (0.12.3) + simplecov-html (0.13.1) simplecov_json_formatter (0.1.4) - snaky_hash (2.0.3) - hashie (>= 0.1.0, < 6) - version_gem (>= 1.1.8, < 3) - solargraph (0.50.0) + snaky_hash (2.0.1) + hashie + version_gem (~> 1.1, >= 1.1.1) + solargraph (0.53.4) backport (~> 1.2) benchmark bundler (~> 2.0) diff-lcs (~> 1.4) - e2mmap - jaro_winkler (~> 1.5) + jaro_winkler (~> 1.6) kramdown (~> 2.3) kramdown-parser-gfm (~> 1.1) + logger (~> 1.6) + observer (~> 0.1) + ostruct (~> 0.6) parser (~> 3.0) - rbs (~> 2.0) - reverse_markdown (~> 2.0) + rbs (~> 3.3) + reverse_markdown (>= 2.0, < 4) rubocop (~> 1.38) thor (~> 1.0) tilt (~> 2.0) yard (~> 0.9, >= 0.9.24) - sorbet-runtime (0.5.11422) + yard-solargraph (~> 0.1) + sorbet-runtime (0.5.11966) sorted_set (1.0.3) rbtree set (~> 1.0) - spreadsheet (1.3.1) + spreadsheet (1.3.4) bigdecimal + logger ruby-ole sprockets (4.2.1) concurrent-ruby (~> 1.0) rack (>= 2.2.4, < 4) - sprockets-rails (3.5.1) + sprockets-rails (3.5.2) actionpack (>= 6.1) activesupport (>= 6.1) sprockets (>= 3.0.0) - stringio (3.1.0) - strscan (3.1.0) + stringio (3.1.6) tca_client (1.0.4) typhoeus (~> 1.0, >= 1.0.1) tcp_timeout (0.1.1) - thor (1.3.1) - tilt (2.3.0) - timeout (0.4.1) - tsort (0.2.0) + thor (1.3.2) + tilt (2.6.0) + timeout (0.4.3) ttfunk (1.8.0) bigdecimal (~> 3.1) typhoeus (1.4.1) ethon (>= 0.9.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - unicode-display_width (2.5.0) - uri (1.1.1) + unicode (0.4.4.5) + unicode-display_width (3.1.4) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) + uri (1.0.4) useragent (0.16.11) - version_gem (1.1.9) + version_gem (1.1.6) warden (1.2.9) rack (>= 2.0.9) - webmock (3.23.1) + webmock (3.25.1) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - webrick (1.8.1) - websocket-driver (0.8.0) + websocket-driver (0.7.7) base64 websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) - yard (0.9.36) - zeitwerk (2.6.15) + yard (0.9.37) + yard-solargraph (0.1.0) + yard (~> 0.9) + zeitwerk (2.7.2) PLATFORMS - aarch64-linux-gnu - aarch64-linux-musl - arm-linux - arm-linux-gnu - arm-linux-musl - arm64-darwin - x86-linux - x86-linux-gnu - x86-linux-musl - x86_64-darwin - x86_64-linux x86_64-linux-gnu - x86_64-linux-musl DEPENDENCIES better_errors @@ -617,4 +626,4 @@ RUBY VERSION ruby 3.4.7p58 BUNDLED WITH - 2.5.11 + 2.6.9 diff --git a/app/api/api_root.rb b/app/api/api_root.rb index f262edba27..46c6094799 100644 --- a/app/api/api_root.rb +++ b/app/api/api_root.rb @@ -68,7 +68,6 @@ class ApiRoot < Grape::API # the mount above is available in 9.x but has not been ported to `10.0.x` mount NotificationsApi mount ProjectsApi - mount ProjectsApi mount SettingsApi mount StaffGrantExtensionApi mount StudentsApi diff --git a/db/schema.rb b/db/schema.rb index 0f1e6f5f9b..91184060a8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.1].define(version: 2025_11_02_221253) do +ActiveRecord::Schema[8.0].define(version: 2025_12_12_010033) do create_table "activity_types", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.string "abbreviation", null: false t.datetime "created_at", null: false @@ -90,6 +90,15 @@ t.datetime "updated_at", null: false end + create_table "discussion_prompts", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + t.text "content", null: false + t.datetime "created_at", null: false + t.integer "priority", default: 0 + t.bigint "task_definition_id", null: false + t.datetime "updated_at", null: false + t.index ["task_definition_id"], name: "index_discussion_prompts_on_task_definition_id" + end + create_table "feedback_chips", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.text "chip_text" t.text "comment_text" @@ -199,6 +208,13 @@ t.index ["user_id"], name: "index_marking_sessions_on_user_id" end + create_table "notifications", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + t.datetime "created_at", null: false + t.string "message" + t.datetime "updated_at", null: false + t.integer "user_id" + end + create_table "overseer_assessments", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.datetime "created_at", null: false t.string "result_task_status" From 1b8c7241cfff389fb6bd418c66b07854a9a2a753 Mon Sep 17 00:00:00 2001 From: Steven Dalamaras Date: Sat, 17 Jan 2026 22:59:51 +1000 Subject: [PATCH 15/22] fix: remove duplicate api endpoints --- app/api/api_root.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/api/api_root.rb b/app/api/api_root.rb index 46c6094799..e039e0b6cd 100644 --- a/app/api/api_root.rb +++ b/app/api/api_root.rb @@ -63,7 +63,6 @@ class ApiRoot < Grape::API mount ScormExtensionCommentsApi mount GroupSetsApi mount LearningOutcomesApi - mount LearningOutcomesApi # mount LearningAlignmentApi # the mount above is available in 9.x but has not been ported to `10.0.x` mount NotificationsApi From c7f9b669101cceaf88a06a80da4d49173ef31678 Mon Sep 17 00:00:00 2001 From: Steven Dalamaras Date: Sun, 18 Jan 2026 01:20:12 +1000 Subject: [PATCH 16/22] chore: address rubocop nits --- .rubocop_todo.yml | 931 ++++----------------------- app/api/staff_grant_extension_api.rb | 21 +- app/mailers/notifications_mailer.rb | 4 +- app/services/extension_service.rb | 31 +- 4 files changed, 176 insertions(+), 811 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 6268621a0f..0fc47dee4c 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,29 +1,11 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2022-12-29 09:30:33 UTC using RuboCop version 1.41.1. +# on 2026-01-17 13:44:49 UTC using RuboCop version 1.75.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 2 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: outdent, indent -Layout/AccessModifierIndentation: - Exclude: - - 'app/models/unit.rb' - -# Offense count: 10 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: with_first_argument, with_fixed_indentation -Layout/ArgumentAlignment: - Exclude: - - 'app/api/activity_types_authenticated_api.rb' - - 'app/api/authentication_api.rb' - - 'app/api/campuses_authenticated_api.rb' - # Offense count: 3 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, IndentationWidth. @@ -32,90 +14,25 @@ Layout/ArrayAlignment: Exclude: - 'app/helpers/file_helper.rb' -# Offense count: 1 -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: IndentationWidth. -Layout/AssignmentIndentation: - Exclude: - - 'lib/tasks/init.rake' - -# Offense count: 2 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyleAlignWith. -# SupportedStylesAlignWith: either, start_of_block, start_of_line -Layout/BlockAlignment: - Exclude: - - 'app/models/project.rb' - - 'config/deakin.rb' - -# Offense count: 19 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, IndentOneStep, IndentationWidth. -# SupportedStyles: case, end -Layout/CaseIndentation: - Exclude: - - 'app/models/task_status.rb' - - 'app/models/webcal.rb' - - 'config/deakin.rb' - -# Offense count: 4 -# This cop supports safe autocorrection (--autocorrect). -Layout/ClosingParenthesisIndentation: - Exclude: - - 'app/api/units_api.rb' - - 'app/models/unit.rb' - - 'config/deakin.rb' - -# Offense count: 2 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowForAlignment. -Layout/CommentIndentation: - Exclude: - - 'config/initializers/inflections.rb' - -# Offense count: 76 +# Offense count: 8 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: leading, trailing Layout/DotPosition: Exclude: - - 'app/api/group_sets_api.rb' - - 'app/api/task_definitions_api.rb' - - 'app/api/tasks_api.rb' - - 'app/models/project.rb' - - 'app/models/task.rb' - - 'app/models/tutorial_enrolment.rb' - 'app/models/unit.rb' - - 'app/models/unit_role.rb' - - 'config/deakin.rb' - - 'lib/tasks/maintenance.rake' - -# Offense count: 3 -# This cop supports safe autocorrection (--autocorrect). -Layout/ElseAlignment: - Exclude: - - 'app/api/students_api.rb' - - 'app/models/task_definition.rb' -# Offense count: 67 +# Offense count: 28 # This cop supports safe autocorrection (--autocorrect). Layout/EmptyLineAfterGuardClause: Enabled: false -# Offense count: 2 -# This cop supports safe autocorrection (--autocorrect). -Layout/EmptyLineAfterMagicComment: - Exclude: - - 'app/api/discussion_comment_api.rb' - - 'app/models/comments/task_comment.rb' - # Offense count: 4 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EmptyLineBetweenMethodDefs, EmptyLineBetweenClassDefs, EmptyLineBetweenModuleDefs, AllowAdjacentOneLineDefs, NumberOfEmptyLines. +# Configuration parameters: EmptyLineBetweenMethodDefs, EmptyLineBetweenClassDefs, EmptyLineBetweenModuleDefs, DefLikeMacros, AllowAdjacentOneLineDefs, NumberOfEmptyLines. Layout/EmptyLineBetweenDefs: Exclude: - 'app/models/overseer_assessment.rb' - - 'app/models/role.rb' - 'app/models/unit.rb' # Offense count: 13 @@ -123,101 +40,35 @@ Layout/EmptyLineBetweenDefs: Layout/EmptyLines: Exclude: - 'app/api/submission/portfolio_evidence_api.rb' - - 'app/helpers/csv_helper.rb' - - 'app/models/auth_token.rb' - 'app/models/overseer_assessment.rb' - - 'app/models/role.rb' - 'app/models/unit.rb' - - 'config/environments/development.rb' - - 'lib/tasks/init.rake' -# Offense count: 9 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: around, only_before -Layout/EmptyLinesAroundAccessModifier: - Exclude: - - 'app/models/activity_type.rb' - - 'app/models/campus.rb' - - 'app/models/project.rb' - - 'app/models/task.rb' - - 'app/models/tutorial.rb' - - 'app/models/tutorial_enrolment.rb' - - 'app/models/tutorial_stream.rb' - - 'app/models/unit.rb' - -# Offense count: 6 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: empty_lines, no_empty_lines -Layout/EmptyLinesAroundBlockBody: - Exclude: - - 'app/api/submission/portfolio_evidence_api.rb' - - 'app/api/webcal_public_api.rb' - - 'app/models/tutorial_enrolment.rb' - - 'config/environments/production.rb' - - 'lib/tasks/init.rake' - - 'lib/tasks/send_status_emails.rake' - -# Offense count: 22 +# Offense count: 23 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines, beginning_only, ending_only Layout/EmptyLinesAroundClassBody: Enabled: false -# Offense count: 5 +# Offense count: 2 # This cop supports safe autocorrection (--autocorrect). Layout/EmptyLinesAroundExceptionHandlingKeywords: Exclude: - - 'app/helpers/file_helper.rb' - - 'app/models/overseer_assessment.rb' + - 'app/api/staff_grant_extension_api.rb' - 'app/models/task.rb' - - 'app/models/unit.rb' - - 'lib/assets/ontrack_receive_action.rb' -# Offense count: 2 +# Offense count: 1 # This cop supports safe autocorrection (--autocorrect). Layout/EmptyLinesAroundMethodBody: Exclude: - - 'app/models/portfolio_evidence.rb' - 'app/models/unit_role.rb' -# Offense count: 2 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines -Layout/EmptyLinesAroundModuleBody: - Exclude: - - 'app/api/admin/overseer_admin_api.rb' - - 'app/helpers/authentication_helpers.rb' - -# Offense count: 5 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyleAlignWith, Severity. -# SupportedStylesAlignWith: keyword, variable, start_of_line -Layout/EndAlignment: - Exclude: - - 'app/api/students_api.rb' - - 'app/channels/application_cable/channel.rb' - - 'app/models/task_definition.rb' - - 'config/application.rb' - -# Offense count: 21 +# Offense count: 1 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment. Layout/ExtraSpacing: Exclude: - - 'app/api/entities/project_entity.rb' - - 'app/api/entities/unit_entity.rb' - - 'app/api/projects_api.rb' - - 'app/api/tutorial_streams_api.rb' - - 'app/helpers/mime_check_helpers.rb' - - 'app/mailers/notifications_mailer.rb' - - 'app/models/project.rb' - - 'app/models/task.rb' - - 'config/initializers/swagger.rb' - - 'lib/helpers/database_populator.rb' + - 'app/api/staff_grant_extension_api.rb' # Offense count: 2 # This cop supports safe autocorrection (--autocorrect). @@ -227,251 +78,54 @@ Layout/FirstArrayElementIndentation: Exclude: - 'app/models/unit.rb' -# Offense count: 4 +# Offense count: 6 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, IndentationWidth. # SupportedStyles: special_inside_parentheses, consistent, align_braces Layout/FirstHashElementIndentation: Exclude: + - 'app/api/staff_grant_extension_api.rb' - 'app/models/unit.rb' - - 'config/no_institution_setting.rb' - - 'lib/helpers/database_populator.rb' - -# Offense count: 114 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. -# SupportedHashRocketStyles: key, separator, table -# SupportedColonStyles: key, separator, table -# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit -Layout/HashAlignment: - Exclude: - - 'app/api/authentication_api.rb' - - 'app/api/settings_api.rb' - - 'app/models/task.rb' - - 'app/models/teaching_period.rb' - - 'app/models/unit.rb' - - 'app/models/user.rb' - - 'config/deakin.rb' - - 'config/environments/production.rb' - - 'config/no_institution_setting.rb' - - 'lib/helpers/database_populator.rb' - - 'lib/helpers/find_or_create_students.rb' # Offense count: 9 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: normal, indented_internal_methods -Layout/IndentationConsistency: - Exclude: - - 'app/models/task.rb' - - 'app/models/task_definition.rb' - - 'app/models/tutorial_enrolment.rb' - - 'config/deakin.rb' - -# Offense count: 36 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: Width, AllowedPatterns, IgnoredPatterns. +# Configuration parameters: Width, AllowedPatterns. Layout/IndentationWidth: Exclude: - - 'app/api/students_api.rb' - - 'app/channels/application_cable/channel.rb' - - 'app/mailers/notifications_mailer.rb' - - 'app/models/task.rb' - - 'app/models/task_definition.rb' - - 'app/models/tutorial_enrolment.rb' - - 'app/models/unit.rb' - - 'app/models/user.rb' - - 'config/application.rb' - - 'config/deakin.rb' - - 'config/initializers/inflections.rb' - - 'config/no_institution_setting.rb' - - 'lib/tasks/send_status_emails.rake' - - 'lib/tasks/skip_prod.rake' - -# Offense count: 38 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowDoxygenCommentStyle, AllowGemfileRubyComment. -Layout/LeadingCommentSpace: - Exclude: - - 'app/api/entities/group_entity.rb' - - 'app/api/entities/tutorial_entity.rb' - - 'app/api/entities/tutorial_stream_entity.rb' - - 'app/api/task_definitions_api.rb' - - 'app/models/overseer_assessment.rb' - - 'app/models/project.rb' - - 'app/models/task.rb' - 'app/models/task_definition.rb' - - 'app/models/task_status.rb' - - 'app/models/teaching_period.rb' - - 'app/models/unit.rb' - - 'config/deakin.rb' - - 'lib/helpers/database_populator.rb' - -# Offense count: 5 -# This cop supports safe autocorrection (--autocorrect). -Layout/LeadingEmptyLines: - Exclude: - - 'app/api/entities/minimal/minimal_unit_entity.rb' - - 'app/api/entities/minimal/minimal_user_entity.rb' - - 'app/api/entities/user_entity.rb' - - 'app/api/entities/webcal_entity.rb' - - 'config/no_institution_setting.rb' - -# Offense count: 16 -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AutoCorrect, EnforcedStyle. -# SupportedStyles: leading, trailing -Layout/LineContinuationLeadingSpace: - Exclude: - - 'config/application.rb' - -# Offense count: 16 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AutoCorrect, EnforcedStyle. -# SupportedStyles: space, no_space -Layout/LineContinuationSpacing: - Exclude: - - 'config/application.rb' - -# Offense count: 1 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: aligned, indented -Layout/LineEndStringConcatenationIndentation: - Exclude: - - 'config/application.rb' - -# Offense count: 4 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: symmetrical, new_line, same_line -Layout/MultilineMethodCallBraceLayout: - Exclude: - - 'app/api/units_api.rb' - 'app/models/unit.rb' - - 'app/models/unit_role.rb' -# Offense count: 82 +# Offense count: 45 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, IndentationWidth. # SupportedStyles: aligned, indented, indented_relative_to_receiver Layout/MultilineMethodCallIndentation: Exclude: - - 'app/api/activity_types_authenticated_api.rb' - - 'app/api/admin/overseer_admin_api.rb' - - 'app/api/campuses_authenticated_api.rb' - - 'app/api/group_sets_api.rb' - - 'app/api/learning_outcomes_api.rb' - - 'app/api/task_definitions_api.rb' - - 'app/api/tasks_api.rb' - - 'app/api/teaching_periods_authenticated_api.rb' - - 'app/api/unit_roles_api.rb' - - 'app/api/webcal_api.rb' - - 'app/models/project.rb' - - 'app/models/tutorial_enrolment.rb' - 'app/models/unit.rb' - 'app/models/unit_role.rb' - - 'config/deakin.rb' - -# Offense count: 11 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: aligned, indented -Layout/MultilineOperationIndentation: - Exclude: - - 'app/api/authentication_api.rb' - - 'app/models/unit.rb' - - 'config/application.rb' - -# Offense count: 4 -# This cop supports safe autocorrection (--autocorrect). -Layout/SpaceAfterColon: - Exclude: - - 'app/api/extension_comments_api.rb' - - 'config/deakin.rb' -# Offense count: 46 +# Offense count: 35 # This cop supports safe autocorrection (--autocorrect). Layout/SpaceAfterComma: Exclude: - - 'app/api/api_root.rb' - - 'app/api/tutorial_streams_api.rb' - - 'app/api/units_api.rb' - - 'app/helpers/application_helper.rb' - 'app/helpers/file_helper.rb' - - 'app/models/activity_type.rb' - - 'app/models/campus.rb' - - 'app/models/task.rb' - - 'app/models/teaching_period.rb' - - 'config/deakin.rb' - - 'lib/helpers/database_populator.rb' -# Offense count: 7 -# This cop supports safe autocorrection (--autocorrect). -Layout/SpaceAfterMethodName: - Exclude: - - 'app/models/project.rb' - - 'app/models/user.rb' - - 'config/deakin.rb' - - 'config/no_institution_setting.rb' - -# Offense count: 14 +# Offense count: 3 # This cop supports safe autocorrection (--autocorrect). Layout/SpaceAfterNot: Exclude: - - 'app/api/group_sets_api.rb' - - 'app/api/tutorial_enrolments_api.rb' - - 'app/models/comments/extension_comment.rb' - - 'app/models/comments/task_comment.rb' - - 'app/models/group.rb' - - 'app/models/project.rb' - - 'app/models/task.rb' - - 'app/models/tutorial.rb' - - 'app/models/tutorial_enrolment.rb' - 'app/models/unit.rb' - - 'config/deakin.rb' - -# Offense count: 22 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyleInsidePipes. -# SupportedStylesInsidePipes: space, no_space -Layout/SpaceAroundBlockParameters: - Exclude: - - 'app/api/entities/project_entity.rb' - - 'app/models/task.rb' - - 'app/models/webcal.rb' - - 'lib/helpers/database_populator.rb' - - 'lib/tasks/init.rake' - -# Offense count: 6 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: space, no_space -Layout/SpaceAroundEqualsInParameterDefault: - Exclude: - - 'app/models/teaching_period.rb' - - 'app/models/unit.rb' - - 'lib/helpers/faker_randomiser.rb' -# Offense count: 1 -# This cop supports safe autocorrection (--autocorrect). -Layout/SpaceAroundMethodCallOperator: - Exclude: - - 'app/models/unit.rb' - -# Offense count: 7 +# Offense count: 2 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowForAlignment, EnforcedStyleForExponentOperator. +# Configuration parameters: AllowForAlignment, EnforcedStyleForExponentOperator, EnforcedStyleForRationalLiterals. # SupportedStylesForExponentOperator: space, no_space +# SupportedStylesForRationalLiterals: space, no_space Layout/SpaceAroundOperators: Exclude: - - 'app/api/submission/portfolio_evidence_api.rb' - - 'app/models/task.rb' - - 'config/deakin.rb' - - 'config/initializers/swagger.rb' - 'lib/helpers/database_populator.rb' -# Offense count: 12 +# Offense count: 2 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. # SupportedStyles: space, no_space @@ -479,170 +133,39 @@ Layout/SpaceAroundOperators: Layout/SpaceBeforeBlockBraces: Exclude: - 'app/helpers/file_helper.rb' - - 'app/models/activity_type.rb' - - 'app/models/campus.rb' - - 'app/models/project.rb' - 'app/models/task_definition.rb' - - 'app/models/teaching_period.rb' - - 'app/models/unit.rb' - - 'app/models/webcal.rb' -# Offense count: 1 -# This cop supports safe autocorrection (--autocorrect). -Layout/SpaceBeforeBrackets: - Exclude: - - 'app/models/unit.rb' - -# Offense count: 118 +# Offense count: 2 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBrackets. # SupportedStyles: space, no_space, compact # SupportedStylesForEmptyBrackets: space, no_space Layout/SpaceInsideArrayLiteralBrackets: Exclude: - - 'app/api/projects_api.rb' - - 'app/api/units_api.rb' - - 'app/api/users_api.rb' - 'app/helpers/file_helper.rb' - - 'app/models/group.rb' - - 'app/models/group_set.rb' - - 'app/models/project.rb' - - 'app/models/task.rb' - - 'app/models/task_definition.rb' - - 'app/models/unit.rb' - - 'app/models/unit_role.rb' - - 'app/models/user.rb' - - 'config/deakin.rb' - - 'config/initializers/devise.rb' - - 'lib/helpers/database_populator.rb' -# Offense count: 34 +# Offense count: 5 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. # SupportedStyles: space, no_space # SupportedStylesForEmptyBraces: space, no_space Layout/SpaceInsideBlockBraces: Exclude: - - 'app/api/entities/project_entity.rb' - - 'app/helpers/file_helper.rb' - - 'app/mailers/notifications_mailer.rb' - - 'app/models/activity_type.rb' - - 'app/models/campus.rb' - - 'app/models/portfolio_evidence.rb' - - 'app/models/project.rb' - - 'app/models/task.rb' - - 'app/models/task_definition.rb' - - 'app/models/teaching_period.rb' - - 'app/models/unit.rb' - - 'app/models/webcal.rb' - -# Offense count: 69 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. -# SupportedStyles: space, no_space, compact -# SupportedStylesForEmptyBraces: space, no_space -Layout/SpaceInsideHashLiteralBraces: - Exclude: - - 'app/api/api_root.rb' - - 'app/api/extension_comments_api.rb' - - 'app/api/group_sets_api.rb' - - 'app/api/projects_api.rb' - - 'app/api/task_comments_api.rb' - - 'app/api/teaching_periods_authenticated_api.rb' - - 'app/api/units_api.rb' - - 'app/models/overseer_assessment.rb' - - 'app/models/project.rb' - - 'app/models/tutorial_stream.rb' - - 'app/models/unit.rb' - - 'app/models/user.rb' - - 'config/deakin.rb' - - 'lib/helpers/database_populator.rb' - -# Offense count: 27 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: space, compact, no_space -Layout/SpaceInsideParens: - Exclude: - - 'app/api/api_root.rb' - - 'app/api/units_api.rb' - 'app/helpers/file_helper.rb' - - 'app/models/project.rb' - - 'app/models/task_definition.rb' - - 'app/models/tutorial_enrolment.rb' - - 'app/models/unit.rb' - - 'config/deakin.rb' - - 'lib/helpers/database_populator.rb' - - 'lib/tasks/generate_pdfs.rake' - -# Offense count: 1 -# This cop supports safe autocorrection (--autocorrect). -Layout/SpaceInsideRangeLiteral: - Exclude: - - 'lib/helpers/database_populator.rb' - -# Offense count: 1 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBrackets. -# SupportedStyles: space, no_space -# SupportedStylesForEmptyBrackets: space, no_space -Layout/SpaceInsideReferenceBrackets: - Exclude: - - 'app/models/unit.rb' - -# Offense count: 2 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: space, no_space -Layout/SpaceInsideStringInterpolation: - Exclude: - - 'app/helpers/file_helper.rb' - - 'app/models/tutorial_enrolment.rb' - -# Offense count: 4 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: final_newline, final_blank_line -Layout/TrailingEmptyLines: - Exclude: - - 'app/channels/application_cable/channel.rb' - - 'config/initializers/swagger.rb' - - 'lib/helpers/faker_randomiser.rb' - - 'lib/tasks/send_status_emails.rake' - -# Offense count: 10 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowInHeredoc. -Layout/TrailingWhitespace: - Exclude: - - 'app/mailers/convenor_contact_mailer.rb' - - 'app/mailers/portfolio_evidence_mailer.rb' - - 'app/models/portfolio_evidence.rb' - - 'config/deakin.rb' - - 'lib/tasks/send_status_emails.rake' + - 'app/models/task_definition.rb' # Offense count: 1 -# Configuration parameters: AllowedMethods, AllowedPatterns, IgnoredMethods. +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowedMethods, AllowedPatterns. Lint/AmbiguousBlockAssociation: Exclude: - 'app/models/task.rb' -# Offense count: 3 +# Offense count: 1 # This cop supports safe autocorrection (--autocorrect). Lint/AmbiguousOperator: Exclude: - - 'app/helpers/file_helper.rb' - 'app/models/portfolio_evidence.rb' - - 'app/models/task.rb' - -# Offense count: 14 -# This cop supports safe autocorrection (--autocorrect). -Lint/AmbiguousOperatorPrecedence: - Exclude: - - 'app/models/project.rb' - - 'app/models/task.rb' - - 'app/models/unit.rb' - - 'lib/tasks/populate.rake' # Offense count: 7 # This cop supports safe autocorrection (--autocorrect). @@ -655,28 +178,12 @@ Lint/AmbiguousRegexpLiteral: - 'app/helpers/file_helper.rb' # Offense count: 1 -# This cop supports safe autocorrection (--autocorrect). +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: AllowSafeAssignment. Lint/AssignmentInCondition: Exclude: - 'app/channels/application_cable/connection.rb' -# Offense count: 2 -# This cop supports safe autocorrection (--autocorrect). -Lint/DeprecatedClassMethods: - Exclude: - - 'app/models/task.rb' - -# Offense count: 24 -# Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches. -Lint/DuplicateBranch: - Exclude: - - 'app/api/api_root.rb' - - 'app/helpers/file_helper.rb' - - 'app/models/project.rb' - - 'app/models/task_status.rb' - - 'lib/tasks/populate.rake' - # Offense count: 2 # This cop supports safe autocorrection (--autocorrect). Lint/ElseLayout: @@ -684,21 +191,15 @@ Lint/ElseLayout: - 'app/models/project.rb' - 'app/models/task.rb' -# Offense count: 1 -# Configuration parameters: AllowComments, AllowEmptyLambdas. -Lint/EmptyBlock: - Exclude: - - 'app/helpers/file_helper.rb' - # Offense count: 1 Lint/FloatComparison: Exclude: - 'app/models/project.rb' -# Offense count: 8 +# Offense count: 12 +# This cop supports safe autocorrection (--autocorrect). Lint/ImplicitStringConcatenation: Exclude: - - 'app/api/learning_alignment_api.rb' - 'app/api/learning_outcomes_api.rb' # Offense count: 2 @@ -709,6 +210,7 @@ Lint/Loop: - 'config/no_institution_setting.rb' # Offense count: 4 +# Configuration parameters: AllowedParentClasses. Lint/MissingSuper: Exclude: - 'app/controllers/lecture_resource_downloads_controller.rb' @@ -716,15 +218,6 @@ Lint/MissingSuper: - 'app/controllers/task_downloads_controller.rb' - 'app/controllers/task_submission_pdfs_controller.rb' -# Offense count: 18 -# This cop supports unsafe autocorrection (--autocorrect-all). -Lint/NonAtomicFileOperation: - Exclude: - - 'app/helpers/file_helper.rb' - - 'app/models/comments/task_comment.rb' - - 'app/models/project.rb' - - 'app/models/task.rb' - # Offense count: 1 Lint/NonLocalExitFromIterator: Exclude: @@ -736,18 +229,13 @@ Lint/ParenthesesAsGroupedExpression: Exclude: - 'app/models/unit.rb' -# Offense count: 5 +# Offense count: 4 # This cop supports safe autocorrection (--autocorrect). Lint/RedundantStringCoercion: Exclude: - 'app/helpers/file_helper.rb' -# Offense count: 2 -Lint/RequireParentheses: - Exclude: - - 'config/application.rb' - -# Offense count: 15 +# Offense count: 16 Lint/RescueException: Exclude: - 'app/models/portfolio_evidence.rb' @@ -756,34 +244,16 @@ Lint/RescueException: - 'config/deakin.rb' - 'lib/tasks/generate_pdfs.rake' -# Offense count: 1 -# This cop supports safe autocorrection (--autocorrect). -Lint/ScriptPermission: - Exclude: - - 'Rakefile' - -# Offense count: 8 +# Offense count: 4 Lint/ShadowingOuterLocalVariable: Exclude: - - 'app/models/learning_outcome.rb' - - 'app/models/teaching_period.rb' - 'app/models/unit.rb' -# Offense count: 4 +# Offense count: 1 # Configuration parameters: AllowComments, AllowNil. Lint/SuppressedException: Exclude: - - 'app/models/project.rb' - 'app/models/task.rb' - - 'app/models/task_definition.rb' - -# Offense count: 5 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: strict, consistent -Lint/SymbolConversion: - Exclude: - - 'lib/tasks/init.rake' # Offense count: 2 # Configuration parameters: AllowKeywordBlockArguments. @@ -791,9 +261,9 @@ Lint/UnderscorePrefixedVariableName: Exclude: - 'app/models/comments/discussion_comment.rb' -# Offense count: 33 +# Offense count: 45 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. +# Configuration parameters: AutoCorrect, IgnoreEmptyBlocks, AllowUnusedKeywordArguments. Lint/UnusedBlockArgument: Exclude: - 'app/api/entities/comment_entity.rb' @@ -811,9 +281,10 @@ Lint/UnusedBlockArgument: - 'lib/helpers/database_populator.rb' - 'lib/tasks/checks.rake' -# Offense count: 7 +# Offense count: 9 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods. +# Configuration parameters: AutoCorrect, AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods, NotImplementedExceptions. +# NotImplementedExceptions: NotImplementedError Lint/UnusedMethodArgument: Exclude: - 'app/models/project.rb' @@ -821,51 +292,53 @@ Lint/UnusedMethodArgument: - 'config/deakin.rb' - 'config/no_institution_setting.rb' -# Offense count: 55 +# Offense count: 61 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AutoCorrect. Lint/UselessAssignment: Enabled: false -# Offense count: 145 -# Configuration parameters: AllowedMethods, AllowedPatterns, IgnoredMethods, CountRepeatedAttributes. +# Offense count: 225 +# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. Metrics/AbcSize: Max: 153 -# Offense count: 65 -# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, AllowedMethods, AllowedPatterns, IgnoredMethods. +# Offense count: 96 +# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. # AllowedMethods: refine Metrics/BlockLength: - Max: 200 + Max: 157 -# Offense count: 12 -# Configuration parameters: CountBlocks. +# Offense count: 10 +# Configuration parameters: CountBlocks, CountModifierForms. Metrics/BlockNesting: Max: 5 -# Offense count: 65 -# Configuration parameters: AllowedMethods, AllowedPatterns, IgnoredMethods. +# Offense count: 96 +# Configuration parameters: AllowedMethods, AllowedPatterns. Metrics/CyclomaticComplexity: - Max: 39 + Max: 36 -# Offense count: 173 -# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, AllowedMethods, AllowedPatterns, IgnoredMethods. +# Offense count: 273 +# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. Metrics/MethodLength: - Max: 140 + Max: 115 -# Offense count: 1 +# Offense count: 4 # Configuration parameters: CountComments, CountAsOne. Metrics/ModuleLength: - Enabled: false + Max: 580 -# Offense count: 4 +# Offense count: 7 # Configuration parameters: CountKeywordArgs. Metrics/ParameterLists: MaxOptionalParameters: 4 Max: 8 -# Offense count: 62 -# Configuration parameters: AllowedMethods, AllowedPatterns, IgnoredMethods. +# Offense count: 84 +# Configuration parameters: AllowedMethods, AllowedPatterns. Metrics/PerceivedComplexity: - Max: 50 + Max: 39 # Offense count: 2 Naming/AccessorMethodName: @@ -874,13 +347,14 @@ Naming/AccessorMethodName: - 'app/models/user.rb' # Offense count: 1 -# Configuration parameters: EnforcedStyle, AllowedPatterns, IgnoredPatterns. +# Configuration parameters: EnforcedStyle, AllowedPatterns, ForbiddenIdentifiers, ForbiddenPatterns. # SupportedStyles: snake_case, camelCase +# ForbiddenIdentifiers: __id__, __send__ Naming/MethodName: Exclude: - 'app/models/comments/discussion_comment.rb' -# Offense count: 16 +# Offense count: 13 # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. # AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to Naming/MethodParameterName: @@ -892,15 +366,14 @@ Naming/MethodParameterName: - 'app/models/task.rb' - 'app/models/unit.rb' -# Offense count: 36 -# Configuration parameters: NamePrefix, ForbiddenPrefixes, AllowedMethods, MethodDefinitionMacros. -# NamePrefix: is_, has_, have_ -# ForbiddenPrefixes: is_, has_, have_ +# Offense count: 37 +# Configuration parameters: NamePrefix, ForbiddenPrefixes, AllowedMethods, MethodDefinitionMacros, UseSorbetSigs. +# NamePrefix: is_, has_, have_, does_ +# ForbiddenPrefixes: is_, has_, have_, does_ # AllowedMethods: is_a? # MethodDefinitionMacros: define_method, define_singleton_method Naming/PredicateName: Exclude: - - 'spec/**/*' - 'app/api/entities/unit_entity.rb' - 'app/models/group.rb' - 'app/models/overseer_assessment.rb' @@ -914,7 +387,7 @@ Naming/PredicateName: - 'lib/tasks/generate_pdfs.rake' # Offense count: 27 -# Configuration parameters: EnforcedStyle, AllowedIdentifiers, AllowedPatterns. +# Configuration parameters: EnforcedStyle, AllowedIdentifiers, AllowedPatterns, ForbiddenIdentifiers, ForbiddenPatterns. # SupportedStyles: snake_case, camelCase Naming/VariableName: Exclude: @@ -922,28 +395,12 @@ Naming/VariableName: - 'app/models/comments/task_comment.rb' - 'config/deakin.rb' -# Offense count: 2 -# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns. -# SupportedStyles: snake_case, normalcase, non_integer -# AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339 -Naming/VariableNumber: - Exclude: - - 'app/models/unit.rb' - -# Offense count: 3 -# This cop supports unsafe autocorrection (--autocorrect-all). -Security/IoMethods: - Exclude: - - 'app/api/discussion_comment_api.rb' - - 'app/api/task_comments_api.rb' - -# Offense count: 13 +# Offense count: 5 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: separated, grouped Style/AccessorGrouping: Exclude: - - 'app/models/project.rb' - 'app/models/task.rb' # Offense count: 2 @@ -970,9 +427,9 @@ Style/AndOr: - 'app/models/tutorial_enrolment.rb' - 'app/models/tutorial_stream.rb' -# Offense count: 7 +# Offense count: 6 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, AllowedMethods, AllowedPatterns, IgnoredMethods, AllowBracesOnProceduralOneLiners, BracesRequiredMethods. +# Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, AllowedMethods, AllowedPatterns, AllowBracesOnProceduralOneLiners, BracesRequiredMethods. # SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces # ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object # FunctionalMethods: let, let!, subject, watch @@ -986,28 +443,26 @@ Style/BlockDelimiters: - 'config/deakin.rb' - 'lib/helpers/database_populator.rb' -# Offense count: 6 +# Offense count: 1 # This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: MinBranchesCount. Style/CaseLikeIf: Exclude: - 'app/helpers/csv_helper.rb' - - 'app/helpers/file_helper.rb' - - 'app/models/comments/task_comment.rb' - - 'app/models/user.rb' -# Offense count: 3 +# Offense count: 1 # This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: EnforcedStyle. +# Configuration parameters: EnforcedStyle, EnforcedStyleForClasses, EnforcedStyleForModules. # SupportedStyles: nested, compact +# SupportedStylesForClasses: , nested, compact +# SupportedStylesForModules: , nested, compact Style/ClassAndModuleChildren: Exclude: - - 'app/api/entities/minimal/minimal_unit_entity.rb' - - 'app/api/entities/minimal/minimal_user_entity.rb' - 'app/api/submission/generate_helpers.rb' -# Offense count: 12 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowedMethods, AllowedPatterns, IgnoredMethods. +# Offense count: 4 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: AllowedMethods, AllowedPatterns. # AllowedMethods: ==, equal?, eql? Style/ClassEqualityComparison: Exclude: @@ -1022,6 +477,7 @@ Style/ColonMethodCall: - 'app/helpers/timeout_helper.rb' # Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). Style/CombinableLoops: Exclude: - 'app/models/unit.rb' @@ -1034,18 +490,17 @@ Style/CommentAnnotation: Exclude: - 'app/api/task_definitions_api.rb' -# Offense count: 24 +# Offense count: 20 # This cop supports unsafe autocorrection (--autocorrect-all). Style/CommentedKeyword: Exclude: - 'app/api/projects_api.rb' - - 'app/api/submission/batch_task_api.rb' - 'app/api/submission/portfolio_api.rb' - 'app/api/submission/portfolio_evidence_api.rb' - 'app/models/unit.rb' - 'config/deakin.rb' -# Offense count: 13 +# Offense count: 12 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions. # SupportedStyles: assign_to_condition, assign_inside_condition @@ -1053,30 +508,28 @@ Style/ConditionalAssignment: Exclude: - 'app/api/submission/portfolio_evidence_api.rb' - 'app/models/comments/extension_comment.rb' - - 'app/models/learning_outcome_task_link.rb' - 'app/models/task.rb' - 'app/models/unit.rb' - 'lib/helpers/database_populator.rb' - 'lib/tasks/maintenance.rake' -# Offense count: 11 +# Offense count: 9 # This cop supports safe autocorrection (--autocorrect). Style/DefWithParentheses: Exclude: - 'app/helpers/file_helper.rb' - - 'app/models/overseer_assessment.rb' - 'app/models/task_definition.rb' - 'app/models/user.rb' - 'config/deakin.rb' -# Offense count: 120 +# Offense count: 197 # Configuration parameters: AllowedConstants. Style/Documentation: Enabled: false # Offense count: 3 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, AllowComments. +# Configuration parameters: AutoCorrect, EnforcedStyle, AllowComments. # SupportedStyles: empty, nil, both Style/EmptyElse: Exclude: @@ -1105,30 +558,7 @@ Style/ExplicitBlockArgument: Exclude: - 'app/helpers/timeout_helper.rb' -# Offense count: 31 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowedVars. -Style/FetchEnvVar: - Exclude: - - 'config/application.rb' - - 'config/deakin.rb' - - 'config/environments/production.rb' - -# Offense count: 3 -# This cop supports safe autocorrection (--autocorrect). -Style/FileRead: - Exclude: - - 'app/helpers/file_helper.rb' - - 'app/models/unit.rb' - - 'lib/tasks/checks.rake' - -# Offense count: 1 -# This cop supports safe autocorrection (--autocorrect). -Style/FileWrite: - Exclude: - - 'lib/tasks/generate_pdfs.rake' - -# Offense count: 19 +# Offense count: 18 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: each, for @@ -1153,27 +583,28 @@ Style/FormatString: Exclude: - 'app/models/project.rb' -# Offense count: 8 +# Offense count: 7 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: MaxUnannotatedPlaceholdersAllowed, AllowedMethods, AllowedPatterns, IgnoredMethods. +# Configuration parameters: MaxUnannotatedPlaceholdersAllowed, Mode, AllowedMethods, AllowedPatterns. # SupportedStyles: annotated, template, unannotated +# AllowedMethods: redirect Style/FormatStringToken: EnforcedStyle: template -# Offense count: 152 +# Offense count: 251 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: always, always_true, never Style/FrozenStringLiteralComment: Enabled: false -# Offense count: 2 +# Offense count: 1 # This cop supports unsafe autocorrection (--autocorrect-all). Style/GlobalStdStream: Exclude: - 'lib/tasks/skip_prod.rake' -# Offense count: 46 +# Offense count: 70 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: MinBodyLength, AllowConsecutiveConditionals. Style/GuardClause: @@ -1183,7 +614,7 @@ Style/GuardClause: # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, EnforcedShorthandSyntax, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols. # SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys -# SupportedShorthandSyntax: always, never, either, consistent +# SupportedShorthandSyntax: always, never, either, consistent, either_consistent Style/HashSyntax: Exclude: - 'app/models/campus.rb' @@ -1197,30 +628,20 @@ Style/HashSyntax: Style/IfInsideElse: Exclude: - 'app/api/units_api.rb' - - 'app/models/learning_outcome_task_link.rb' - 'app/models/task.rb' -# Offense count: 316 +# Offense count: 500 # This cop supports safe autocorrection (--autocorrect). Style/IfUnlessModifier: Enabled: false -# Offense count: 2 -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AllowedMethods. -# AllowedMethods: nonzero? -Style/IfWithBooleanLiteralBranches: - Exclude: - - 'config/application.rb' - -# Offense count: 5 +# Offense count: 3 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: InverseMethods, InverseBlocks. Style/InverseMethods: Exclude: - 'app/api/entities/task_entity.rb' - 'app/api/submission/generate_helpers.rb' - - 'app/helpers/file_helper.rb' - 'app/models/unit.rb' # Offense count: 15 @@ -1229,29 +650,26 @@ Style/InverseMethods: # SupportedStyles: line_count_dependent, lambda, literal Style/Lambda: Exclude: - - 'app/api/entities/project_entity.rb' - 'app/api/entities/unit_entity.rb' - 'app/models/project.rb' - 'app/models/unit.rb' - 'config/deakin.rb' -# Offense count: 25 +# Offense count: 27 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowedMethods, AllowedPatterns, IgnoredMethods. +# Configuration parameters: AllowedMethods, AllowedPatterns. Style/MethodCallWithoutArgsParentheses: Exclude: - 'app/api/task_definitions_api.rb' - 'app/models/comments/discussion_comment.rb' - - 'app/models/overseer_assessment.rb' - 'app/models/project.rb' - - 'app/models/task.rb' - 'app/models/task_definition.rb' - 'app/models/unit.rb' - 'config/deakin.rb' - 'lib/helpers/database_populator.rb' - 'lib/tasks/checks.rake' -# Offense count: 12 +# Offense count: 10 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: require_parentheses, require_no_parentheses, require_no_parentheses_except_multiline @@ -1262,7 +680,6 @@ Style/MethodDefParentheses: - 'app/models/group_submission.rb' - 'app/models/task_definition.rb' - 'config/deakin.rb' - - 'lib/helpers/database_populator.rb' # Offense count: 1 Style/MultilineBlockChain: @@ -1270,14 +687,12 @@ Style/MultilineBlockChain: - 'app/models/project.rb' # Offense count: 1 -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: literals, strict -Style/MutableConstant: +# This cop supports safe autocorrection (--autocorrect). +Style/MultilineIfModifier: Exclude: - - 'app/helpers/grade_helper.rb' + - 'app/api/staff_grant_extension_api.rb' -# Offense count: 6 +# Offense count: 4 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: both, prefix, postfix @@ -1285,16 +700,7 @@ Style/NegatedIf: Exclude: - 'app/api/task_definitions_api.rb' - 'app/api/units_api.rb' - - 'app/helpers/file_helper.rb' - 'app/models/task.rb' - - 'app/models/task_definition.rb' - -# Offense count: 3 -# This cop supports safe autocorrection (--autocorrect). -Style/NegatedIfElseCondition: - Exclude: - - 'app/models/project.rb' - - 'app/models/unit.rb' # Offense count: 1 # This cop supports safe autocorrection (--autocorrect). @@ -1302,14 +708,13 @@ Style/NestedTernaryOperator: Exclude: - 'app/models/project.rb' -# Offense count: 7 +# Offense count: 6 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, MinBodyLength. +# Configuration parameters: EnforcedStyle, MinBodyLength, AllowConsecutiveConditionals. # SupportedStyles: skip_modifier_ifs, always Style/Next: Exclude: - 'app/models/teaching_period.rb' - - 'app/models/unit.rb' - 'config/deakin.rb' # Offense count: 2 @@ -1335,28 +740,27 @@ Style/Not: Style/NumericLiterals: MinDigits: 6 -# Offense count: 90 +# Offense count: 97 # This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: EnforcedStyle, AllowedMethods, AllowedPatterns, IgnoredMethods. +# Configuration parameters: EnforcedStyle, AllowedMethods, AllowedPatterns. # SupportedStyles: predicate, comparison Style/NumericPredicate: Enabled: false -# Offense count: 23 +# Offense count: 27 # Configuration parameters: AllowedMethods. # AllowedMethods: respond_to_missing? Style/OptionalBooleanParameter: Exclude: - 'app/helpers/file_helper.rb' + - 'app/mailers/notifications_mailer.rb' - 'app/models/auth_token.rb' - 'app/models/comments/extension_comment.rb' - 'app/models/portfolio_evidence.rb' - - 'app/models/project.rb' - 'app/models/task.rb' - 'app/models/task_definition.rb' - - 'app/models/teaching_period.rb' - 'app/models/unit.rb' - - 'app/models/user.rb' + - 'app/services/extension_service.rb' - 'lib/helpers/faker_randomiser.rb' # Offense count: 2 @@ -1365,16 +769,14 @@ Style/OrAssignment: Exclude: - 'app/models/unit.rb' -# Offense count: 4 +# Offense count: 1 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowSafeAssignment, AllowInMultilineConditions. Style/ParenthesesAroundCondition: Exclude: - - 'app/models/group.rb' - - 'app/models/task_definition.rb' - 'config/application.rb' -# Offense count: 29 +# Offense count: 34 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: PreferredDelimiters. Style/PercentLiteralDelimiters: @@ -1382,7 +784,6 @@ Style/PercentLiteralDelimiters: - 'app/api/task_comments_api.rb' - 'app/helpers/file_helper.rb' - 'app/models/learning_outcome.rb' - - 'app/models/learning_outcome_task_link.rb' - 'app/models/task.rb' - 'app/models/task_definition.rb' - 'app/models/unit.rb' @@ -1390,36 +791,20 @@ Style/PercentLiteralDelimiters: - 'app/models/webcal.rb' - 'config/application.rb' -# Offense count: 12 +# Offense count: 16 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: . # SupportedStyles: same_as_string_literals, single_quotes, double_quotes Style/QuotedSymbols: EnforcedStyle: double_quotes -# Offense count: 5 -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: Methods. -Style/RedundantArgument: - Exclude: - - 'app/helpers/csv_helper.rb' - - 'app/models/unit.rb' - - 'app/models/user.rb' - -# Offense count: 5 +# Offense count: 3 # This cop supports safe autocorrection (--autocorrect). Style/RedundantBegin: Exclude: - 'app/helpers/timeout_helper.rb' - - 'app/models/task_definition.rb' - 'app/models/unit.rb' -# Offense count: 1 -# This cop supports safe autocorrection (--autocorrect). -Style/RedundantConstantBase: - Exclude: - - 'config.ru' - # Offense count: 2 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: SafeForConstants. @@ -1427,13 +812,7 @@ Style/RedundantFetchBlock: Exclude: - 'config/puma.rb' -# Offense count: 1 -# This cop supports safe autocorrection (--autocorrect). -Style/RedundantFileExtensionInRequire: - Exclude: - - 'lib/tasks/register_q_assessment_results_subscriber.rake' - -# Offense count: 11 +# Offense count: 16 # This cop supports unsafe autocorrection (--autocorrect-all). Style/RedundantInterpolation: Exclude: @@ -1441,26 +820,21 @@ Style/RedundantInterpolation: - 'app/models/task_definition.rb' - 'lib/helpers/database_populator.rb' -# Offense count: 6 +# Offense count: 3 # This cop supports safe autocorrection (--autocorrect). Style/RedundantParentheses: Exclude: - 'app/models/project.rb' - 'app/models/task.rb' - - 'app/models/task_definition.rb' - 'config/application.rb' -# Offense count: 17 +# Offense count: 8 # This cop supports safe autocorrection (--autocorrect). Style/RedundantRegexpEscape: Exclude: - - 'app/api/discussion_comment_api.rb' - - 'app/api/task_comments_api.rb' - 'app/helpers/csv_helper.rb' - - 'app/helpers/file_helper.rb' - - 'app/models/project.rb' -# Offense count: 20 +# Offense count: 21 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowMultipleReturnValues. Style/RedundantReturn: @@ -1474,7 +848,7 @@ Style/RedundantReturn: - 'app/models/user.rb' - 'app/models/webcal.rb' -# Offense count: 88 +# Offense count: 174 # This cop supports safe autocorrection (--autocorrect). Style/RedundantSelf: Enabled: false @@ -1485,13 +859,7 @@ Style/RedundantSort: Exclude: - 'app/models/project.rb' -# Offense count: 2 -# This cop supports safe autocorrection (--autocorrect). -Style/RedundantStringEscape: - Exclude: - - 'app/api/authentication_api.rb' - -# Offense count: 13 +# Offense count: 9 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, AllowInnerSlashes. # SupportedStyles: slashes, percent_r, mixed @@ -1503,10 +871,7 @@ Style/RegexpLiteral: - 'app/controllers/task_submission_pdfs_controller.rb' - 'app/helpers/csv_helper.rb' - 'app/helpers/file_helper.rb' - - 'app/models/project.rb' - 'app/models/task.rb' - - 'app/models/task_definition.rb' - - 'app/models/unit.rb' # Offense count: 1 # This cop supports safe autocorrection (--autocorrect). @@ -1514,14 +879,16 @@ Style/RescueModifier: Exclude: - 'app/models/unit.rb' -# Offense count: 27 +# Offense count: 22 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: implicit, explicit Style/RescueStandardError: Exclude: + - 'app/api/staff_grant_extension_api.rb' - 'app/helpers/file_helper.rb' - 'app/helpers/timeout_helper.rb' + - 'app/mailers/notifications_mailer.rb' - 'app/models/group_submission.rb' - 'app/models/portfolio_evidence.rb' - 'app/models/project.rb' @@ -1532,13 +899,12 @@ Style/RescueStandardError: - 'lib/tasks/checks.rake' - 'lib/tasks/maintenance.rake' -# Offense count: 29 +# Offense count: 41 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength. # AllowedMethods: present?, blank?, presence, try, try! Style/SafeNavigation: Exclude: - - 'app/api/entities/minimal/minimal_unit_entity.rb' - 'app/api/entities/task_definition_entity.rb' - 'app/api/entities/tutorial_entity.rb' - 'app/api/entities/unit_entity.rb' @@ -1548,13 +914,6 @@ Style/SafeNavigation: - 'app/models/tutorial.rb' - 'app/models/unit.rb' - 'app/models/user.rb' - - 'lib/assets/ontrack_receive_action.rb' - -# Offense count: 1 -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/SelectByRegexp: - Exclude: - - 'app/helpers/file_helper.rb' # Offense count: 3 # This cop supports safe autocorrection (--autocorrect). @@ -1562,30 +921,29 @@ Style/SelfAssignment: Exclude: - 'app/models/unit.rb' -# Offense count: 4 +# Offense count: 2 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowAsExpressionSeparator. Style/Semicolon: Exclude: - - 'app/models/unit.rb' - 'lib/helpers/database_populator.rb' - 'lib/tasks/generate_pdfs.rake' -# Offense count: 2 +# Offense count: 3 # This cop supports unsafe autocorrection (--autocorrect-all). Style/SlicingWithRange: Exclude: - 'app/models/task.rb' -# Offense count: 8 +# Offense count: 9 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowModifier. Style/SoleNestedConditional: Exclude: - 'app/api/group_sets_api.rb' - - 'app/api/task_definitions_api.rb' - 'app/models/group.rb' - 'app/models/task.rb' + - 'app/services/extension_service.rb' - 'config/deakin.rb' # Offense count: 10 @@ -1597,7 +955,7 @@ Style/StringConcatenation: - 'app/models/task.rb' - 'app/models/unit.rb' -# Offense count: 349 +# Offense count: 627 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. # SupportedStyles: single_quotes, double_quotes @@ -1615,20 +973,19 @@ Style/StringLiteralsInInterpolation: - 'config/deakin.rb' - 'lib/helpers/database_populator.rb' -# Offense count: 41 +# Offense count: 72 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, MinSize. # SupportedStyles: percent, brackets Style/SymbolArray: Enabled: false -# Offense count: 5 +# Offense count: 4 # This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AllowMethodsWithArguments, AllowedMethods, AllowedPatterns, IgnoredMethods, AllowComments. -# AllowedMethods: define_method +# Configuration parameters: AllowMethodsWithArguments, AllowedMethods, AllowedPatterns, AllowComments. +# AllowedMethods: define_method, mail, respond_to Style/SymbolProc: Exclude: - - 'app/models/teaching_period.rb' - 'app/models/unit.rb' # Offense count: 2 @@ -1640,7 +997,7 @@ Style/TernaryParentheses: - 'app/models/project.rb' - 'app/models/tutorial_stream.rb' -# Offense count: 5 +# Offense count: 7 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyleForMultiline. # SupportedStylesForMultiline: comma, consistent_comma, no_comma @@ -1650,20 +1007,20 @@ Style/TrailingCommaInArguments: - 'app/api/units_api.rb' - 'app/models/unit.rb' -# Offense count: 6 +# Offense count: 9 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyleForMultiline. -# SupportedStylesForMultiline: comma, consistent_comma, no_comma +# SupportedStylesForMultiline: comma, consistent_comma, diff_comma, no_comma Style/TrailingCommaInArrayLiteral: Exclude: - 'app/models/task.rb' - 'app/models/unit.rb' - 'lib/helpers/database_populator.rb' -# Offense count: 7 +# Offense count: 10 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyleForMultiline. -# SupportedStylesForMultiline: comma, consistent_comma, no_comma +# SupportedStylesForMultiline: comma, consistent_comma, diff_comma, no_comma Style/TrailingCommaInHashLiteral: Exclude: - 'app/models/comments/task_comment.rb' @@ -1684,15 +1041,9 @@ Style/WordArray: - 'config/deakin.rb' - 'lib/helpers/database_populator.rb' -# Offense count: 1 -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/ZeroLengthPredicate: - Exclude: - - 'app/models/unit.rb' - -# Offense count: 583 +# Offense count: 594 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, IgnoredPatterns. +# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings. # URISchemes: http, https Layout/LineLength: Max: 369 diff --git a/app/api/staff_grant_extension_api.rb b/app/api/staff_grant_extension_api.rb index e63c3360d5..a54ac214a4 100644 --- a/app/api/staff_grant_extension_api.rb +++ b/app/api/staff_grant_extension_api.rb @@ -10,11 +10,17 @@ class StaffGrantExtensionApi < Grape::API before do authenticated? - error!({ - error: 'Not authorized to grant extensions', - code: 'UNAUTHORIZED', - details: {} - }, 403) unless current_user.has_tutor_capability? + + unless current_user.has_tutor_capability? + error!( + { + error: 'Not authorized to grant extensions', + code: 'UNAUTHORIZED', + details: {} + }, + 403 + ) + end end desc 'Grant extensions to multiple students', @@ -98,7 +104,7 @@ class StaffGrantExtensionApi < Grape::API weeks_requested: extension_comment.extension_weeks, extension_response: extension_comment.extension_response, task_status: extension_comment.task.status, - extension_comment: extension_comment # Store internally for notifications + extension_comment: extension_comment # Store internally for notifications } else results[:failed] << { @@ -145,7 +151,7 @@ class StaffGrantExtensionApi < Grape::API true # is_staff_grant = true ).deliver_now Rails.logger.info "Extension notifications sent successfully" - rescue => e + rescue StandardError => e Rails.logger.error "Failed to send extension notifications: #{e.message}" Rails.logger.error e.backtrace.join("\n") # Don't fail the entire request if email fails, but log the error @@ -167,7 +173,6 @@ class StaffGrantExtensionApi < Grape::API status 201 present results, with: Grape::Presenters::Presenter end - rescue ActiveRecord::RecordNotFound error!({ error: 'Unit or task definition not found' }, 404) rescue StandardError diff --git a/app/mailers/notifications_mailer.rb b/app/mailers/notifications_mailer.rb index 5aa251caf8..5c810fd80d 100644 --- a/app/mailers/notifications_mailer.rb +++ b/app/mailers/notifications_mailer.rb @@ -163,7 +163,7 @@ def extension_granted_notification(extension, granted_by) end # Main method to handle extension notifications from staff - def extension_granted(extensions, granted_by, total_selected, failed_extensions = [], is_staff_grant = false) + def extension_granted(extensions, granted_by, total_selected, failed_extensions = [], is_staff_grant: false) # Only send notifications for staff-granted bulk extensions return unless is_staff_grant && (extensions.any? || failed_extensions.any?) @@ -178,7 +178,7 @@ def extension_granted(extensions, granted_by, total_selected, failed_extensions NotificationsMailer.extension_granted_notification(extension, granted_by).deliver_now end end - rescue => e + rescue StandardError => e Rails.logger.error "Failed to send extension notifications: #{e.message}" Rails.logger.error e.backtrace.join("\n") end diff --git a/app/services/extension_service.rb b/app/services/extension_service.rb index 01af8cc4c4..0730b421b9 100644 --- a/app/services/extension_service.rb +++ b/app/services/extension_service.rb @@ -1,5 +1,5 @@ class ExtensionService - def self.grant_extension(project_id, task_definition_id, user, weeks_requested, comment, is_staff_grant = false) + def self.grant_extension(project_id, task_definition_id, user, weeks_requested, comment, is_staff_grant: false) # Find project and task project = Project.find(project_id) task_definition = project.unit.task_definitions.find(task_definition_id) @@ -18,19 +18,28 @@ def self.grant_extension(project_id, task_definition_id, user, weeks_requested, return { success: false, error: 'Extensions cannot be granted beyond task deadline', status: 403 } if duration <= 0 # ===== Student-Initiated Extension Logic (current endpoint) ===== - unless is_staff_grant - # Check task-level authorization for student requests with specific permission hash - unless AuthorisationHelpers.authorise?(user, task, :request_extension, ->(role, perm_hash, other) { task.specific_permission_hash(role, perm_hash, other) }) - return { success: false, error: 'Not authorised to request an extension for this task', status: 403 } - end + unless is_staff_grant || + AuthorisationHelpers.authorise?( + user, + task, + :request_extension, + ->(role, perm_hash, other) { task.specific_permission_hash(role, perm_hash, other) } + ) + return { + success: false, + error: 'Not authorised to request an extension for this task', + status: 403 + } end # ===== Staff Grant Logic (new endpoint) ===== - if is_staff_grant - # Check unit-level authorization for staff grants - unless AuthorisationHelpers.authorise?(user, project.unit, :grant_extensions) - return { success: false, error: 'Not authorised to grant extensions for this unit', status: 403 } - end + if is_staff_grant && + !AuthorisationHelpers.authorise?(user, project.unit, :grant_extensions) + return { + success: false, + error: 'Not authorised to grant extensions for this unit', + status: 403 + } end # ===== Common Extension Logic ===== From 7a98ff2c77b67e98baab1df80e6021e279b798da Mon Sep 17 00:00:00 2001 From: Steven Dalamaras Date: Sun, 18 Jan 2026 01:30:16 +1000 Subject: [PATCH 17/22] chore: Remove shebang from Rakefile (rubocop) --- Rakefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Rakefile b/Rakefile index 350ebd4984..f36b80f286 100644 --- a/Rakefile +++ b/Rakefile @@ -1,4 +1,3 @@ -#!/usr/bin/env rake # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. From bf6347c454033496adcf3036d9672c517a9689fc Mon Sep 17 00:00:00 2001 From: Steven Dalamaras Date: Mon, 19 Jan 2026 01:38:48 +1000 Subject: [PATCH 18/22] fix: address testing issues with SGE rebase --- app/api/extension_comments_api.rb | 3 ++- app/api/staff_grant_extension_api.rb | 5 +---- app/models/unit.rb | 11 ++++++++--- app/services/extension_service.rb | 9 +++++++++ test/api/staff_grant_extension_test.rb | 3 +-- test/api/tasks_api_test.rb | 1 - test/mailers/notifications_mailer_test.rb | 12 +++++++++--- 7 files changed, 30 insertions(+), 14 deletions(-) diff --git a/app/api/extension_comments_api.rb b/app/api/extension_comments_api.rb index 86acd33f22..ffe7127f37 100644 --- a/app/api/extension_comments_api.rb +++ b/app/api/extension_comments_api.rb @@ -25,7 +25,7 @@ class ExtensionCommentsApi < Grape::API else error!({ error: result[:error] }, result[:status]) end - +=begin if project.unit.allow_flexible_dates error!({ error: 'Extensions are disabled for this unit.' }, 403) end @@ -40,6 +40,7 @@ class ExtensionCommentsApi < Grape::API result = task.apply_for_extension(current_user, params[:comment], duration) present result.serialize(current_user), Grape::Presenters::Presenter +=end end desc 'Assess an extension for a task' diff --git a/app/api/staff_grant_extension_api.rb b/app/api/staff_grant_extension_api.rb index a54ac214a4..0db191ecbd 100644 --- a/app/api/staff_grant_extension_api.rb +++ b/app/api/staff_grant_extension_api.rb @@ -10,7 +10,6 @@ class StaffGrantExtensionApi < Grape::API before do authenticated? - unless current_user.has_tutor_capability? error!( { @@ -86,16 +85,14 @@ class StaffGrantExtensionApi < Grape::API } next end - result = ExtensionService.grant_extension( project.id, task_definition.id, current_user, params[:weeks_requested], params[:comment], - true # is_staff_grant = true + is_staff_grant: true ) - if result[:success] extension_comment = result[:result] results[:successful] << { diff --git a/app/models/unit.rb b/app/models/unit.rb index b01362f1fe..141e898e07 100644 --- a/app/models/unit.rb +++ b/app/models/unit.rb @@ -2092,18 +2092,23 @@ def get_all_tasks_for(user, my_tutorials_only = false) group( 'sq.tutorial_id', 'sq.tutorial_stream_id', + 'tasks.id', 'task_statuses.id', 'project_id', - 'tasks.id', 'task_definition_id', 'task_definitions.start_date', - 'status_id', 'completion_date', 'times_assessed', 'submission_date', 'grade', - 'quality_pts' + 'quality_pts', + 'task_comments.id', + 'task_comments.created_at', + 'task_pins.task_id', + 'task_similarities.id', + 'task_similarities.flagged' ) + if my_tutorials_only unit_role = unit_role_for(user) unless unit_role.nil? diff --git a/app/services/extension_service.rb b/app/services/extension_service.rb index 0730b421b9..9dd7fdc9fe 100644 --- a/app/services/extension_service.rb +++ b/app/services/extension_service.rb @@ -17,6 +17,15 @@ def self.grant_extension(project_id, task_definition_id, user, weeks_requested, # Check if extension would exceed deadline return { success: false, error: 'Extensions cannot be granted beyond task deadline', status: 403 } if duration <= 0 + # === Flexible dates rule === + if !is_staff_grant && project.unit.allow_flexible_dates + return { + success: false, + error: 'Extensions are disabled for this unit.', + status: 403 + } + end + # ===== Student-Initiated Extension Logic (current endpoint) ===== unless is_staff_grant || AuthorisationHelpers.authorise?( diff --git a/test/api/staff_grant_extension_test.rb b/test/api/staff_grant_extension_test.rb index a90d74dcd0..9c7c8af7c3 100644 --- a/test/api/staff_grant_extension_test.rb +++ b/test/api/staff_grant_extension_test.rb @@ -33,7 +33,6 @@ def test_staff_grant_extension_success max_quality_pts: 0 }) td.save! - data_to_post = { student_ids: [project.student.id], task_definition_id: td.id, @@ -58,7 +57,7 @@ def test_staff_grant_extension_success notification = notifications.first assert_match /You were granted an extension for task/, notification.message assert_match /#{td.name}/, notification.message - assert_match /#{unit.name}/, notification.message + assert_match /#{unit.code}/, notification.message td.destroy! unit.destroy! diff --git a/test/api/tasks_api_test.rb b/test/api/tasks_api_test.rb index 7848aaec8d..a7eb30a97e 100644 --- a/test/api/tasks_api_test.rb +++ b/test/api/tasks_api_test.rb @@ -979,7 +979,6 @@ def test_resubmission_doesnt_change_submission_date assert_equal 201, last_response.status, last_response_body tasks = unit.tasks_for_task_inbox(tutor, false) - assert_equal project1.id, tasks.first.project.id, "First task in inbox should be project1's task" assert_equal project2.id, tasks.second.project.id, "Second task in inbox should be project2's task" diff --git a/test/mailers/notifications_mailer_test.rb b/test/mailers/notifications_mailer_test.rb index 393b380298..f410055e71 100644 --- a/test/mailers/notifications_mailer_test.rb +++ b/test/mailers/notifications_mailer_test.rb @@ -58,8 +58,8 @@ def setup end def teardown - @task_definition.destroy! - @unit.destroy! + # @task_definition.destroy! + # @unit.destroy! ActionMailer::Base.deliveries.clear end @@ -190,7 +190,13 @@ def teardown } unit_role = @unit.unit_roles.find_by(user: @staff) - summary_stats[:staff][unit_role] = { data: "test data" } + summary_stats[:staff][unit_role.user] = { + tasks_awaiting_feedback_count: 1, + weekly_engagements_count: 2, + staff_engagements: 3, + oldest_task_days: 4, + weekly_total_tasks_discussed: 5 + } # Get the mail object mail = NotificationsMailer.weekly_staff_summary(unit_role, summary_stats) From 9dad019590864d23a200c91045d7e49026012aee Mon Sep 17 00:00:00 2001 From: Steven Dalamaras Date: Mon, 19 Jan 2026 01:43:46 +1000 Subject: [PATCH 19/22] chore: remove block comment (rubocop) --- app/api/extension_comments_api.rb | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/app/api/extension_comments_api.rb b/app/api/extension_comments_api.rb index ffe7127f37..966d69e6ff 100644 --- a/app/api/extension_comments_api.rb +++ b/app/api/extension_comments_api.rb @@ -25,22 +25,6 @@ class ExtensionCommentsApi < Grape::API else error!({ error: result[:error] }, result[:status]) end -=begin - if project.unit.allow_flexible_dates - error!({ error: 'Extensions are disabled for this unit.' }, 403) - end - - error!({ error: 'Extension weeks can not be 0.' }, 403) if params[:weeks_requested] == 0 - - max_duration = task.weeks_can_extend - duration = params[:weeks_requested] - duration = max_duration unless params[:weeks_requested] <= max_duration - - error!({ error: 'Extensions cannot be granted beyond task deadline.' }, 403) if duration <= 0 - - result = task.apply_for_extension(current_user, params[:comment], duration) - present result.serialize(current_user), Grape::Presenters::Presenter -=end end desc 'Assess an extension for a task' From 4408f58ae148794a888183952f888607bcd6a61b Mon Sep 17 00:00:00 2001 From: Steven Dalamaras Date: Mon, 19 Jan 2026 18:51:32 +1000 Subject: [PATCH 20/22] fix: broken regex for last names with ' --- test/api/staff_grant_extension_test.rb | 3 ++- test/mailers/notifications_mailer_test.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/test/api/staff_grant_extension_test.rb b/test/api/staff_grant_extension_test.rb index 9c7c8af7c3..a38b28b6ec 100644 --- a/test/api/staff_grant_extension_test.rb +++ b/test/api/staff_grant_extension_test.rb @@ -179,7 +179,8 @@ def test_staff_grant_extension_transaction_rollback weeks_requested: 1, comment: 'Initial extensions' } - +puts unit.students.map(&:id) +puts project2.student.id add_auth_header_for user: staff post_json "/api/units/#{unit.id}/staff-grant-extension", data_to_post assert_equal 201, last_response.status, 'Should succeed for both students' diff --git a/test/mailers/notifications_mailer_test.rb b/test/mailers/notifications_mailer_test.rb index f410055e71..a8a92358ef 100644 --- a/test/mailers/notifications_mailer_test.rb +++ b/test/mailers/notifications_mailer_test.rb @@ -99,7 +99,7 @@ def teardown # Verify email properties assert_equal [@students.first.email], mail.to assert_equal "#{@unit.name}: Extension granted for #{@task_definition.name}", mail.subject - assert_match /Dear #{@students.first.name}/, mail.html_part.body.to_s + assert_match /Dear #{@students.first.first_name}/, mail.html_part.body.to_s # Verify from address contains staff email assert_includes mail.from.first, @staff.email From 90c8789d99f4e4b07b7d19741d8763d3d3237b6e Mon Sep 17 00:00:00 2001 From: Steven Dalamaras Date: Mon, 19 Jan 2026 21:00:50 +1000 Subject: [PATCH 21/22] fix: failing test due to name ambiguity --- test/api/d2l_test.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/api/d2l_test.rb b/test/api/d2l_test.rb index 37eadc7ab3..6c66e15cb6 100644 --- a/test/api/d2l_test.rb +++ b/test/api/d2l_test.rb @@ -396,7 +396,10 @@ def test_post_grades assert_includes result[1], "Success,#{p1.student.student_id},#{p1.grade},Posted grade for #{p1.student.username}" assert_includes result[2], "Success,#{p2.student.student_id} - somehow mismatch,#{p2.grade},Posted grade for #{p2.student.username}" assert_includes result[3], "Skipped,#{p3.student.student_id} - somehow mismatch,\"\",No grade for #{p3.student.username}" - assert_includes result[4], "Error,#{s1.student_id},\"\",No OnTrack result for" + assert_match( + /Error,#{s1.student_id},"",No (OnTrack|Doubtfire) result for/, + result[4] + ) assert_includes result[5], "Error,#{p4.student.username},#{p4.grade},Not found in D2L" add_auth_header_for(user: unit.main_convenor_user) From 1df9bb246c6776d52db26f60142e7b74a416f38c Mon Sep 17 00:00:00 2001 From: Steven Dalamaras Date: Wed, 4 Feb 2026 18:18:41 +1000 Subject: [PATCH 22/22] fix: address changes requested to #565 fix: changes requested to #565 fix: db/schema.rb changes requested to #565 fix: change approach to institution settings fix: rubocop nit fix: test issues --- .rubocop_todo.yml | 929 +++++++++++++++--- Gemfile.lock | 22 +- Rakefile | 1 + app/api/api_root.rb | 2 - app/mailers/notifications_mailer.rb | 10 +- .../extension_granted.html.erb | 2 +- db/schema.rb | 480 ++++----- test/mailers/notifications_mailer_test.rb | 90 +- 8 files changed, 1093 insertions(+), 443 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 0fc47dee4c..6268621a0f 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,11 +1,29 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2026-01-17 13:44:49 UTC using RuboCop version 1.75.1. +# on 2022-12-29 09:30:33 UTC using RuboCop version 1.41.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, IndentationWidth. +# SupportedStyles: outdent, indent +Layout/AccessModifierIndentation: + Exclude: + - 'app/models/unit.rb' + +# Offense count: 10 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, IndentationWidth. +# SupportedStyles: with_first_argument, with_fixed_indentation +Layout/ArgumentAlignment: + Exclude: + - 'app/api/activity_types_authenticated_api.rb' + - 'app/api/authentication_api.rb' + - 'app/api/campuses_authenticated_api.rb' + # Offense count: 3 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, IndentationWidth. @@ -14,25 +32,90 @@ Layout/ArrayAlignment: Exclude: - 'app/helpers/file_helper.rb' -# Offense count: 8 +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: IndentationWidth. +Layout/AssignmentIndentation: + Exclude: + - 'lib/tasks/init.rake' + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyleAlignWith. +# SupportedStylesAlignWith: either, start_of_block, start_of_line +Layout/BlockAlignment: + Exclude: + - 'app/models/project.rb' + - 'config/deakin.rb' + +# Offense count: 19 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, IndentOneStep, IndentationWidth. +# SupportedStyles: case, end +Layout/CaseIndentation: + Exclude: + - 'app/models/task_status.rb' + - 'app/models/webcal.rb' + - 'config/deakin.rb' + +# Offense count: 4 +# This cop supports safe autocorrection (--autocorrect). +Layout/ClosingParenthesisIndentation: + Exclude: + - 'app/api/units_api.rb' + - 'app/models/unit.rb' + - 'config/deakin.rb' + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowForAlignment. +Layout/CommentIndentation: + Exclude: + - 'config/initializers/inflections.rb' + +# Offense count: 76 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: leading, trailing Layout/DotPosition: Exclude: + - 'app/api/group_sets_api.rb' + - 'app/api/task_definitions_api.rb' + - 'app/api/tasks_api.rb' + - 'app/models/project.rb' + - 'app/models/task.rb' + - 'app/models/tutorial_enrolment.rb' - 'app/models/unit.rb' + - 'app/models/unit_role.rb' + - 'config/deakin.rb' + - 'lib/tasks/maintenance.rake' + +# Offense count: 3 +# This cop supports safe autocorrection (--autocorrect). +Layout/ElseAlignment: + Exclude: + - 'app/api/students_api.rb' + - 'app/models/task_definition.rb' -# Offense count: 28 +# Offense count: 67 # This cop supports safe autocorrection (--autocorrect). Layout/EmptyLineAfterGuardClause: Enabled: false +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +Layout/EmptyLineAfterMagicComment: + Exclude: + - 'app/api/discussion_comment_api.rb' + - 'app/models/comments/task_comment.rb' + # Offense count: 4 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EmptyLineBetweenMethodDefs, EmptyLineBetweenClassDefs, EmptyLineBetweenModuleDefs, DefLikeMacros, AllowAdjacentOneLineDefs, NumberOfEmptyLines. +# Configuration parameters: EmptyLineBetweenMethodDefs, EmptyLineBetweenClassDefs, EmptyLineBetweenModuleDefs, AllowAdjacentOneLineDefs, NumberOfEmptyLines. Layout/EmptyLineBetweenDefs: Exclude: - 'app/models/overseer_assessment.rb' + - 'app/models/role.rb' - 'app/models/unit.rb' # Offense count: 13 @@ -40,35 +123,101 @@ Layout/EmptyLineBetweenDefs: Layout/EmptyLines: Exclude: - 'app/api/submission/portfolio_evidence_api.rb' + - 'app/helpers/csv_helper.rb' + - 'app/models/auth_token.rb' - 'app/models/overseer_assessment.rb' + - 'app/models/role.rb' - 'app/models/unit.rb' + - 'config/environments/development.rb' + - 'lib/tasks/init.rake' -# Offense count: 23 +# Offense count: 9 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: around, only_before +Layout/EmptyLinesAroundAccessModifier: + Exclude: + - 'app/models/activity_type.rb' + - 'app/models/campus.rb' + - 'app/models/project.rb' + - 'app/models/task.rb' + - 'app/models/tutorial.rb' + - 'app/models/tutorial_enrolment.rb' + - 'app/models/tutorial_stream.rb' + - 'app/models/unit.rb' + +# Offense count: 6 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: empty_lines, no_empty_lines +Layout/EmptyLinesAroundBlockBody: + Exclude: + - 'app/api/submission/portfolio_evidence_api.rb' + - 'app/api/webcal_public_api.rb' + - 'app/models/tutorial_enrolment.rb' + - 'config/environments/production.rb' + - 'lib/tasks/init.rake' + - 'lib/tasks/send_status_emails.rake' + +# Offense count: 22 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines, beginning_only, ending_only Layout/EmptyLinesAroundClassBody: Enabled: false -# Offense count: 2 +# Offense count: 5 # This cop supports safe autocorrection (--autocorrect). Layout/EmptyLinesAroundExceptionHandlingKeywords: Exclude: - - 'app/api/staff_grant_extension_api.rb' + - 'app/helpers/file_helper.rb' + - 'app/models/overseer_assessment.rb' - 'app/models/task.rb' + - 'app/models/unit.rb' + - 'lib/assets/ontrack_receive_action.rb' -# Offense count: 1 +# Offense count: 2 # This cop supports safe autocorrection (--autocorrect). Layout/EmptyLinesAroundMethodBody: Exclude: + - 'app/models/portfolio_evidence.rb' - 'app/models/unit_role.rb' -# Offense count: 1 +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines +Layout/EmptyLinesAroundModuleBody: + Exclude: + - 'app/api/admin/overseer_admin_api.rb' + - 'app/helpers/authentication_helpers.rb' + +# Offense count: 5 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyleAlignWith, Severity. +# SupportedStylesAlignWith: keyword, variable, start_of_line +Layout/EndAlignment: + Exclude: + - 'app/api/students_api.rb' + - 'app/channels/application_cable/channel.rb' + - 'app/models/task_definition.rb' + - 'config/application.rb' + +# Offense count: 21 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment. Layout/ExtraSpacing: Exclude: - - 'app/api/staff_grant_extension_api.rb' + - 'app/api/entities/project_entity.rb' + - 'app/api/entities/unit_entity.rb' + - 'app/api/projects_api.rb' + - 'app/api/tutorial_streams_api.rb' + - 'app/helpers/mime_check_helpers.rb' + - 'app/mailers/notifications_mailer.rb' + - 'app/models/project.rb' + - 'app/models/task.rb' + - 'config/initializers/swagger.rb' + - 'lib/helpers/database_populator.rb' # Offense count: 2 # This cop supports safe autocorrection (--autocorrect). @@ -78,54 +227,251 @@ Layout/FirstArrayElementIndentation: Exclude: - 'app/models/unit.rb' -# Offense count: 6 +# Offense count: 4 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, IndentationWidth. # SupportedStyles: special_inside_parentheses, consistent, align_braces Layout/FirstHashElementIndentation: Exclude: - - 'app/api/staff_grant_extension_api.rb' - 'app/models/unit.rb' + - 'config/no_institution_setting.rb' + - 'lib/helpers/database_populator.rb' + +# Offense count: 114 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. +# SupportedHashRocketStyles: key, separator, table +# SupportedColonStyles: key, separator, table +# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit +Layout/HashAlignment: + Exclude: + - 'app/api/authentication_api.rb' + - 'app/api/settings_api.rb' + - 'app/models/task.rb' + - 'app/models/teaching_period.rb' + - 'app/models/unit.rb' + - 'app/models/user.rb' + - 'config/deakin.rb' + - 'config/environments/production.rb' + - 'config/no_institution_setting.rb' + - 'lib/helpers/database_populator.rb' + - 'lib/helpers/find_or_create_students.rb' # Offense count: 9 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: Width, AllowedPatterns. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: normal, indented_internal_methods +Layout/IndentationConsistency: + Exclude: + - 'app/models/task.rb' + - 'app/models/task_definition.rb' + - 'app/models/tutorial_enrolment.rb' + - 'config/deakin.rb' + +# Offense count: 36 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: Width, AllowedPatterns, IgnoredPatterns. Layout/IndentationWidth: Exclude: + - 'app/api/students_api.rb' + - 'app/channels/application_cable/channel.rb' + - 'app/mailers/notifications_mailer.rb' + - 'app/models/task.rb' + - 'app/models/task_definition.rb' + - 'app/models/tutorial_enrolment.rb' + - 'app/models/unit.rb' + - 'app/models/user.rb' + - 'config/application.rb' + - 'config/deakin.rb' + - 'config/initializers/inflections.rb' + - 'config/no_institution_setting.rb' + - 'lib/tasks/send_status_emails.rake' + - 'lib/tasks/skip_prod.rake' + +# Offense count: 38 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowDoxygenCommentStyle, AllowGemfileRubyComment. +Layout/LeadingCommentSpace: + Exclude: + - 'app/api/entities/group_entity.rb' + - 'app/api/entities/tutorial_entity.rb' + - 'app/api/entities/tutorial_stream_entity.rb' + - 'app/api/task_definitions_api.rb' + - 'app/models/overseer_assessment.rb' + - 'app/models/project.rb' + - 'app/models/task.rb' - 'app/models/task_definition.rb' + - 'app/models/task_status.rb' + - 'app/models/teaching_period.rb' + - 'app/models/unit.rb' + - 'config/deakin.rb' + - 'lib/helpers/database_populator.rb' + +# Offense count: 5 +# This cop supports safe autocorrection (--autocorrect). +Layout/LeadingEmptyLines: + Exclude: + - 'app/api/entities/minimal/minimal_unit_entity.rb' + - 'app/api/entities/minimal/minimal_user_entity.rb' + - 'app/api/entities/user_entity.rb' + - 'app/api/entities/webcal_entity.rb' + - 'config/no_institution_setting.rb' + +# Offense count: 16 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: AutoCorrect, EnforcedStyle. +# SupportedStyles: leading, trailing +Layout/LineContinuationLeadingSpace: + Exclude: + - 'config/application.rb' + +# Offense count: 16 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AutoCorrect, EnforcedStyle. +# SupportedStyles: space, no_space +Layout/LineContinuationSpacing: + Exclude: + - 'config/application.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, IndentationWidth. +# SupportedStyles: aligned, indented +Layout/LineEndStringConcatenationIndentation: + Exclude: + - 'config/application.rb' + +# Offense count: 4 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: symmetrical, new_line, same_line +Layout/MultilineMethodCallBraceLayout: + Exclude: + - 'app/api/units_api.rb' - 'app/models/unit.rb' + - 'app/models/unit_role.rb' -# Offense count: 45 +# Offense count: 82 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, IndentationWidth. # SupportedStyles: aligned, indented, indented_relative_to_receiver Layout/MultilineMethodCallIndentation: Exclude: + - 'app/api/activity_types_authenticated_api.rb' + - 'app/api/admin/overseer_admin_api.rb' + - 'app/api/campuses_authenticated_api.rb' + - 'app/api/group_sets_api.rb' + - 'app/api/learning_outcomes_api.rb' + - 'app/api/task_definitions_api.rb' + - 'app/api/tasks_api.rb' + - 'app/api/teaching_periods_authenticated_api.rb' + - 'app/api/unit_roles_api.rb' + - 'app/api/webcal_api.rb' + - 'app/models/project.rb' + - 'app/models/tutorial_enrolment.rb' - 'app/models/unit.rb' - 'app/models/unit_role.rb' + - 'config/deakin.rb' -# Offense count: 35 +# Offense count: 11 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, IndentationWidth. +# SupportedStyles: aligned, indented +Layout/MultilineOperationIndentation: + Exclude: + - 'app/api/authentication_api.rb' + - 'app/models/unit.rb' + - 'config/application.rb' + +# Offense count: 4 +# This cop supports safe autocorrection (--autocorrect). +Layout/SpaceAfterColon: + Exclude: + - 'app/api/extension_comments_api.rb' + - 'config/deakin.rb' + +# Offense count: 46 # This cop supports safe autocorrection (--autocorrect). Layout/SpaceAfterComma: Exclude: + - 'app/api/api_root.rb' + - 'app/api/tutorial_streams_api.rb' + - 'app/api/units_api.rb' + - 'app/helpers/application_helper.rb' - 'app/helpers/file_helper.rb' + - 'app/models/activity_type.rb' + - 'app/models/campus.rb' + - 'app/models/task.rb' + - 'app/models/teaching_period.rb' + - 'config/deakin.rb' + - 'lib/helpers/database_populator.rb' -# Offense count: 3 +# Offense count: 7 +# This cop supports safe autocorrection (--autocorrect). +Layout/SpaceAfterMethodName: + Exclude: + - 'app/models/project.rb' + - 'app/models/user.rb' + - 'config/deakin.rb' + - 'config/no_institution_setting.rb' + +# Offense count: 14 # This cop supports safe autocorrection (--autocorrect). Layout/SpaceAfterNot: Exclude: + - 'app/api/group_sets_api.rb' + - 'app/api/tutorial_enrolments_api.rb' + - 'app/models/comments/extension_comment.rb' + - 'app/models/comments/task_comment.rb' + - 'app/models/group.rb' + - 'app/models/project.rb' + - 'app/models/task.rb' + - 'app/models/tutorial.rb' + - 'app/models/tutorial_enrolment.rb' + - 'app/models/unit.rb' + - 'config/deakin.rb' + +# Offense count: 22 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyleInsidePipes. +# SupportedStylesInsidePipes: space, no_space +Layout/SpaceAroundBlockParameters: + Exclude: + - 'app/api/entities/project_entity.rb' + - 'app/models/task.rb' + - 'app/models/webcal.rb' + - 'lib/helpers/database_populator.rb' + - 'lib/tasks/init.rake' + +# Offense count: 6 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: space, no_space +Layout/SpaceAroundEqualsInParameterDefault: + Exclude: + - 'app/models/teaching_period.rb' - 'app/models/unit.rb' + - 'lib/helpers/faker_randomiser.rb' -# Offense count: 2 +# Offense count: 1 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowForAlignment, EnforcedStyleForExponentOperator, EnforcedStyleForRationalLiterals. +Layout/SpaceAroundMethodCallOperator: + Exclude: + - 'app/models/unit.rb' + +# Offense count: 7 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowForAlignment, EnforcedStyleForExponentOperator. # SupportedStylesForExponentOperator: space, no_space -# SupportedStylesForRationalLiterals: space, no_space Layout/SpaceAroundOperators: Exclude: + - 'app/api/submission/portfolio_evidence_api.rb' + - 'app/models/task.rb' + - 'config/deakin.rb' + - 'config/initializers/swagger.rb' - 'lib/helpers/database_populator.rb' -# Offense count: 2 +# Offense count: 12 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. # SupportedStyles: space, no_space @@ -133,39 +479,170 @@ Layout/SpaceAroundOperators: Layout/SpaceBeforeBlockBraces: Exclude: - 'app/helpers/file_helper.rb' + - 'app/models/activity_type.rb' + - 'app/models/campus.rb' + - 'app/models/project.rb' - 'app/models/task_definition.rb' + - 'app/models/teaching_period.rb' + - 'app/models/unit.rb' + - 'app/models/webcal.rb' -# Offense count: 2 +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Layout/SpaceBeforeBrackets: + Exclude: + - 'app/models/unit.rb' + +# Offense count: 118 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBrackets. # SupportedStyles: space, no_space, compact # SupportedStylesForEmptyBrackets: space, no_space Layout/SpaceInsideArrayLiteralBrackets: Exclude: + - 'app/api/projects_api.rb' + - 'app/api/units_api.rb' + - 'app/api/users_api.rb' - 'app/helpers/file_helper.rb' + - 'app/models/group.rb' + - 'app/models/group_set.rb' + - 'app/models/project.rb' + - 'app/models/task.rb' + - 'app/models/task_definition.rb' + - 'app/models/unit.rb' + - 'app/models/unit_role.rb' + - 'app/models/user.rb' + - 'config/deakin.rb' + - 'config/initializers/devise.rb' + - 'lib/helpers/database_populator.rb' -# Offense count: 5 +# Offense count: 34 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. # SupportedStyles: space, no_space # SupportedStylesForEmptyBraces: space, no_space Layout/SpaceInsideBlockBraces: Exclude: + - 'app/api/entities/project_entity.rb' + - 'app/helpers/file_helper.rb' + - 'app/mailers/notifications_mailer.rb' + - 'app/models/activity_type.rb' + - 'app/models/campus.rb' + - 'app/models/portfolio_evidence.rb' + - 'app/models/project.rb' + - 'app/models/task.rb' + - 'app/models/task_definition.rb' + - 'app/models/teaching_period.rb' + - 'app/models/unit.rb' + - 'app/models/webcal.rb' + +# Offense count: 69 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. +# SupportedStyles: space, no_space, compact +# SupportedStylesForEmptyBraces: space, no_space +Layout/SpaceInsideHashLiteralBraces: + Exclude: + - 'app/api/api_root.rb' + - 'app/api/extension_comments_api.rb' + - 'app/api/group_sets_api.rb' + - 'app/api/projects_api.rb' + - 'app/api/task_comments_api.rb' + - 'app/api/teaching_periods_authenticated_api.rb' + - 'app/api/units_api.rb' + - 'app/models/overseer_assessment.rb' + - 'app/models/project.rb' + - 'app/models/tutorial_stream.rb' + - 'app/models/unit.rb' + - 'app/models/user.rb' + - 'config/deakin.rb' + - 'lib/helpers/database_populator.rb' + +# Offense count: 27 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: space, compact, no_space +Layout/SpaceInsideParens: + Exclude: + - 'app/api/api_root.rb' + - 'app/api/units_api.rb' - 'app/helpers/file_helper.rb' + - 'app/models/project.rb' - 'app/models/task_definition.rb' + - 'app/models/tutorial_enrolment.rb' + - 'app/models/unit.rb' + - 'config/deakin.rb' + - 'lib/helpers/database_populator.rb' + - 'lib/tasks/generate_pdfs.rake' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Layout/SpaceInsideRangeLiteral: + Exclude: + - 'lib/helpers/database_populator.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBrackets. +# SupportedStyles: space, no_space +# SupportedStylesForEmptyBrackets: space, no_space +Layout/SpaceInsideReferenceBrackets: + Exclude: + - 'app/models/unit.rb' + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: space, no_space +Layout/SpaceInsideStringInterpolation: + Exclude: + - 'app/helpers/file_helper.rb' + - 'app/models/tutorial_enrolment.rb' + +# Offense count: 4 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: final_newline, final_blank_line +Layout/TrailingEmptyLines: + Exclude: + - 'app/channels/application_cable/channel.rb' + - 'config/initializers/swagger.rb' + - 'lib/helpers/faker_randomiser.rb' + - 'lib/tasks/send_status_emails.rake' + +# Offense count: 10 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowInHeredoc. +Layout/TrailingWhitespace: + Exclude: + - 'app/mailers/convenor_contact_mailer.rb' + - 'app/mailers/portfolio_evidence_mailer.rb' + - 'app/models/portfolio_evidence.rb' + - 'config/deakin.rb' + - 'lib/tasks/send_status_emails.rake' # Offense count: 1 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowedMethods, AllowedPatterns. +# Configuration parameters: AllowedMethods, AllowedPatterns, IgnoredMethods. Lint/AmbiguousBlockAssociation: Exclude: - 'app/models/task.rb' -# Offense count: 1 +# Offense count: 3 # This cop supports safe autocorrection (--autocorrect). Lint/AmbiguousOperator: Exclude: + - 'app/helpers/file_helper.rb' - 'app/models/portfolio_evidence.rb' + - 'app/models/task.rb' + +# Offense count: 14 +# This cop supports safe autocorrection (--autocorrect). +Lint/AmbiguousOperatorPrecedence: + Exclude: + - 'app/models/project.rb' + - 'app/models/task.rb' + - 'app/models/unit.rb' + - 'lib/tasks/populate.rake' # Offense count: 7 # This cop supports safe autocorrection (--autocorrect). @@ -178,12 +655,28 @@ Lint/AmbiguousRegexpLiteral: - 'app/helpers/file_helper.rb' # Offense count: 1 -# This cop supports unsafe autocorrection (--autocorrect-all). +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowSafeAssignment. Lint/AssignmentInCondition: Exclude: - 'app/channels/application_cable/connection.rb' +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +Lint/DeprecatedClassMethods: + Exclude: + - 'app/models/task.rb' + +# Offense count: 24 +# Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches. +Lint/DuplicateBranch: + Exclude: + - 'app/api/api_root.rb' + - 'app/helpers/file_helper.rb' + - 'app/models/project.rb' + - 'app/models/task_status.rb' + - 'lib/tasks/populate.rake' + # Offense count: 2 # This cop supports safe autocorrection (--autocorrect). Lint/ElseLayout: @@ -191,15 +684,21 @@ Lint/ElseLayout: - 'app/models/project.rb' - 'app/models/task.rb' +# Offense count: 1 +# Configuration parameters: AllowComments, AllowEmptyLambdas. +Lint/EmptyBlock: + Exclude: + - 'app/helpers/file_helper.rb' + # Offense count: 1 Lint/FloatComparison: Exclude: - 'app/models/project.rb' -# Offense count: 12 -# This cop supports safe autocorrection (--autocorrect). +# Offense count: 8 Lint/ImplicitStringConcatenation: Exclude: + - 'app/api/learning_alignment_api.rb' - 'app/api/learning_outcomes_api.rb' # Offense count: 2 @@ -210,7 +709,6 @@ Lint/Loop: - 'config/no_institution_setting.rb' # Offense count: 4 -# Configuration parameters: AllowedParentClasses. Lint/MissingSuper: Exclude: - 'app/controllers/lecture_resource_downloads_controller.rb' @@ -218,6 +716,15 @@ Lint/MissingSuper: - 'app/controllers/task_downloads_controller.rb' - 'app/controllers/task_submission_pdfs_controller.rb' +# Offense count: 18 +# This cop supports unsafe autocorrection (--autocorrect-all). +Lint/NonAtomicFileOperation: + Exclude: + - 'app/helpers/file_helper.rb' + - 'app/models/comments/task_comment.rb' + - 'app/models/project.rb' + - 'app/models/task.rb' + # Offense count: 1 Lint/NonLocalExitFromIterator: Exclude: @@ -229,13 +736,18 @@ Lint/ParenthesesAsGroupedExpression: Exclude: - 'app/models/unit.rb' -# Offense count: 4 +# Offense count: 5 # This cop supports safe autocorrection (--autocorrect). Lint/RedundantStringCoercion: Exclude: - 'app/helpers/file_helper.rb' -# Offense count: 16 +# Offense count: 2 +Lint/RequireParentheses: + Exclude: + - 'config/application.rb' + +# Offense count: 15 Lint/RescueException: Exclude: - 'app/models/portfolio_evidence.rb' @@ -244,16 +756,34 @@ Lint/RescueException: - 'config/deakin.rb' - 'lib/tasks/generate_pdfs.rake' -# Offense count: 4 +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Lint/ScriptPermission: + Exclude: + - 'Rakefile' + +# Offense count: 8 Lint/ShadowingOuterLocalVariable: Exclude: + - 'app/models/learning_outcome.rb' + - 'app/models/teaching_period.rb' - 'app/models/unit.rb' -# Offense count: 1 +# Offense count: 4 # Configuration parameters: AllowComments, AllowNil. Lint/SuppressedException: Exclude: + - 'app/models/project.rb' - 'app/models/task.rb' + - 'app/models/task_definition.rb' + +# Offense count: 5 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: strict, consistent +Lint/SymbolConversion: + Exclude: + - 'lib/tasks/init.rake' # Offense count: 2 # Configuration parameters: AllowKeywordBlockArguments. @@ -261,9 +791,9 @@ Lint/UnderscorePrefixedVariableName: Exclude: - 'app/models/comments/discussion_comment.rb' -# Offense count: 45 +# Offense count: 33 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AutoCorrect, IgnoreEmptyBlocks, AllowUnusedKeywordArguments. +# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. Lint/UnusedBlockArgument: Exclude: - 'app/api/entities/comment_entity.rb' @@ -281,10 +811,9 @@ Lint/UnusedBlockArgument: - 'lib/helpers/database_populator.rb' - 'lib/tasks/checks.rake' -# Offense count: 9 +# Offense count: 7 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AutoCorrect, AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods, NotImplementedExceptions. -# NotImplementedExceptions: NotImplementedError +# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods. Lint/UnusedMethodArgument: Exclude: - 'app/models/project.rb' @@ -292,53 +821,51 @@ Lint/UnusedMethodArgument: - 'config/deakin.rb' - 'config/no_institution_setting.rb' -# Offense count: 61 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AutoCorrect. +# Offense count: 55 Lint/UselessAssignment: Enabled: false -# Offense count: 225 -# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. +# Offense count: 145 +# Configuration parameters: AllowedMethods, AllowedPatterns, IgnoredMethods, CountRepeatedAttributes. Metrics/AbcSize: Max: 153 -# Offense count: 96 -# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. +# Offense count: 65 +# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, AllowedMethods, AllowedPatterns, IgnoredMethods. # AllowedMethods: refine Metrics/BlockLength: - Max: 157 + Max: 200 -# Offense count: 10 -# Configuration parameters: CountBlocks, CountModifierForms. +# Offense count: 12 +# Configuration parameters: CountBlocks. Metrics/BlockNesting: Max: 5 -# Offense count: 96 -# Configuration parameters: AllowedMethods, AllowedPatterns. +# Offense count: 65 +# Configuration parameters: AllowedMethods, AllowedPatterns, IgnoredMethods. Metrics/CyclomaticComplexity: - Max: 36 + Max: 39 -# Offense count: 273 -# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. +# Offense count: 173 +# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, AllowedMethods, AllowedPatterns, IgnoredMethods. Metrics/MethodLength: - Max: 115 + Max: 140 -# Offense count: 4 +# Offense count: 1 # Configuration parameters: CountComments, CountAsOne. Metrics/ModuleLength: - Max: 580 + Enabled: false -# Offense count: 7 +# Offense count: 4 # Configuration parameters: CountKeywordArgs. Metrics/ParameterLists: MaxOptionalParameters: 4 Max: 8 -# Offense count: 84 -# Configuration parameters: AllowedMethods, AllowedPatterns. +# Offense count: 62 +# Configuration parameters: AllowedMethods, AllowedPatterns, IgnoredMethods. Metrics/PerceivedComplexity: - Max: 39 + Max: 50 # Offense count: 2 Naming/AccessorMethodName: @@ -347,14 +874,13 @@ Naming/AccessorMethodName: - 'app/models/user.rb' # Offense count: 1 -# Configuration parameters: EnforcedStyle, AllowedPatterns, ForbiddenIdentifiers, ForbiddenPatterns. +# Configuration parameters: EnforcedStyle, AllowedPatterns, IgnoredPatterns. # SupportedStyles: snake_case, camelCase -# ForbiddenIdentifiers: __id__, __send__ Naming/MethodName: Exclude: - 'app/models/comments/discussion_comment.rb' -# Offense count: 13 +# Offense count: 16 # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. # AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to Naming/MethodParameterName: @@ -366,14 +892,15 @@ Naming/MethodParameterName: - 'app/models/task.rb' - 'app/models/unit.rb' -# Offense count: 37 -# Configuration parameters: NamePrefix, ForbiddenPrefixes, AllowedMethods, MethodDefinitionMacros, UseSorbetSigs. -# NamePrefix: is_, has_, have_, does_ -# ForbiddenPrefixes: is_, has_, have_, does_ +# Offense count: 36 +# Configuration parameters: NamePrefix, ForbiddenPrefixes, AllowedMethods, MethodDefinitionMacros. +# NamePrefix: is_, has_, have_ +# ForbiddenPrefixes: is_, has_, have_ # AllowedMethods: is_a? # MethodDefinitionMacros: define_method, define_singleton_method Naming/PredicateName: Exclude: + - 'spec/**/*' - 'app/api/entities/unit_entity.rb' - 'app/models/group.rb' - 'app/models/overseer_assessment.rb' @@ -387,7 +914,7 @@ Naming/PredicateName: - 'lib/tasks/generate_pdfs.rake' # Offense count: 27 -# Configuration parameters: EnforcedStyle, AllowedIdentifiers, AllowedPatterns, ForbiddenIdentifiers, ForbiddenPatterns. +# Configuration parameters: EnforcedStyle, AllowedIdentifiers, AllowedPatterns. # SupportedStyles: snake_case, camelCase Naming/VariableName: Exclude: @@ -395,12 +922,28 @@ Naming/VariableName: - 'app/models/comments/task_comment.rb' - 'config/deakin.rb' -# Offense count: 5 +# Offense count: 2 +# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns. +# SupportedStyles: snake_case, normalcase, non_integer +# AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339 +Naming/VariableNumber: + Exclude: + - 'app/models/unit.rb' + +# Offense count: 3 +# This cop supports unsafe autocorrection (--autocorrect-all). +Security/IoMethods: + Exclude: + - 'app/api/discussion_comment_api.rb' + - 'app/api/task_comments_api.rb' + +# Offense count: 13 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: separated, grouped Style/AccessorGrouping: Exclude: + - 'app/models/project.rb' - 'app/models/task.rb' # Offense count: 2 @@ -427,9 +970,9 @@ Style/AndOr: - 'app/models/tutorial_enrolment.rb' - 'app/models/tutorial_stream.rb' -# Offense count: 6 +# Offense count: 7 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, AllowedMethods, AllowedPatterns, AllowBracesOnProceduralOneLiners, BracesRequiredMethods. +# Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, AllowedMethods, AllowedPatterns, IgnoredMethods, AllowBracesOnProceduralOneLiners, BracesRequiredMethods. # SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces # ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object # FunctionalMethods: let, let!, subject, watch @@ -443,26 +986,28 @@ Style/BlockDelimiters: - 'config/deakin.rb' - 'lib/helpers/database_populator.rb' -# Offense count: 1 +# Offense count: 6 # This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: MinBranchesCount. Style/CaseLikeIf: Exclude: - 'app/helpers/csv_helper.rb' + - 'app/helpers/file_helper.rb' + - 'app/models/comments/task_comment.rb' + - 'app/models/user.rb' -# Offense count: 1 +# Offense count: 3 # This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: EnforcedStyle, EnforcedStyleForClasses, EnforcedStyleForModules. +# Configuration parameters: EnforcedStyle. # SupportedStyles: nested, compact -# SupportedStylesForClasses: , nested, compact -# SupportedStylesForModules: , nested, compact Style/ClassAndModuleChildren: Exclude: + - 'app/api/entities/minimal/minimal_unit_entity.rb' + - 'app/api/entities/minimal/minimal_user_entity.rb' - 'app/api/submission/generate_helpers.rb' -# Offense count: 4 -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AllowedMethods, AllowedPatterns. +# Offense count: 12 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowedMethods, AllowedPatterns, IgnoredMethods. # AllowedMethods: ==, equal?, eql? Style/ClassEqualityComparison: Exclude: @@ -477,7 +1022,6 @@ Style/ColonMethodCall: - 'app/helpers/timeout_helper.rb' # Offense count: 1 -# This cop supports unsafe autocorrection (--autocorrect-all). Style/CombinableLoops: Exclude: - 'app/models/unit.rb' @@ -490,17 +1034,18 @@ Style/CommentAnnotation: Exclude: - 'app/api/task_definitions_api.rb' -# Offense count: 20 +# Offense count: 24 # This cop supports unsafe autocorrection (--autocorrect-all). Style/CommentedKeyword: Exclude: - 'app/api/projects_api.rb' + - 'app/api/submission/batch_task_api.rb' - 'app/api/submission/portfolio_api.rb' - 'app/api/submission/portfolio_evidence_api.rb' - 'app/models/unit.rb' - 'config/deakin.rb' -# Offense count: 12 +# Offense count: 13 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions. # SupportedStyles: assign_to_condition, assign_inside_condition @@ -508,28 +1053,30 @@ Style/ConditionalAssignment: Exclude: - 'app/api/submission/portfolio_evidence_api.rb' - 'app/models/comments/extension_comment.rb' + - 'app/models/learning_outcome_task_link.rb' - 'app/models/task.rb' - 'app/models/unit.rb' - 'lib/helpers/database_populator.rb' - 'lib/tasks/maintenance.rake' -# Offense count: 9 +# Offense count: 11 # This cop supports safe autocorrection (--autocorrect). Style/DefWithParentheses: Exclude: - 'app/helpers/file_helper.rb' + - 'app/models/overseer_assessment.rb' - 'app/models/task_definition.rb' - 'app/models/user.rb' - 'config/deakin.rb' -# Offense count: 197 +# Offense count: 120 # Configuration parameters: AllowedConstants. Style/Documentation: Enabled: false # Offense count: 3 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AutoCorrect, EnforcedStyle, AllowComments. +# Configuration parameters: EnforcedStyle, AllowComments. # SupportedStyles: empty, nil, both Style/EmptyElse: Exclude: @@ -558,7 +1105,30 @@ Style/ExplicitBlockArgument: Exclude: - 'app/helpers/timeout_helper.rb' -# Offense count: 18 +# Offense count: 31 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowedVars. +Style/FetchEnvVar: + Exclude: + - 'config/application.rb' + - 'config/deakin.rb' + - 'config/environments/production.rb' + +# Offense count: 3 +# This cop supports safe autocorrection (--autocorrect). +Style/FileRead: + Exclude: + - 'app/helpers/file_helper.rb' + - 'app/models/unit.rb' + - 'lib/tasks/checks.rake' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Style/FileWrite: + Exclude: + - 'lib/tasks/generate_pdfs.rake' + +# Offense count: 19 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: each, for @@ -583,28 +1153,27 @@ Style/FormatString: Exclude: - 'app/models/project.rb' -# Offense count: 7 +# Offense count: 8 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: MaxUnannotatedPlaceholdersAllowed, Mode, AllowedMethods, AllowedPatterns. +# Configuration parameters: MaxUnannotatedPlaceholdersAllowed, AllowedMethods, AllowedPatterns, IgnoredMethods. # SupportedStyles: annotated, template, unannotated -# AllowedMethods: redirect Style/FormatStringToken: EnforcedStyle: template -# Offense count: 251 +# Offense count: 152 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: always, always_true, never Style/FrozenStringLiteralComment: Enabled: false -# Offense count: 1 +# Offense count: 2 # This cop supports unsafe autocorrection (--autocorrect-all). Style/GlobalStdStream: Exclude: - 'lib/tasks/skip_prod.rake' -# Offense count: 70 +# Offense count: 46 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: MinBodyLength, AllowConsecutiveConditionals. Style/GuardClause: @@ -614,7 +1183,7 @@ Style/GuardClause: # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, EnforcedShorthandSyntax, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols. # SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys -# SupportedShorthandSyntax: always, never, either, consistent, either_consistent +# SupportedShorthandSyntax: always, never, either, consistent Style/HashSyntax: Exclude: - 'app/models/campus.rb' @@ -628,20 +1197,30 @@ Style/HashSyntax: Style/IfInsideElse: Exclude: - 'app/api/units_api.rb' + - 'app/models/learning_outcome_task_link.rb' - 'app/models/task.rb' -# Offense count: 500 +# Offense count: 316 # This cop supports safe autocorrection (--autocorrect). Style/IfUnlessModifier: Enabled: false -# Offense count: 3 +# Offense count: 2 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: AllowedMethods. +# AllowedMethods: nonzero? +Style/IfWithBooleanLiteralBranches: + Exclude: + - 'config/application.rb' + +# Offense count: 5 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: InverseMethods, InverseBlocks. Style/InverseMethods: Exclude: - 'app/api/entities/task_entity.rb' - 'app/api/submission/generate_helpers.rb' + - 'app/helpers/file_helper.rb' - 'app/models/unit.rb' # Offense count: 15 @@ -650,26 +1229,29 @@ Style/InverseMethods: # SupportedStyles: line_count_dependent, lambda, literal Style/Lambda: Exclude: + - 'app/api/entities/project_entity.rb' - 'app/api/entities/unit_entity.rb' - 'app/models/project.rb' - 'app/models/unit.rb' - 'config/deakin.rb' -# Offense count: 27 +# Offense count: 25 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowedMethods, AllowedPatterns. +# Configuration parameters: AllowedMethods, AllowedPatterns, IgnoredMethods. Style/MethodCallWithoutArgsParentheses: Exclude: - 'app/api/task_definitions_api.rb' - 'app/models/comments/discussion_comment.rb' + - 'app/models/overseer_assessment.rb' - 'app/models/project.rb' + - 'app/models/task.rb' - 'app/models/task_definition.rb' - 'app/models/unit.rb' - 'config/deakin.rb' - 'lib/helpers/database_populator.rb' - 'lib/tasks/checks.rake' -# Offense count: 10 +# Offense count: 12 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: require_parentheses, require_no_parentheses, require_no_parentheses_except_multiline @@ -680,6 +1262,7 @@ Style/MethodDefParentheses: - 'app/models/group_submission.rb' - 'app/models/task_definition.rb' - 'config/deakin.rb' + - 'lib/helpers/database_populator.rb' # Offense count: 1 Style/MultilineBlockChain: @@ -687,12 +1270,14 @@ Style/MultilineBlockChain: - 'app/models/project.rb' # Offense count: 1 -# This cop supports safe autocorrection (--autocorrect). -Style/MultilineIfModifier: +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: literals, strict +Style/MutableConstant: Exclude: - - 'app/api/staff_grant_extension_api.rb' + - 'app/helpers/grade_helper.rb' -# Offense count: 4 +# Offense count: 6 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: both, prefix, postfix @@ -700,7 +1285,16 @@ Style/NegatedIf: Exclude: - 'app/api/task_definitions_api.rb' - 'app/api/units_api.rb' + - 'app/helpers/file_helper.rb' - 'app/models/task.rb' + - 'app/models/task_definition.rb' + +# Offense count: 3 +# This cop supports safe autocorrection (--autocorrect). +Style/NegatedIfElseCondition: + Exclude: + - 'app/models/project.rb' + - 'app/models/unit.rb' # Offense count: 1 # This cop supports safe autocorrection (--autocorrect). @@ -708,13 +1302,14 @@ Style/NestedTernaryOperator: Exclude: - 'app/models/project.rb' -# Offense count: 6 +# Offense count: 7 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, MinBodyLength, AllowConsecutiveConditionals. +# Configuration parameters: EnforcedStyle, MinBodyLength. # SupportedStyles: skip_modifier_ifs, always Style/Next: Exclude: - 'app/models/teaching_period.rb' + - 'app/models/unit.rb' - 'config/deakin.rb' # Offense count: 2 @@ -740,27 +1335,28 @@ Style/Not: Style/NumericLiterals: MinDigits: 6 -# Offense count: 97 +# Offense count: 90 # This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: EnforcedStyle, AllowedMethods, AllowedPatterns. +# Configuration parameters: EnforcedStyle, AllowedMethods, AllowedPatterns, IgnoredMethods. # SupportedStyles: predicate, comparison Style/NumericPredicate: Enabled: false -# Offense count: 27 +# Offense count: 23 # Configuration parameters: AllowedMethods. # AllowedMethods: respond_to_missing? Style/OptionalBooleanParameter: Exclude: - 'app/helpers/file_helper.rb' - - 'app/mailers/notifications_mailer.rb' - 'app/models/auth_token.rb' - 'app/models/comments/extension_comment.rb' - 'app/models/portfolio_evidence.rb' + - 'app/models/project.rb' - 'app/models/task.rb' - 'app/models/task_definition.rb' + - 'app/models/teaching_period.rb' - 'app/models/unit.rb' - - 'app/services/extension_service.rb' + - 'app/models/user.rb' - 'lib/helpers/faker_randomiser.rb' # Offense count: 2 @@ -769,14 +1365,16 @@ Style/OrAssignment: Exclude: - 'app/models/unit.rb' -# Offense count: 1 +# Offense count: 4 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowSafeAssignment, AllowInMultilineConditions. Style/ParenthesesAroundCondition: Exclude: + - 'app/models/group.rb' + - 'app/models/task_definition.rb' - 'config/application.rb' -# Offense count: 34 +# Offense count: 29 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: PreferredDelimiters. Style/PercentLiteralDelimiters: @@ -784,6 +1382,7 @@ Style/PercentLiteralDelimiters: - 'app/api/task_comments_api.rb' - 'app/helpers/file_helper.rb' - 'app/models/learning_outcome.rb' + - 'app/models/learning_outcome_task_link.rb' - 'app/models/task.rb' - 'app/models/task_definition.rb' - 'app/models/unit.rb' @@ -791,20 +1390,36 @@ Style/PercentLiteralDelimiters: - 'app/models/webcal.rb' - 'config/application.rb' -# Offense count: 16 +# Offense count: 12 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: . # SupportedStyles: same_as_string_literals, single_quotes, double_quotes Style/QuotedSymbols: EnforcedStyle: double_quotes -# Offense count: 3 +# Offense count: 5 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: Methods. +Style/RedundantArgument: + Exclude: + - 'app/helpers/csv_helper.rb' + - 'app/models/unit.rb' + - 'app/models/user.rb' + +# Offense count: 5 # This cop supports safe autocorrection (--autocorrect). Style/RedundantBegin: Exclude: - 'app/helpers/timeout_helper.rb' + - 'app/models/task_definition.rb' - 'app/models/unit.rb' +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Style/RedundantConstantBase: + Exclude: + - 'config.ru' + # Offense count: 2 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: SafeForConstants. @@ -812,7 +1427,13 @@ Style/RedundantFetchBlock: Exclude: - 'config/puma.rb' -# Offense count: 16 +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Style/RedundantFileExtensionInRequire: + Exclude: + - 'lib/tasks/register_q_assessment_results_subscriber.rake' + +# Offense count: 11 # This cop supports unsafe autocorrection (--autocorrect-all). Style/RedundantInterpolation: Exclude: @@ -820,21 +1441,26 @@ Style/RedundantInterpolation: - 'app/models/task_definition.rb' - 'lib/helpers/database_populator.rb' -# Offense count: 3 +# Offense count: 6 # This cop supports safe autocorrection (--autocorrect). Style/RedundantParentheses: Exclude: - 'app/models/project.rb' - 'app/models/task.rb' + - 'app/models/task_definition.rb' - 'config/application.rb' -# Offense count: 8 +# Offense count: 17 # This cop supports safe autocorrection (--autocorrect). Style/RedundantRegexpEscape: Exclude: + - 'app/api/discussion_comment_api.rb' + - 'app/api/task_comments_api.rb' - 'app/helpers/csv_helper.rb' + - 'app/helpers/file_helper.rb' + - 'app/models/project.rb' -# Offense count: 21 +# Offense count: 20 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowMultipleReturnValues. Style/RedundantReturn: @@ -848,7 +1474,7 @@ Style/RedundantReturn: - 'app/models/user.rb' - 'app/models/webcal.rb' -# Offense count: 174 +# Offense count: 88 # This cop supports safe autocorrection (--autocorrect). Style/RedundantSelf: Enabled: false @@ -859,7 +1485,13 @@ Style/RedundantSort: Exclude: - 'app/models/project.rb' -# Offense count: 9 +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +Style/RedundantStringEscape: + Exclude: + - 'app/api/authentication_api.rb' + +# Offense count: 13 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, AllowInnerSlashes. # SupportedStyles: slashes, percent_r, mixed @@ -871,7 +1503,10 @@ Style/RegexpLiteral: - 'app/controllers/task_submission_pdfs_controller.rb' - 'app/helpers/csv_helper.rb' - 'app/helpers/file_helper.rb' + - 'app/models/project.rb' - 'app/models/task.rb' + - 'app/models/task_definition.rb' + - 'app/models/unit.rb' # Offense count: 1 # This cop supports safe autocorrection (--autocorrect). @@ -879,16 +1514,14 @@ Style/RescueModifier: Exclude: - 'app/models/unit.rb' -# Offense count: 22 +# Offense count: 27 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: implicit, explicit Style/RescueStandardError: Exclude: - - 'app/api/staff_grant_extension_api.rb' - 'app/helpers/file_helper.rb' - 'app/helpers/timeout_helper.rb' - - 'app/mailers/notifications_mailer.rb' - 'app/models/group_submission.rb' - 'app/models/portfolio_evidence.rb' - 'app/models/project.rb' @@ -899,12 +1532,13 @@ Style/RescueStandardError: - 'lib/tasks/checks.rake' - 'lib/tasks/maintenance.rake' -# Offense count: 41 +# Offense count: 29 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength. # AllowedMethods: present?, blank?, presence, try, try! Style/SafeNavigation: Exclude: + - 'app/api/entities/minimal/minimal_unit_entity.rb' - 'app/api/entities/task_definition_entity.rb' - 'app/api/entities/tutorial_entity.rb' - 'app/api/entities/unit_entity.rb' @@ -914,6 +1548,13 @@ Style/SafeNavigation: - 'app/models/tutorial.rb' - 'app/models/unit.rb' - 'app/models/user.rb' + - 'lib/assets/ontrack_receive_action.rb' + +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). +Style/SelectByRegexp: + Exclude: + - 'app/helpers/file_helper.rb' # Offense count: 3 # This cop supports safe autocorrection (--autocorrect). @@ -921,29 +1562,30 @@ Style/SelfAssignment: Exclude: - 'app/models/unit.rb' -# Offense count: 2 +# Offense count: 4 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowAsExpressionSeparator. Style/Semicolon: Exclude: + - 'app/models/unit.rb' - 'lib/helpers/database_populator.rb' - 'lib/tasks/generate_pdfs.rake' -# Offense count: 3 +# Offense count: 2 # This cop supports unsafe autocorrection (--autocorrect-all). Style/SlicingWithRange: Exclude: - 'app/models/task.rb' -# Offense count: 9 +# Offense count: 8 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowModifier. Style/SoleNestedConditional: Exclude: - 'app/api/group_sets_api.rb' + - 'app/api/task_definitions_api.rb' - 'app/models/group.rb' - 'app/models/task.rb' - - 'app/services/extension_service.rb' - 'config/deakin.rb' # Offense count: 10 @@ -955,7 +1597,7 @@ Style/StringConcatenation: - 'app/models/task.rb' - 'app/models/unit.rb' -# Offense count: 627 +# Offense count: 349 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. # SupportedStyles: single_quotes, double_quotes @@ -973,19 +1615,20 @@ Style/StringLiteralsInInterpolation: - 'config/deakin.rb' - 'lib/helpers/database_populator.rb' -# Offense count: 72 +# Offense count: 41 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, MinSize. # SupportedStyles: percent, brackets Style/SymbolArray: Enabled: false -# Offense count: 4 +# Offense count: 5 # This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AllowMethodsWithArguments, AllowedMethods, AllowedPatterns, AllowComments. -# AllowedMethods: define_method, mail, respond_to +# Configuration parameters: AllowMethodsWithArguments, AllowedMethods, AllowedPatterns, IgnoredMethods, AllowComments. +# AllowedMethods: define_method Style/SymbolProc: Exclude: + - 'app/models/teaching_period.rb' - 'app/models/unit.rb' # Offense count: 2 @@ -997,7 +1640,7 @@ Style/TernaryParentheses: - 'app/models/project.rb' - 'app/models/tutorial_stream.rb' -# Offense count: 7 +# Offense count: 5 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyleForMultiline. # SupportedStylesForMultiline: comma, consistent_comma, no_comma @@ -1007,20 +1650,20 @@ Style/TrailingCommaInArguments: - 'app/api/units_api.rb' - 'app/models/unit.rb' -# Offense count: 9 +# Offense count: 6 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyleForMultiline. -# SupportedStylesForMultiline: comma, consistent_comma, diff_comma, no_comma +# SupportedStylesForMultiline: comma, consistent_comma, no_comma Style/TrailingCommaInArrayLiteral: Exclude: - 'app/models/task.rb' - 'app/models/unit.rb' - 'lib/helpers/database_populator.rb' -# Offense count: 10 +# Offense count: 7 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyleForMultiline. -# SupportedStylesForMultiline: comma, consistent_comma, diff_comma, no_comma +# SupportedStylesForMultiline: comma, consistent_comma, no_comma Style/TrailingCommaInHashLiteral: Exclude: - 'app/models/comments/task_comment.rb' @@ -1041,9 +1684,15 @@ Style/WordArray: - 'config/deakin.rb' - 'lib/helpers/database_populator.rb' -# Offense count: 594 +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). +Style/ZeroLengthPredicate: + Exclude: + - 'app/models/unit.rb' + +# Offense count: 583 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings. +# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, IgnoredPatterns. # URISchemes: http, https Layout/LineLength: Max: 369 diff --git a/Gemfile.lock b/Gemfile.lock index 68cf502eb6..1149766c40 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -98,7 +98,6 @@ GEM bunny-pub-sub (0.5.2) bunny (~> 2.14) byebug (12.0.0) - cgi (0.4.2) chronic_duration (0.10.6) numerizer (~> 0.1.1) ci_reporter (2.1.0) @@ -153,8 +152,6 @@ GEM dry-inflector (~> 1.0) dry-logic (~> 1.4) zeitwerk (~> 2.6) - erb (4.0.4) - cgi (>= 0.3.3) erubi (1.13.1) erubis (2.7.0) et-orbi (1.2.11) @@ -176,6 +173,7 @@ GEM faraday (>= 1, < 3) faraday-net_http (3.4.0) net-http (>= 0.5.0) + ffi (1.17.1-aarch64-linux-gnu) ffi (1.17.1-x86_64-linux-gnu) fugit (1.11.1) et-orbi (~> 1, >= 1.2.11) @@ -210,7 +208,7 @@ GEM ice_cube (~> 0.16) ostruct ice_cube (0.17.0) - io-console (0.8.1) + io-console (0.8.0) irb (1.15.1) pp (>= 0.6.0) rdoc (>= 4.0.0) @@ -281,6 +279,8 @@ GEM net-protocol netrc (0.11.0) nio4r (2.7.4) + nokogiri (1.18.7-aarch64-linux-gnu) + racc (~> 1.4) nokogiri (1.18.7-x86_64-linux-gnu) racc (~> 1.4) numerizer (0.1.1) @@ -308,7 +308,7 @@ GEM pp (0.6.2) prettyprint prettyprint (0.2.0) - prism (1.5.1) + prism (1.4.0) psych (5.2.3) date stringio @@ -374,8 +374,7 @@ GEM rbs (3.9.2) logger rbtree (0.4.6) - rdoc (6.14.0) - erb + rdoc (6.13.1) psych (>= 4.0.0) redis (5.4.0) redis-client (>= 0.22.0) @@ -538,7 +537,7 @@ GEM unicode-display_width (3.1.4) unicode-emoji (~> 4.0, >= 4.0.4) unicode-emoji (4.0.4) - uri (1.0.4) + uri (1.0.3) useragent (0.16.11) version_gem (1.1.6) warden (1.2.9) @@ -557,7 +556,8 @@ GEM zeitwerk (2.7.2) PLATFORMS - x86_64-linux-gnu + aarch64-linux + x86_64-linux DEPENDENCIES better_errors @@ -623,7 +623,7 @@ DEPENDENCIES webmock RUBY VERSION - ruby 3.4.7p58 + ruby 3.4.2p28 BUNDLED WITH - 2.6.9 + 2.6.6 diff --git a/Rakefile b/Rakefile index f36b80f286..350ebd4984 100644 --- a/Rakefile +++ b/Rakefile @@ -1,3 +1,4 @@ +#!/usr/bin/env rake # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. diff --git a/app/api/api_root.rb b/app/api/api_root.rb index 69690f2e73..db00a8a7d6 100644 --- a/app/api/api_root.rb +++ b/app/api/api_root.rb @@ -63,8 +63,6 @@ class ApiRoot < Grape::API mount ScormExtensionCommentsApi mount GroupSetsApi mount LearningOutcomesApi - # mount LearningAlignmentApi - # the mount above is available in 9.x but has not been ported to `10.0.x` mount NotificationsApi mount ProjectsApi mount SettingsApi diff --git a/app/mailers/notifications_mailer.rb b/app/mailers/notifications_mailer.rb index 5c810fd80d..75180be4d8 100644 --- a/app/mailers/notifications_mailer.rb +++ b/app/mailers/notifications_mailer.rb @@ -2,11 +2,15 @@ class NotificationsMailer < ApplicationMailer # Load configuration values at class level def self.doubtfire_host - Doubtfire::Application.config.institution[:host] || 'doubtfire.deakin.edu.au' + Doubtfire::Application.config.institution[:host] end def self.doubtfire_product_name - Doubtfire::Application.config.institution[:product_name] || 'Doubtfire' + Doubtfire::Application.config.institution[:product_name] + end + + def self.doubtfire_email_domain + Doubtfire::Application.config.institution[:email_domain] end def add_general @@ -131,7 +135,7 @@ def extension_granted_summary(extensions, granted_by, total_selected, failed_ext email_with_name = %("#{@granted_by.name}" <#{@granted_by.email}>) # Set explicit from address using product name and a default sender - from_address = %("#{self.class.doubtfire_product_name}" ) + from_address = %("#{self.class.doubtfire_product_name}" ) mail( to: email_with_name, diff --git a/app/views/notifications_mailer/extension_granted.html.erb b/app/views/notifications_mailer/extension_granted.html.erb index d3eb9a397d..3595200f45 100644 --- a/app/views/notifications_mailer/extension_granted.html.erb +++ b/app/views/notifications_mailer/extension_granted.html.erb @@ -118,7 +118,7 @@ diff --git a/db/schema.rb b/db/schema.rb index 56f4009278..c25bf092dd 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -12,9 +12,9 @@ ActiveRecord::Schema[8.0].define(version: 2025_12_18_031455) do create_table "activity_types", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + t.string "name", null: false t.string "abbreviation", null: false t.datetime "created_at", null: false - t.string "name", null: false t.datetime "updated_at", null: false t.index ["abbreviation"], name: "index_activity_types_on_abbreviation", unique: true t.index ["name"], name: "index_activity_types_on_name", unique: true @@ -22,152 +22,152 @@ create_table "auth_tokens", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.datetime "auth_token_expiry", null: false + t.bigint "user_id" t.string "authentication_token", null: false - t.datetime "created_at", null: false t.integer "token_type", default: 0, null: false + t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.bigint "user_id" t.index ["token_type"], name: "index_auth_tokens_on_token_type" t.index ["user_id"], name: "index_auth_tokens_on_user_id" end create_table "breaks", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "created_at", null: false - t.integer "number_of_weeks", null: false t.datetime "start_date", null: false + t.integer "number_of_weeks", null: false t.bigint "teaching_period_id" + t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["teaching_period_id"], name: "index_breaks_on_teaching_period_id" end create_table "campuses", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + t.string "name", null: false + t.integer "mode", null: false t.string "abbreviation", null: false t.boolean "active", null: false t.datetime "created_at", null: false - t.integer "mode", null: false - t.string "name", null: false - t.string "timezone" t.datetime "updated_at", null: false + t.string "timezone" t.index ["abbreviation"], name: "index_campuses_on_abbreviation", unique: true t.index ["active"], name: "index_campuses_on_active" t.index ["name"], name: "index_campuses_on_name", unique: true end create_table "chip_usages", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "created_at", null: false t.bigint "feedback_chip_id", null: false t.bigint "tutor_id", null: false - t.datetime "updated_at", null: false t.integer "usage_count", default: 0, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.index ["feedback_chip_id"], name: "index_chip_usages_on_feedback_chip_id" t.index ["tutor_id"], name: "index_chip_usages_on_tutor_id" end create_table "comments_read_receipts", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "created_at", null: false t.bigint "task_comment_id", null: false - t.datetime "updated_at", null: false t.bigint "user_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.index ["task_comment_id", "user_id"], name: "index_comments_read_receipts_on_task_comment_id_and_user_id", unique: true t.index ["task_comment_id"], name: "index_comments_read_receipts_on_task_comment_id" t.index ["user_id"], name: "index_comments_read_receipts_on_user_id" end create_table "d2l_assessment_mappings", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "created_at", null: false - t.integer "grade_object_id" - t.string "org_unit_id" t.bigint "unit_id", null: false + t.string "org_unit_id" + t.integer "grade_object_id" + t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["unit_id"], name: "index_d2l_assessment_mappings_on_unit_id", unique: true end create_table "discussion_comments", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "created_at", null: false - t.integer "number_of_prompts" - t.datetime "time_completed" t.datetime "time_started" + t.datetime "time_completed" + t.integer "number_of_prompts" + t.datetime "created_at", null: false t.datetime "updated_at", null: false end create_table "discussion_prompts", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + t.bigint "task_definition_id", null: false t.text "content", null: false - t.datetime "created_at", null: false t.integer "priority", default: 0 - t.bigint "task_definition_id", null: false + t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["task_definition_id"], name: "index_discussion_prompts_on_task_definition_id" end create_table "feedback_chips", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + t.string "type" t.text "chip_text" - t.text "comment_text" - t.datetime "created_at", null: false t.text "description" + t.text "comment_text" + t.text "summary_text" t.bigint "learning_outcome_id", null: false t.bigint "parent_chip_id" - t.text "summary_text" - t.string "task_status" - t.string "type" + t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "task_status" t.index ["learning_outcome_id"], name: "index_feedback_chips_on_learning_outcome_id" t.index ["parent_chip_id"], name: "index_feedback_chips_on_parent_chip_id" end create_table "group_memberships", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.boolean "active", default: true - t.datetime "created_at" t.bigint "group_id" t.bigint "project_id" + t.boolean "active", default: true + t.datetime "created_at" t.datetime "updated_at" t.index ["group_id"], name: "index_group_memberships_on_group_id" t.index ["project_id"], name: "index_group_memberships_on_project_id" end create_table "group_sets", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + t.bigint "unit_id" + t.string "name" t.boolean "allow_students_to_create_groups", default: true t.boolean "allow_students_to_manage_groups", default: true - t.integer "capacity" - t.datetime "created_at" t.boolean "keep_groups_in_same_class", default: false - t.boolean "locked", default: false, null: false - t.string "name" - t.bigint "unit_id" + t.datetime "created_at" t.datetime "updated_at" + t.integer "capacity" + t.boolean "locked", default: false, null: false t.index ["name", "unit_id"], name: "index_group_sets_on_name_and_unit_id", unique: true t.index ["unit_id"], name: "index_group_sets_on_unit_id" end create_table "group_submissions", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "created_at" t.bigint "group_id" t.string "notes" t.bigint "submitted_by_project_id" - t.bigint "task_definition_id" + t.datetime "created_at" t.datetime "updated_at" + t.bigint "task_definition_id" t.index ["group_id"], name: "index_group_submissions_on_group_id" t.index ["submitted_by_project_id"], name: "index_group_submissions_on_submitted_by_project_id" t.index ["task_definition_id"], name: "index_group_submissions_on_task_definition_id" end create_table "groups", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.integer "capacity_adjustment", default: 0, null: false - t.datetime "created_at" t.bigint "group_set_id" - t.boolean "locked", default: false, null: false - t.string "name" t.bigint "tutorial_id" + t.string "name" + t.datetime "created_at" t.datetime "updated_at" + t.integer "capacity_adjustment", default: 0, null: false + t.boolean "locked", default: false, null: false t.index ["group_set_id"], name: "index_groups_on_group_set_id" t.index ["name", "group_set_id"], name: "index_groups_on_name_and_group_set_id", unique: true t.index ["tutorial_id"], name: "index_groups_on_tutorial_id" end create_table "learning_outcome_links", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "created_at", null: false - t.string "link_type" t.bigint "source_id", null: false t.bigint "target_id", null: false + t.string "link_type" + t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["source_id", "target_id"], name: "index_learning_outcome_links_on_source_id_and_target_id", unique: true t.index ["source_id"], name: "index_learning_outcome_links_on_source_id" @@ -175,52 +175,52 @@ end create_table "learning_outcomes", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + t.string "short_description" + t.string "full_outcome_description", limit: 4096 t.string "abbreviation" t.bigint "context_id" t.string "context_type" t.datetime "created_at", null: false - t.string "full_outcome_description", limit: 4096 - t.string "short_description" t.datetime "updated_at", null: false t.index ["abbreviation", "context_type", "context_id"], name: "index_learning_outcomes_on_abbreviation_and_context", unique: true t.index ["context_id", "context_type"], name: "index_learning_outcomes_on_context_id_and_context_type" end create_table "logins", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "created_at", null: false t.datetime "timestamp" - t.datetime "updated_at", null: false t.bigint "user_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.index ["user_id"], name: "index_logins_on_user_id" end create_table "marking_sessions", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "created_at", null: false - t.boolean "during_tutorial" - t.datetime "end_time" + t.bigint "user_id", null: false + t.bigint "unit_id", null: false t.string "ip_address" t.datetime "start_time" - t.bigint "unit_id", null: false + t.datetime "end_time" + t.boolean "during_tutorial" + t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.bigint "user_id", null: false t.index ["unit_id"], name: "index_marking_sessions_on_unit_id" t.index ["user_id", "unit_id", "ip_address", "updated_at"], name: "index_marking_sessions_on_user_unit_ip_and_time" t.index ["user_id"], name: "index_marking_sessions_on_user_id" end create_table "notifications", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "created_at", null: false + t.integer "user_id" t.string "message" + t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.integer "user_id" end create_table "overseer_assessments", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "created_at", null: false + t.bigint "task_id", null: false + t.string "submission_timestamp", null: false t.string "result_task_status" t.integer "status", default: 0, null: false - t.string "submission_timestamp", null: false - t.bigint "task_id", null: false + t.datetime "created_at", null: false t.datetime "updated_at", null: false t.integer "total_steps" t.index ["task_id", "submission_timestamp"], name: "index_overseer_assessments_on_task_id_and_submission_timestamp", unique: true @@ -228,13 +228,13 @@ end create_table "overseer_images", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "created_at", null: false - t.datetime "last_pulled_date" t.string "name", null: false - t.integer "pulled_image_status" - t.text "pulled_image_text" t.string "tag", null: false + t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.text "pulled_image_text" + t.integer "pulled_image_status" + t.datetime "last_pulled_date" t.index ["name"], name: "index_overseer_images_on_name", unique: true t.index ["tag"], name: "index_overseer_images_on_tag", unique: true end @@ -287,28 +287,28 @@ end create_table "projects", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.bigint "assessor_id" - t.bigint "campus_id" - t.boolean "compile_portfolio", default: false + t.bigint "unit_id" + t.string "project_role" t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "started" + t.string "progress" + t.string "status" + t.string "task_stats" t.boolean "enrolled", default: true + t.integer "target_grade", default: 0 + t.boolean "compile_portfolio", default: false + t.date "portfolio_production_date" + t.bigint "user_id" t.integer "grade", default: 0 t.string "grade_rationale", limit: 4096 + t.bigint "campus_id" + t.integer "submitted_grade" + t.boolean "uses_draft_learning_summary", default: false, null: false t.boolean "portfolio_auto_generated", default: false, null: false t.integer "portfolio_generation_pid" - t.date "portfolio_production_date" - t.string "progress" - t.string "project_role" t.integer "spec_con_days", default: 0, null: false - t.boolean "started" - t.string "status" - t.integer "submitted_grade" - t.integer "target_grade", default: 0 - t.string "task_stats" - t.bigint "unit_id" - t.datetime "updated_at", null: false - t.bigint "user_id" - t.boolean "uses_draft_learning_summary", default: false, null: false + t.bigint "assessor_id" t.index ["assessor_id"], name: "index_projects_on_assessor_id" t.index ["campus_id"], name: "index_projects_on_campus_id" t.index ["enrolled"], name: "index_projects_on_enrolled" @@ -318,19 +318,19 @@ end create_table "roles", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "created_at", null: false - t.text "description" t.string "name" + t.text "description" + t.datetime "created_at", null: false t.datetime "updated_at", null: false end create_table "session_activities", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.string "action" - t.datetime "created_at", null: false t.bigint "marking_session_id", null: false + t.string "action" t.bigint "project_id" - t.bigint "task_definition_id" t.bigint "task_id" + t.bigint "task_definition_id" + t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["action", "task_id", "created_at"], name: "index_session_activities_on_action_task_created_at" t.index ["marking_session_id"], name: "index_session_activities_on_marking_session_id" @@ -340,13 +340,13 @@ end create_table "staff_notes", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "created_at", null: false t.text "note" t.bigint "project_id", null: false - t.bigint "reply_to_id" + t.bigint "user_id", null: false t.bigint "staff_notes_id" + t.bigint "reply_to_id" + t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.bigint "user_id", null: false t.index ["project_id"], name: "index_staff_notes_on_project_id" t.index ["reply_to_id"], name: "index_staff_notes_on_reply_to_id" t.index ["staff_notes_id"], name: "index_staff_notes_on_staff_notes_id" @@ -354,27 +354,27 @@ end create_table "task_comments", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.bigint "assessor_id" - t.string "attachment_extension" + t.bigint "task_id", null: false + t.bigint "user_id", null: false t.string "comment", limit: 4096 - t.bigint "commentable_id" - t.string "commentable_type" - t.string "content_type" t.datetime "created_at", null: false - t.datetime "date_extension_assessed" + t.bigint "recipient_id" + t.string "content_type" + t.string "attachment_extension" t.bigint "discussion_comment_id" + t.string "type" + t.datetime "time_discussion_started" + t.datetime "time_discussion_completed" + t.integer "number_of_prompts" + t.datetime "date_extension_assessed" t.boolean "extension_granted" - t.string "extension_response" + t.bigint "assessor_id" + t.bigint "task_status_id" t.integer "extension_weeks" - t.integer "number_of_prompts" - t.bigint "recipient_id" + t.string "extension_response" t.bigint "reply_to_id" - t.bigint "task_id", null: false - t.bigint "task_status_id" - t.datetime "time_discussion_completed" - t.datetime "time_discussion_started" - t.string "type" - t.bigint "user_id", null: false + t.bigint "commentable_id" + t.string "commentable_type" t.index ["assessor_id"], name: "index_task_comments_on_assessor_id" t.index ["commentable_type", "commentable_id"], name: "index_task_comments_on_commentable_type_and_commentable_id" t.index ["discussion_comment_id"], name: "index_task_comments_on_discussion_comment_id" @@ -386,38 +386,38 @@ end create_table "task_definitions", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.string "abbreviation" - t.boolean "assess_in_portfolio_only", default: false, null: false - t.boolean "assessment_enabled", default: false - t.datetime "created_at", null: false + t.bigint "unit_id" + t.string "name" t.string "description", limit: 4096 - t.datetime "due_date" + t.decimal "weighting", precision: 10 + t.datetime "target_date", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "abbreviation" + t.string "upload_requirements", limit: 4096 + t.integer "target_grade", default: 0 + t.boolean "restrict_status_updates", default: false + t.string "plagiarism_report_url" + t.boolean "plagiarism_updated", default: false + t.integer "plagiarism_warn_pct", default: 50 t.bigint "group_set_id" + t.datetime "due_date" + t.datetime "start_date", null: false t.boolean "is_graded", default: false - t.boolean "lock_assessments_to_tutorial_stream", default: false, null: false t.integer "max_quality_pts", default: 0 - t.string "name" + t.bigint "tutorial_stream_id" + t.boolean "assessment_enabled", default: false t.bigint "overseer_image_id" - t.string "plagiarism_report_url" - t.boolean "plagiarism_updated", default: false - t.integer "plagiarism_warn_pct", default: 50 - t.boolean "restrict_status_updates", default: false + t.string "tii_group_id" + t.string "similarity_language" + t.boolean "scorm_enabled", default: false t.boolean "scorm_allow_review", default: false - t.integer "scorm_attempt_limit", default: 0 t.boolean "scorm_bypass_test", default: false - t.boolean "scorm_enabled", default: false t.boolean "scorm_time_delay_enabled", default: false - t.string "similarity_language" - t.datetime "start_date", null: false - t.datetime "target_date", null: false - t.integer "target_grade", default: 0 - t.string "tii_group_id" - t.bigint "tutorial_stream_id" - t.bigint "unit_id" - t.datetime "updated_at", null: false - t.string "upload_requirements", limit: 4096 + t.integer "scorm_attempt_limit", default: 0 + t.boolean "assess_in_portfolio_only", default: false, null: false t.boolean "use_resources_for_jplag_base_code", default: false, null: false - t.decimal "weighting", precision: 10 + t.boolean "lock_assessments_to_tutorial_stream", default: false, null: false t.index ["abbreviation", "unit_id"], name: "index_task_definitions_on_abbreviation_and_unit_id", unique: true t.index ["group_set_id"], name: "index_task_definitions_on_group_set_id" t.index ["name", "unit_id"], name: "index_task_definitions_on_name_and_unit_id", unique: true @@ -427,29 +427,29 @@ end create_table "task_engagements", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "created_at", null: false - t.string "engagement" t.datetime "engagement_time" + t.string "engagement" t.bigint "task_id" + t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["task_id"], name: "index_task_engagements_on_task_id" end create_table "task_pins", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "created_at", null: false t.bigint "task_id", null: false - t.datetime "updated_at", null: false t.bigint "user_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.index ["task_id", "user_id"], name: "index_task_pins_on_task_id_and_user_id", unique: true t.index ["task_id"], name: "index_task_pins_on_task_id" t.index ["user_id"], name: "fk_rails_915df186ed" end create_table "task_prerequisites", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "created_at", null: false - t.bigint "prerequisite_id", null: false t.bigint "task_definition_id", null: false + t.bigint "prerequisite_id", null: false t.bigint "task_status_id", null: false + t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["prerequisite_id"], name: "index_task_prerequisites_on_prerequisite_id" t.index ["task_definition_id", "prerequisite_id"], name: "idx_on_task_definition_id_prerequisite_id_90b47ca126", unique: true @@ -458,59 +458,59 @@ end create_table "task_similarities", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "created_at" - t.boolean "flagged", default: false + t.bigint "task_id" t.bigint "other_task_id" t.integer "pct" + t.datetime "created_at" + t.datetime "updated_at" t.string "plagiarism_report_url" - t.bigint "task_id" - t.bigint "tii_submission_id" + t.boolean "flagged", default: false t.string "type" - t.datetime "updated_at" + t.bigint "tii_submission_id" t.index ["other_task_id"], name: "index_task_similarities_on_other_task_id" t.index ["task_id"], name: "index_task_similarities_on_task_id" t.index ["tii_submission_id"], name: "index_task_similarities_on_tii_submission_id" end create_table "task_statuses", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "created_at", null: false - t.string "description" t.string "name" + t.string "description" + t.datetime "created_at", null: false t.datetime "updated_at", null: false end create_table "task_submissions", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + t.datetime "submission_time" t.datetime "assessment_time" - t.bigint "assessor_id" - t.datetime "created_at", null: false t.string "outcome" - t.datetime "submission_time" t.bigint "task_id" + t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.bigint "assessor_id" t.index ["assessor_id"], name: "index_task_submissions_on_assessor_id" t.index ["task_id"], name: "index_task_submissions_on_task_id" end create_table "tasks", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "assessment_date" - t.date "completion_date" - t.integer "contribution_pct", default: 100 - t.integer "contribution_pts", default: 3 + t.bigint "task_definition_id" + t.bigint "project_id" + t.bigint "task_status_id" t.datetime "created_at", null: false - t.integer "extensions", default: 0, null: false + t.datetime "updated_at", null: false + t.date "completion_date" + t.string "portfolio_evidence" + t.boolean "include_in_portfolio", default: true t.datetime "file_uploaded_at" - t.integer "grade" t.bigint "group_submission_id" - t.boolean "include_in_portfolio", default: true - t.string "portfolio_evidence" - t.bigint "project_id" + t.integer "contribution_pct", default: 100 + t.integer "times_assessed", default: 0 + t.datetime "submission_date" + t.datetime "assessment_date" + t.integer "grade" + t.integer "contribution_pts", default: 3 t.integer "quality_pts", default: -1 + t.integer "extensions", default: 0, null: false t.integer "scorm_extensions", default: 0, null: false - t.datetime "submission_date" - t.bigint "task_definition_id" - t.bigint "task_status_id" - t.integer "times_assessed", default: 0 - t.datetime "updated_at", null: false t.datetime "target_start_date" t.datetime "target_due_date" t.index ["group_submission_id"], name: "index_tasks_on_group_submission_id" @@ -521,43 +521,43 @@ end create_table "teaching_periods", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "active_until", null: false - t.datetime "created_at", null: false - t.datetime "end_date", null: false t.string "period", null: false t.datetime "start_date", null: false - t.datetime "updated_at", null: false + t.datetime "end_date", null: false t.integer "year", null: false + t.datetime "active_until", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.index ["period", "year"], name: "index_teaching_periods_on_period_and_year", unique: true end create_table "test_attempts", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + t.bigint "task_id" t.datetime "attempted_time", null: false - t.text "cmi_datamodel" + t.boolean "terminated", default: false t.boolean "completion_status", default: false - t.datetime "created_at", null: false - t.float "score_scaled", default: 0.0 t.boolean "success_status", default: false - t.bigint "task_id" - t.boolean "terminated", default: false + t.float "score_scaled", default: 0.0 + t.text "cmi_datamodel" + t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["task_id"], name: "index_test_attempts_on_task_id" end create_table "tii_actions", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + t.string "entity_type" + t.bigint "entity_id" + t.string "type" t.boolean "complete", default: false, null: false + t.integer "retries", default: 0, null: false + t.datetime "last_run" t.datetime "complete_at" - t.datetime "created_at", null: false - t.text "custom_error_message" - t.bigint "entity_id" - t.string "entity_type" + t.boolean "retry", default: true, null: false t.integer "error_code" - t.datetime "last_run" + t.text "custom_error_message" t.text "log" t.string "params", limit: 1024, default: "{}" - t.integer "retries", default: 0, null: false - t.boolean "retry", default: true, null: false - t.string "type" + t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["complete"], name: "index_tii_actions_on_complete" t.index ["entity_type", "entity_id"], name: "index_tii_actions_on_entity" @@ -565,29 +565,29 @@ end create_table "tii_group_attachments", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "created_at", null: false - t.string "file_sha1_digest" + t.bigint "task_definition_id", null: false t.string "filename", null: false t.string "group_attachment_id" + t.string "file_sha1_digest" t.integer "status", default: 0, null: false - t.bigint "task_definition_id", null: false + t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["task_definition_id"], name: "index_tii_group_attachments_on_task_definition_id" end create_table "tii_submissions", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "created_at", null: false + t.bigint "task_id", null: false + t.bigint "tii_task_similarity_id" + t.bigint "submitted_by_user_id", null: false t.string "filename", null: false t.integer "idx", null: false - t.integer "overall_match_percentage" + t.string "submission_id" t.string "similarity_pdf_id" + t.datetime "submitted_at" t.datetime "similarity_request_at" t.integer "status", default: 0, null: false - t.string "submission_id" - t.datetime "submitted_at" - t.bigint "submitted_by_user_id", null: false - t.bigint "task_id", null: false - t.bigint "tii_task_similarity_id" + t.integer "overall_match_percentage" + t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["submitted_by_user_id"], name: "index_tii_submissions_on_submitted_by_user_id" t.index ["task_id"], name: "index_tii_submissions_on_task_id" @@ -596,21 +596,21 @@ create_table "tutorial_enrolments", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.bigint "project_id", null: false t.bigint "tutorial_id", null: false - t.datetime "updated_at", null: false t.index ["project_id"], name: "index_tutorial_enrolments_on_project_id" t.index ["tutorial_id", "project_id"], name: "index_tutorial_enrolments_on_tutorial_id_and_project_id", unique: true t.index ["tutorial_id"], name: "index_tutorial_enrolments_on_tutorial_id" end create_table "tutorial_streams", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + t.string "name", null: false t.string "abbreviation", null: false - t.bigint "activity_type_id", null: false t.datetime "created_at", null: false - t.string "name", null: false - t.bigint "unit_id", null: false t.datetime "updated_at", null: false + t.bigint "activity_type_id", null: false + t.bigint "unit_id", null: false t.index ["abbreviation", "unit_id"], name: "index_tutorial_streams_on_abbreviation_and_unit_id", unique: true t.index ["abbreviation"], name: "index_tutorial_streams_on_abbreviation" t.index ["activity_type_id"], name: "fk_rails_14ef80da76" @@ -619,18 +619,18 @@ end create_table "tutorials", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.string "abbreviation" - t.bigint "campus_id" - t.integer "capacity", default: -1 - t.string "code" - t.datetime "created_at", null: false + t.bigint "unit_id" t.string "meeting_day" - t.string "meeting_location" t.string "meeting_time" - t.bigint "tutorial_stream_id" - t.bigint "unit_id" - t.bigint "unit_role_id" + t.string "meeting_location" + t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "code" + t.bigint "unit_role_id" + t.string "abbreviation" + t.integer "capacity", default: -1 + t.bigint "campus_id" + t.bigint "tutorial_stream_id" t.index ["abbreviation", "unit_id"], name: "index_tutorials_on_abbreviation_and_unit_id", unique: true t.index ["campus_id"], name: "index_tutorials_on_campus_id" t.index ["tutorial_stream_id"], name: "index_tutorials_on_tutorial_stream_id" @@ -639,13 +639,13 @@ end create_table "unit_roles", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + t.bigint "user_id" + t.bigint "tutorial_id" t.datetime "created_at", null: false - t.boolean "observer_only", default: false + t.datetime "updated_at", null: false t.bigint "role_id" - t.bigint "tutorial_id" t.bigint "unit_id" - t.datetime "updated_at", null: false - t.bigint "user_id" + t.boolean "observer_only", default: false t.index ["role_id"], name: "index_unit_roles_on_role_id" t.index ["tutorial_id"], name: "index_unit_roles_on_tutorial_id" t.index ["unit_id"], name: "index_unit_roles_on_unit_id" @@ -653,33 +653,33 @@ end create_table "units", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.boolean "active", default: true - t.boolean "allow_flexible_dates", default: false, null: false - t.boolean "allow_student_change_tutorial", default: true, null: false - t.boolean "allow_student_extension_requests", default: true, null: false - t.boolean "archived", default: false - t.boolean "assessment_enabled", default: true - t.boolean "auto_apply_extension_before_deadline", default: true, null: false - t.string "code" - t.datetime "created_at", null: false + t.string "name" t.string "description", limit: 4096 - t.bigint "draft_task_definition_id" - t.boolean "enable_sync_enrolments", default: true, null: false - t.boolean "enable_sync_timetable", default: true, null: false + t.datetime "start_date" t.datetime "end_date" - t.integer "extension_weeks_on_resubmit_request", default: 1, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "code" + t.boolean "active", default: true t.datetime "last_plagarism_scan" + t.bigint "teaching_period_id" t.bigint "main_convenor_id" - t.boolean "mark_late_submissions_as_assess_in_portfolio", default: false, null: false - t.string "name" + t.boolean "auto_apply_extension_before_deadline", default: true, null: false + t.boolean "send_notifications", default: true, null: false + t.boolean "enable_sync_timetable", default: true, null: false + t.boolean "enable_sync_enrolments", default: true, null: false + t.bigint "draft_task_definition_id" + t.boolean "allow_student_extension_requests", default: true, null: false + t.integer "extension_weeks_on_resubmit_request", default: 1, null: false + t.boolean "allow_student_change_tutorial", default: true, null: false + t.boolean "assessment_enabled", default: true t.bigint "overseer_image_id" t.datetime "portfolio_auto_generation_date" - t.datetime "portfolio_due_date" - t.boolean "send_notifications", default: true, null: false - t.datetime "start_date" - t.bigint "teaching_period_id" t.string "tii_group_context_id" - t.datetime "updated_at", null: false + t.boolean "archived", default: false + t.boolean "allow_flexible_dates", default: false, null: false + t.datetime "portfolio_due_date" + t.boolean "mark_late_submissions_as_assess_in_portfolio", default: false, null: false t.index ["draft_task_definition_id"], name: "index_units_on_draft_task_definition_id" t.index ["main_convenor_id"], name: "index_units_on_main_convenor_id" t.index ["overseer_image_id"], name: "index_units_on_overseer_image_id" @@ -687,53 +687,53 @@ end create_table "user_oauth_states", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "created_at", null: false + t.bigint "user_id", null: false t.string "state" + t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.bigint "user_id", null: false t.index ["state"], name: "index_user_oauth_states_on_state", unique: true t.index ["user_id"], name: "index_user_oauth_states_on_user_id" end create_table "user_oauth_tokens", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "created_at", null: false - t.datetime "expires_at" + t.bigint "user_id", null: false t.integer "provider", default: 0, null: false t.text "token" + t.datetime "expires_at" + t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.bigint "user_id", null: false t.index ["user_id"], name: "index_user_oauth_tokens_on_user_id" end create_table "users", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "created_at", null: false - t.datetime "current_sign_in_at" - t.string "current_sign_in_ip" t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false - t.string "first_name" - t.boolean "has_run_first_time_setup", default: false - t.string "last_name" + t.string "reset_password_token" + t.datetime "reset_password_sent_at" + t.datetime "remember_created_at" + t.integer "sign_in_count", default: 0 + t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" + t.string "current_sign_in_ip" t.string "last_sign_in_ip" - t.string "login_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "first_name" + t.string "last_name" + t.string "username" t.string "nickname" - t.boolean "opt_in_to_research" + t.string "unlock_token" + t.bigint "role_id", default: 0 + t.boolean "receive_task_notifications", default: true t.boolean "receive_feedback_notifications", default: true t.boolean "receive_portfolio_notifications", default: true - t.boolean "receive_task_notifications", default: true - t.datetime "remember_created_at" - t.datetime "reset_password_sent_at" - t.string "reset_password_token" - t.bigint "role_id", default: 0 - t.integer "sign_in_count", default: 0 + t.boolean "opt_in_to_research" + t.boolean "has_run_first_time_setup", default: false + t.string "login_id" t.string "student_id" - t.datetime "tii_eula_date" t.string "tii_eula_version" + t.datetime "tii_eula_date" t.boolean "tii_eula_version_confirmed", default: false, null: false - t.string "unlock_token" - t.datetime "updated_at", null: false - t.string "username" t.index ["email"], name: "index_users_on_email", unique: true t.index ["login_id"], name: "index_users_on_login_id", unique: true t.index ["role_id"], name: "index_users_on_role_id" @@ -742,23 +742,23 @@ end create_table "webcal_unit_exclusions", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "created_at", null: false + t.bigint "webcal_id", null: false t.bigint "unit_id", null: false + t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.bigint "webcal_id", null: false t.index ["unit_id", "webcal_id"], name: "index_webcal_unit_exclusions_on_unit_id_and_webcal_id", unique: true t.index ["unit_id"], name: "index_webcal_unit_exclusions_on_unit_id" t.index ["webcal_id"], name: "fk_rails_d5fab02cb7" end create_table "webcals", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| - t.datetime "created_at", null: false t.string "guid", limit: 36, null: false t.boolean "include_start_dates", default: false, null: false + t.bigint "user_id" t.integer "reminder_time" t.string "reminder_unit" + t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.bigint "user_id" t.index ["guid"], name: "index_webcals_on_guid", unique: true t.index ["user_id"], name: "index_webcals_on_user_id", unique: true end diff --git a/test/mailers/notifications_mailer_test.rb b/test/mailers/notifications_mailer_test.rb index a8a92358ef..65238fd1e5 100644 --- a/test/mailers/notifications_mailer_test.rb +++ b/test/mailers/notifications_mailer_test.rb @@ -4,34 +4,30 @@ class NotificationsMailerTest < ActionMailer::TestCase include TestHelpers::AuthHelper def setup - # Mock Doubtfire configuration - Doubtfire::Application.config.institution = { - host: 'doubtfire.deakin.edu.au', - product_name: 'Doubtfire' - } - # Create unit and staff @unit = FactoryBot.create(:unit) @staff = FactoryBot.create(:user, role: Role.tutor) @unit.employ_staff(@staff, Role.tutor) # Create a task definition - @task_definition = @unit.task_definitions.create!({ - tutorial_stream: @unit.tutorial_streams.first, - name: 'Test Task', - description: 'Test task for notifications', - weighting: 4, - target_grade: 0, - start_date: Time.zone.now - 1.week, - target_date: Time.zone.now + 1.week, - due_date: Time.zone.now + 2.weeks, - abbreviation: 'TESTTASK', - restrict_status_updates: false, - upload_requirements: [], - plagiarism_warn_pct: 0.8, - is_graded: false, - max_quality_pts: 0 - }) + @task_definition = @unit.task_definitions.create!( + { + tutorial_stream: @unit.tutorial_streams.first, + name: 'Test Task', + description: 'Test task for notifications', + weighting: 4, + target_grade: 0, + start_date: Time.zone.now - 1.week, + target_date: Time.zone.now + 1.week, + due_date: Time.zone.now + 2.weeks, + abbreviation: 'TESTTASK', + restrict_status_updates: false, + upload_requirements: [], + plagiarism_warn_pct: 0.8, + is_graded: false, + max_quality_pts: 0 + } + ) # Create students and projects with notification preferences @students = [] @@ -79,11 +75,11 @@ def teardown # Verify email properties assert_equal [@staff.email], mail.to assert_equal "#{@unit.name}: Staff Grant Extensions", mail.subject - assert_match /You have granted extensions for the following students/, mail.html_part.body.to_s + assert_match(/You have granted extensions for the following students/, mail.html_part.body.to_s) # Verify from address contains no-reply assert_includes mail.from.first, "no-reply@" - assert_includes mail.from.first, NotificationsMailer.doubtfire_host + assert_includes mail.from.first, Doubtfire::Application.config.institution[:email_domain] end test 'creates correct extension notification email' do @@ -99,7 +95,7 @@ def teardown # Verify email properties assert_equal [@students.first.email], mail.to assert_equal "#{@unit.name}: Extension granted for #{@task_definition.name}", mail.subject - assert_match /Dear #{@students.first.first_name}/, mail.html_part.body.to_s + assert_match(/Dear #{@students.first.first_name}/, mail.html_part.body.to_s) # Verify from address contains staff email assert_includes mail.from.first, @staff.email @@ -131,33 +127,35 @@ def teardown # Verify email includes failed extensions assert_equal [@staff.email], mail.to - assert_match /Failed Extensions/, mail.html_part.body.to_s - assert_match /999/, mail.html_part.body.to_s - assert_match /1000/, mail.html_part.body.to_s + assert_match(/Failed Extensions/, mail.html_part.body.to_s) + assert_match(/999/, mail.html_part.body.to_s) + assert_match(/1000/, mail.html_part.body.to_s) # Verify from address contains no-reply assert_includes mail.from.first, "no-reply@" - assert_includes mail.from.first, NotificationsMailer.doubtfire_host + assert_includes mail.from.first, Doubtfire::Application.config.institution[:email_domain] end test 'creates correct extension notification with special characters' do # Create task with special characters - special_task = @unit.task_definitions.create!({ - tutorial_stream: @unit.tutorial_streams.first, - name: 'Test Task with !@#$%^&*()', - description: 'Test task with special characters', - weighting: 4, - target_grade: 0, - start_date: Time.zone.now - 1.week, - target_date: Time.zone.now + 1.week, - due_date: Time.zone.now + 2.weeks, - abbreviation: 'SPECIAL', - restrict_status_updates: false, - upload_requirements: [], - plagiarism_warn_pct: 0.8, - is_graded: false, - max_quality_pts: 0 - }) + special_task = @unit.task_definitions.create!( + { + tutorial_stream: @unit.tutorial_streams.first, + name: 'Test Task with !@#$%^&*()', + description: 'Test task with special characters', + weighting: 4, + target_grade: 0, + start_date: Time.zone.now - 1.week, + target_date: Time.zone.now + 1.week, + due_date: Time.zone.now + 2.weeks, + abbreviation: 'SPECIAL', + restrict_status_updates: false, + upload_requirements: [], + plagiarism_warn_pct: 0.8, + is_graded: false, + max_quality_pts: 0 + } + ) # Create extension project = @projects.first @@ -171,7 +169,7 @@ def teardown # Verify email handles special characters assert_equal [@students.first.email], mail.to assert_equal "#{@unit.name}: Extension granted for #{special_task.name}", mail.subject - assert_match /Dear #{@students.first.name}/, mail.html_part.body.to_s + assert_match(/Dear #{@students.first.name}/, mail.html_part.body.to_s) # Verify from address contains staff email assert_includes mail.from.first, @staff.email