Update lecture properties and various migration changes

Modified code to remove 'status' field from lecture's table and added 'event_uuid' field. Also refined the 'start_time' field in recordings' table to handle datetime instead of strings. Further, made minor changes in scraping jobs and seeding logic. Implemented these changes to allow lectures to be associated with calendar events and streamlined various fields.
This commit is contained in:
Joshua Coles 2023-10-01 19:44:20 +01:00
parent 070816f0a2
commit d9d182bc94
13 changed files with 153 additions and 36 deletions

View File

@ -77,3 +77,4 @@ gem "httparty"
gem "sidekiq" gem "sidekiq"
gem "icalendar" gem "icalendar"
gem "administrate"

View File

@ -68,6 +68,14 @@ GEM
tzinfo (~> 2.0) tzinfo (~> 2.0)
addressable (2.8.5) addressable (2.8.5)
public_suffix (>= 2.0.2, < 6.0) 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) bindex (0.8.1)
bootsnap (1.16.0) bootsnap (1.16.0)
msgpack (~> 1.2) msgpack (~> 1.2)
@ -112,6 +120,22 @@ GEM
jbuilder (2.11.5) jbuilder (2.11.5)
actionview (>= 5.0.0) actionview (>= 5.0.0)
activesupport (>= 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) loofah (2.21.3)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.12.0) nokogiri (>= 1.12.0)
@ -190,6 +214,13 @@ GEM
rubyzip (2.3.2) rubyzip (2.3.2)
sassc (2.4.0) sassc (2.4.0)
ffi (~> 1.9) 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) selenium-webdriver (4.10.0)
rexml (~> 3.2, >= 3.2.5) rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0) rubyzip (>= 1.2.2, < 3.0)
@ -212,6 +243,7 @@ GEM
tailwindcss-rails (2.0.30-arm64-darwin) tailwindcss-rails (2.0.30-arm64-darwin)
railties (>= 6.0.0) railties (>= 6.0.0)
thor (1.2.2) thor (1.2.2)
tilt (2.3.0)
timeout (0.4.0) timeout (0.4.0)
turbo-rails (1.4.0) turbo-rails (1.4.0)
actionpack (>= 6.0.0) actionpack (>= 6.0.0)
@ -240,6 +272,7 @@ PLATFORMS
arm64-darwin-22 arm64-darwin-22
DEPENDENCIES DEPENDENCIES
administrate
bootsnap bootsnap
capybara capybara
debug debug

View File

@ -11,6 +11,7 @@ class CourseDashboard < Administrate::BaseDashboard
id: Field::Number, id: Field::Number,
lectures: Field::HasMany, lectures: Field::HasMany,
panopto_folders: Field::String.with_options(searchable: false), panopto_folders: Field::String.with_options(searchable: false),
semester_start_date: Field::Date,
title: Field::String, title: Field::String,
toggl_project: Field::Number, toggl_project: Field::Number,
unit_code: Field::String, unit_code: Field::String,
@ -27,7 +28,7 @@ class CourseDashboard < Administrate::BaseDashboard
id id
lectures lectures
panopto_folders panopto_folders
title semester_start_date
].freeze ].freeze
# SHOW_PAGE_ATTRIBUTES # SHOW_PAGE_ATTRIBUTES
@ -36,6 +37,7 @@ class CourseDashboard < Administrate::BaseDashboard
id id
lectures lectures
panopto_folders panopto_folders
semester_start_date
title title
toggl_project toggl_project
unit_code unit_code
@ -49,6 +51,7 @@ class CourseDashboard < Administrate::BaseDashboard
FORM_ATTRIBUTES = %i[ FORM_ATTRIBUTES = %i[
lectures lectures
panopto_folders panopto_folders
semester_start_date
title title
toggl_project toggl_project
unit_code unit_code

View File

@ -11,9 +11,9 @@ class LectureDashboard < Administrate::BaseDashboard
id: Field::Number, id: Field::Number,
attendance: Field::HasOne, attendance: Field::HasOne,
course: Field::BelongsTo, course: Field::BelongsTo,
recording_id: Field::String, event_uuid: Field::String,
recording: Field::BelongsTo,
start_time: Field::DateTime, 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, title: Field::String,
created_at: Field::DateTime, created_at: Field::DateTime,
updated_at: Field::DateTime, updated_at: Field::DateTime,
@ -28,7 +28,7 @@ class LectureDashboard < Administrate::BaseDashboard
id id
attendance attendance
course course
recording_id event_uuid
].freeze ].freeze
# SHOW_PAGE_ATTRIBUTES # SHOW_PAGE_ATTRIBUTES
@ -37,9 +37,9 @@ class LectureDashboard < Administrate::BaseDashboard
id id
attendance attendance
course course
recording_id event_uuid
recording
start_time start_time
status
title title
created_at created_at
updated_at updated_at
@ -51,9 +51,9 @@ class LectureDashboard < Administrate::BaseDashboard
FORM_ATTRIBUTES = %i[ FORM_ATTRIBUTES = %i[
attendance attendance
course course
recording_id event_uuid
recording
start_time start_time
status
title title
].freeze ].freeze

