diff --git a/Gemfile b/Gemfile index 2aeddf2..482e533 100644 --- a/Gemfile +++ b/Gemfile @@ -77,3 +77,4 @@ gem "httparty" gem "sidekiq" gem "icalendar" +gem "administrate" diff --git a/Gemfile.lock b/Gemfile.lock index 9761cc4..64f7d59 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -68,6 +68,14 @@ GEM tzinfo (~> 2.0) addressable (2.8.5) public_suffix (>= 2.0.2, < 6.0) + administrate (0.19.0) + actionpack (>= 5.0) + actionview (>= 5.0) + activerecord (>= 5.0) + jquery-rails (>= 4.0) + kaminari (>= 1.0) + sassc-rails (~> 2.1) + selectize-rails (~> 0.6) bindex (0.8.1) bootsnap (1.16.0) msgpack (~> 1.2) @@ -112,6 +120,22 @@ GEM jbuilder (2.11.5) actionview (>= 5.0.0) activesupport (>= 5.0.0) + jquery-rails (4.6.0) + rails-dom-testing (>= 1, < 3) + railties (>= 4.2.0) + thor (>= 0.14, < 2.0) + kaminari (1.2.2) + activesupport (>= 4.1.0) + kaminari-actionview (= 1.2.2) + kaminari-activerecord (= 1.2.2) + kaminari-core (= 1.2.2) + kaminari-actionview (1.2.2) + actionview + kaminari-core (= 1.2.2) + kaminari-activerecord (1.2.2) + activerecord + kaminari-core (= 1.2.2) + kaminari-core (1.2.2) loofah (2.21.3) crass (~> 1.0.2) nokogiri (>= 1.12.0) @@ -190,6 +214,13 @@ GEM rubyzip (2.3.2) sassc (2.4.0) ffi (~> 1.9) + sassc-rails (2.1.2) + railties (>= 4.0.0) + sassc (>= 2.0) + sprockets (> 3.0) + sprockets-rails + tilt + selectize-rails (0.12.6) selenium-webdriver (4.10.0) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) @@ -212,6 +243,7 @@ GEM tailwindcss-rails (2.0.30-arm64-darwin) railties (>= 6.0.0) thor (1.2.2) + tilt (2.3.0) timeout (0.4.0) turbo-rails (1.4.0) actionpack (>= 6.0.0) @@ -240,6 +272,7 @@ PLATFORMS arm64-darwin-22 DEPENDENCIES + administrate bootsnap capybara debug diff --git a/app/dashboards/course_dashboard.rb b/app/dashboards/course_dashboard.rb index cab4f24..b812a63 100644 --- a/app/dashboards/course_dashboard.rb +++ b/app/dashboards/course_dashboard.rb @@ -11,6 +11,7 @@ class CourseDashboard < Administrate::BaseDashboard id: Field::Number, lectures: Field::HasMany, panopto_folders: Field::String.with_options(searchable: false), + semester_start_date: Field::Date, title: Field::String, toggl_project: Field::Number, unit_code: Field::String, @@ -27,7 +28,7 @@ class CourseDashboard < Administrate::BaseDashboard id lectures panopto_folders - title + semester_start_date ].freeze # SHOW_PAGE_ATTRIBUTES @@ -36,6 +37,7 @@ class CourseDashboard < Administrate::BaseDashboard id lectures panopto_folders + semester_start_date title toggl_project unit_code @@ -49,6 +51,7 @@ class CourseDashboard < Administrate::BaseDashboard FORM_ATTRIBUTES = %i[ lectures panopto_folders + semester_start_date title toggl_project unit_code diff --git a/app/dashboards/lecture_dashboard.rb b/app/dashboards/lecture_dashboard.rb index c182d23..cda6db2 100644 --- a/app/dashboards/lecture_dashboard.rb +++ b/app/dashboards/lecture_dashboard.rb @@ -11,9 +11,9 @@ class LectureDashboard < Administrate::BaseDashboard id: Field::Number, attendance: Field::HasOne, course: Field::BelongsTo, - recording_id: Field::String, + event_uuid: Field::String, + recording: Field::BelongsTo, start_time: Field::DateTime, - status: Field::Select.with_options(searchable: false, collection: ->(field) { field.resource.class.send(field.attribute.to_s.pluralize).keys }), title: Field::String, created_at: Field::DateTime, updated_at: Field::DateTime, @@ -28,7 +28,7 @@ class LectureDashboard < Administrate::BaseDashboard id attendance course - recording_id + event_uuid ].freeze # SHOW_PAGE_ATTRIBUTES @@ -37,9 +37,9 @@ class LectureDashboard < Administrate::BaseDashboard id attendance course - recording_id + event_uuid + recording start_time - status title created_at updated_at @@ -51,9 +51,9 @@ class LectureDashboard < Administrate::BaseDashboard FORM_ATTRIBUTES = %i[ attendance course - recording_id + event_uuid + recording start_time - status title ].freeze diff --git a/app/jobs/scrape_calendar_job.rb b/app/jobs/scrape_calendar_job.rb index 229d32b..84ce2cf 100644 --- a/app/jobs/scrape_calendar_job.rb +++ b/app/jobs/scrape_calendar_job.rb @@ -1,21 +1,71 @@ class ScrapeCalendarJob < ApplicationJob queue_as :default + def clean_up_lecture_title(unit_code, short_lecture_title) end + def perform(*args) ics_file = HTTParty.get("https://mytimetable.bath.ac.uk/ical?6519757b&group=false&timetable=!MjAyMyFzdHVkZW50c2V0ITRDRjQ5MjlGRTg1M0Q4N0MyMDZENTVDNUQ3QTJFNzk0&eu=amMzMDkxQGJhdGguYWMudWs=&h=MiuDbRiudE_Yf7B25v2SfEuFCtmYGkFb5sAUI3yGmtY=") calendars = Icalendar::Calendar.parse(ics_file) calendar = calendars.first - calendar.events.each do |event| + unit_codes = Course.all.map(&:unit_code) + + events = calendar.events.map do |event| summary = event.summary match = summary.split('-') # Handle odd events we don't care about - return if match.length != 2 + next if match.length != 2 unit_code = match[0] short_lecture_title = match[1] - start_time = event.dtstart + start_time = event.dtstart.to_time + event_uuid = event.uid.to_s + + next if unit_code.nil? || + short_lecture_title.nil? || + event_uuid.nil? || + start_time.nil? || + !unit_code.in?(unit_codes) + + { + unit_code:, + short_lecture_title:, + start_time:, + event_uuid:, + } + end + + events + .compact + .filter { |attrs| attrs[:unit_code].in? unit_codes } + .group_by { |attrs| attrs[:unit_code] } + .map do |unit_code, course_events| + course = Course.find_by(unit_code: unit_code) + + lecture_counter = 0 + + course_events.each do |event| + # Naive check to see if we've already created this lecture + if course.lectures.find_by(event_uuid: event[:event_uuid]).present? || course.lectures.find_by( + start_time: (event[:start_time].beginning_of_hour + 5.minutes)..(event[:start_time].beginning_of_hour + 1.hour + 5.minutes) + ).present? + next + end + + if event[:short_lecture_title].starts_with? "Lec" + lecture_counter += 1 + title = "Lecture #{lecture_counter}" + else + title = event[:short_lecture_title] + end + + course.lectures.create!( + title: title, + start_time: event[:start_time], + event_uuid: event[:event_uuid], + ) + end end end end diff --git a/app/jobs/scrape_panopto_job.rb b/app/jobs/scrape_panopto_job.rb index a6d1270..62d911e 100644 --- a/app/jobs/scrape_panopto_job.rb +++ b/app/jobs/scrape_panopto_job.rb @@ -27,13 +27,22 @@ class ScrapePanoptoJob < ApplicationJob created.each do |new_recording_attrs| new_recording = Recording.new(new_recording_attrs) - Lecture.create!( - title: new_recording.nice_title, - start_time: new_recording.start_time, - course_id: new_recording.course_id, - status: :undetermined, - recording_id: new_recording.id - ) + + # Find existing lecture to associate with this recording + if (lecture = course.lectures.find_by( + start_time: new_recording.start_time.beginning_of_hour + 15.minutes, + recording_id: nil, + )) + lecture.update!(recording_id: new_recording.id) + else + # Else create a new lecture + Lecture.create!( + title: new_recording.nice_title, + start_time: new_recording.start_time, + course_id: new_recording.course_id, + recording_id: new_recording.id + ) + end end end end diff --git a/app/models/lecture.rb b/app/models/lecture.rb index ad8fb54..f5fa816 100644 --- a/app/models/lecture.rb +++ b/app/models/lecture.rb @@ -3,15 +3,6 @@ class Lecture < ApplicationRecord has_one :attendance belongs_to :recording, optional: true - enum :status, [ - :undetermined, - :future, - :happening_now, - :attended_in_person, - :watched_recording, - :missed, - ], default: :undetermined - def week_number ((start_time.beginning_of_week - course.semester_start_date.to_time) / 1.week).floor + 1 end diff --git a/app/views/attendance_tracker/index.html.erb b/app/views/attendance_tracker/index.html.erb index 14dec1b..497e672 100644 --- a/app/views/attendance_tracker/index.html.erb +++ b/app/views/attendance_tracker/index.html.erb @@ -28,7 +28,7 @@ <% style = if lecture.start_time.future? then 'background: repeating-linear-gradient(45deg, #f3f4f6, #f3f4f6 10px, white 10px, white 20px);' else '' end %> - <%= lecture.recording.nice_title %> + <%= lecture.title %> <%= lecture.start_time.to_fs(:dmy) %> diff --git a/config/routes.rb b/config/routes.rb index 9f3894f..ab1ab25 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,10 +1,11 @@ Rails.application.routes.draw do namespace :admin do - resources :attendances - resources :courses - resources :lectures + resources :attendances + resources :courses + resources :lectures + + root to: "attendances#index" + end - root to: "attendances#index" - end root controller: :attendance_tracker, action: :index end diff --git a/db/migrate/20231001180954_update_lecture_properties.rb b/db/migrate/20231001180954_update_lecture_properties.rb new file mode 100644 index 0000000..f45ec23 --- /dev/null +++ b/db/migrate/20231001180954_update_lecture_properties.rb @@ -0,0 +1,8 @@ +class UpdateLectureProperties < ActiveRecord::Migration[7.0] + def change + remove_column :lectures, :status + + # Optional association with an event in the calendar + add_column :lectures, :event_uuid, :string, null: true + end +end diff --git a/db/migrate/20231001183712_change_recording_start_time_type.rb b/db/migrate/20231001183712_change_recording_start_time_type.rb new file mode 100644 index 0000000..319d2f6 --- /dev/null +++ b/db/migrate/20231001183712_change_recording_start_time_type.rb @@ -0,0 +1,5 @@ +class ChangeRecordingStartTimeType < ActiveRecord::Migration[7.0] + def change + change_column :recordings, :start_time, :datetime, using: 'start_time::timestamp' + end +end diff --git a/db/schema.rb b/db/schema.rb index 8014af4..5dfc295 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[7.0].define(version: 2023_10_01_165128) do +ActiveRecord::Schema[7.0].define(version: 2023_10_01_183712) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -36,18 +36,18 @@ ActiveRecord::Schema[7.0].define(version: 2023_10_01_165128) do create_table "lectures", force: :cascade do |t| t.string "title", null: false t.datetime "start_time", null: false - t.integer "status", default: 0, null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false t.bigint "course_id" t.bigint "recording_id" + t.string "event_uuid" t.index ["course_id"], name: "index_lectures_on_course_id" t.index ["recording_id"], name: "index_lectures_on_recording_id" end create_table "recordings", force: :cascade do |t| t.string "title", null: false - t.string "start_time", null: false + t.datetime "start_time", null: false t.string "recording_uuid", null: false t.bigint "course_id", null: false t.datetime "created_at", null: false diff --git a/db/seeds.rb b/db/seeds.rb index bc25fce..4156dda 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -5,3 +5,19 @@ # # movies = Movie.create([{ name: "Star Wars" }, { name: "Lord of the Rings" }]) # Character.create(name: "Luke", movie: movies.first) + +Course.create!( + title: 'General Relativity', + panopto_folders: ["5a77867a-236b-478f-9b0d-af2400ed0989"], + toggl_project: 189467492, + unit_code: 'PH30101', + semester_start_date: Date.parse('2023-02-06'), +) + +Course.create!( + title: 'Continuum mechanics', + panopto_folders: ["dca2c510-49b4-4a24-9c91-b07701022d03"], + toggl_project: 195482340, + unit_code: 'MA30253', + semester_start_date: START_OF_YEAR_5_SEMESTER_1.to_date, +)