View File

@ -1,21 +1,71 @@
class ScrapeCalendarJob < ApplicationJob class ScrapeCalendarJob < ApplicationJob
queue_as :default queue_as :default
def clean_up_lecture_title(unit_code, short_lecture_title) end
def perform(*args) def perform(*args)
ics_file = HTTParty.get("https://mytimetable.bath.ac.uk/ical?6519757b&group=false&timetable=!MjAyMyFzdHVkZW50c2V0ITRDRjQ5MjlGRTg1M0Q4N0MyMDZENTVDNUQ3QTJFNzk0&eu=amMzMDkxQGJhdGguYWMudWs=&h=MiuDbRiudE_Yf7B25v2SfEuFCtmYGkFb5sAUI3yGmtY=") 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) calendars = Icalendar::Calendar.parse(ics_file)
calendar = calendars.first calendar = calendars.first
calendar.events.each do |event| unit_codes = Course.all.map(&:unit_code)
events = calendar.events.map do |event|
summary = event.summary summary = event.summary
match = summary.split('-') match = summary.split('-')
# Handle odd events we don't care about # Handle odd events we don't care about
return if match.length != 2 next if match.length != 2
unit_code = match[0] unit_code = match[0]
short_lecture_title = match[1] 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 end
end end

View File

@ -27,14 +27,23 @@ class ScrapePanoptoJob < ApplicationJob
created.each do |new_recording_attrs| created.each do |new_recording_attrs|
new_recording = Recording.new(new_recording_attrs) new_recording = Recording.new(new_recording_attrs)
# 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!( Lecture.create!(
title: new_recording.nice_title, title: new_recording.nice_title,
start_time: new_recording.start_time, start_time: new_recording.start_time,
course_id: new_recording.course_id, course_id: new_recording.course_id,
status: :undetermined,
recording_id: new_recording.id recording_id: new_recording.id
) )
end end
end end
end end
end
end end

View File

@ -3,15 +3,6 @@ class Lecture < ApplicationRecord
has_one :attendance has_one :attendance
belongs_to :recording, optional: true belongs_to :recording, optional: true
enum :status, [
:undetermined,
:future,
:happening_now,
:attended_in_person,
:watched_recording,
:missed,
], default: :undetermined
def week_number def week_number
((start_time.beginning_of_week - course.semester_start_date.to_time) / 1.week).floor + 1 ((start_time.beginning_of_week - course.semester_start_date.to_time) / 1.week).floor + 1
end end

View File

@ -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 %> <% style = if lecture.start_time.future? then 'background: repeating-linear-gradient(45deg, #f3f4f6, #f3f4f6 10px, white 10px, white 20px);' else '' end %>
<tr style="<%= style %>"> <tr style="<%= style %>">
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6"> <td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6">
<%= lecture.recording.nice_title %> <%= lecture.title %>
</td> </td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500"> <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
<%= lecture.start_time.to_fs(:dmy) %> <%= lecture.start_time.to_fs(:dmy) %>

View File

@ -6,5 +6,6 @@ Rails.application.routes.draw do
root to: "attendances#index" root to: "attendances#index"
end end
root controller: :attendance_tracker, action: :index root controller: :attendance_tracker, action: :index
end end

View File

@ -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

View File

@ -0,0 +1,5 @@
class ChangeRecordingStartTimeType < ActiveRecord::Migration[7.0]
def change
change_column :recordings, :start_time, :datetime, using: 'start_time::timestamp'
end
end

6
db/schema.rb generated
View File

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # 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 # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" 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| create_table "lectures", force: :cascade do |t|
t.string "title", null: false t.string "title", null: false
t.datetime "start_time", null: false t.datetime "start_time", null: false
t.integer "status", default: 0, null: false
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.bigint "course_id" t.bigint "course_id"
t.bigint "recording_id" t.bigint "recording_id"
t.string "event_uuid"
t.index ["course_id"], name: "index_lectures_on_course_id" t.index ["course_id"], name: "index_lectures_on_course_id"
t.index ["recording_id"], name: "index_lectures_on_recording_id" t.index ["recording_id"], name: "index_lectures_on_recording_id"
end end
create_table "recordings", force: :cascade do |t| create_table "recordings", force: :cascade do |t|
t.string "title", null: false t.string "title", null: false
t.string "start_time", null: false t.datetime "start_time", null: false
t.string "recording_uuid", null: false t.string "recording_uuid", null: false
t.bigint "course_id", null: false t.bigint "course_id", null: false
t.datetime "created_at", null: false t.datetime "created_at", null: false

View File

@ -5,3 +5,19 @@
# #
# movies = Movie.create([{ name: "Star Wars" }, { name: "Lord of the Rings" }]) # movies = Movie.create([{ name: "Star Wars" }, { name: "Lord of the Rings" }])
# Character.create(name: "Luke", movie: movies.first) # 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,
)