From 4281067a5f820c2af55816ae98c997489bd00ae5 Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Mon, 18 Mar 2024 14:41:17 +0100 Subject: [PATCH] Include V2 registrations in WCIF output (#9074) * Make 'Assignemnts' table polymorphic * Hook up WCIF associations for MicroserviceRegistrations * Add missing WCIF methods to MicroserviceRegistration * Stub microservice writing methods * Store non-competing dummy registrations in cache table * Add 'roles' field to migration and harmonize 'is_competing' * Clarify comments about nil values for comment strings * Fix column sanitizers for overhauled registration tables * Fix WCIF status cancelled VS deleted and waiting_list VS pending * Remove redundant 'roles' accessor --- app/models/assignment.rb | 2 +- app/models/competition.rb | 32 +++++---- .../microservice_registration_holder.rb | 4 +- app/models/microservice_registration.rb | 68 +++++++++++++++++-- app/models/registration.rb | 2 +- ...ignments_table_registration_polymorphic.rb | 12 ++++ ...my_fields_to_microservice_registrations.rb | 8 +++ db/schema.rb | 5 +- lib/database_dumper.rb | 16 ++++- 9 files changed, 125 insertions(+), 24 deletions(-) create mode 100644 db/migrate/20240306134217_make_assignments_table_registration_polymorphic.rb create mode 100644 db/migrate/20240307181801_add_staff_dummy_fields_to_microservice_registrations.rb diff --git a/app/models/assignment.rb b/app/models/assignment.rb index 9d84426361..33c9f4eefb 100644 --- a/app/models/assignment.rb +++ b/app/models/assignment.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class Assignment < ApplicationRecord - belongs_to :registration + belongs_to :registration, polymorphic: true belongs_to :schedule_activity validates :station_number, numericality: { only_integer: true }, allow_nil: true diff --git a/app/models/competition.rb b/app/models/competition.rb index 2485f1d529..e2469a700e 100644 --- a/app/models/competition.rb +++ b/app/models/competition.rb @@ -1773,25 +1773,29 @@ def competition_series_ids def persons_wcif(authorized: false) managers = self.managers includes_associations = [ - :events, { assignments: [:schedule_activity] }, { user: { person: [:ranksSingle, :ranksAverage], } }, :wcif_extensions, ] + # V2 registrations store the event IDs in the microservice data, not in the monolith + includes_associations << :events unless self.uses_new_registration_service? + + registrations_relation = self.uses_new_registration_service? ? self.microservice_registrations : self.registrations + # NOTE: we're including non-competing registrations so that they can have job # assignments as well. These registrations don't have accepted?, but they # should appear in the WCIF. - persons_wcif = registrations.order(:id) - .includes(includes_associations) - .to_enum - .with_index(1) - .select { |r, registrant_id| authorized || r.wcif_status == "accepted" } - .map do |r, registrant_id| - managers.delete(r.user) - r.user.to_wcif(self, r, registrant_id, authorized: authorized) - end + persons_wcif = registrations_relation.order(:id) + .includes(includes_associations) + .to_enum + .with_index(1) + .select { |r, registrant_id| authorized || r.wcif_status == "accepted" } + .map do |r, registrant_id| + managers.delete(r.user) + r.user.to_wcif(self, r, registrant_id, authorized: authorized) + end # NOTE: unregistered managers may generate N+1 queries on their personal bests, # but that's fine because there are very few of them! persons_wcif + managers.map { |m| m.to_wcif(self, authorized: authorized) } @@ -1930,11 +1934,13 @@ def set_wcif_events!(wcif_events, current_user) # Takes an array of partial Person WCIF and updates the fields that are not immutable. def update_persons_wcif!(wcif_persons, current_user) - registrations = self.registrations.includes [ + registrations_relation = self.uses_new_registration_service? ? self.microservice_registrations : self.registrations + registration_includes = [ { assignments: [:schedule_activity] }, :user, - :registration_competition_events, ] + registration_includes << :registration_competition_events unless self.uses_new_registration_service? + registrations = registrations_relation.includes(registration_includes) competition_activities = all_activities new_assignments = [] removed_assignments = [] @@ -1947,8 +1953,6 @@ def update_persons_wcif!(wcif_persons, current_user) registration ||= registrations.create( competition: self, user_id: wcif_person["wcaUserId"], - created_at: DateTime.now, - updated_at: DateTime.now, is_competing: false, ) end diff --git a/app/models/concerns/microservice_registration_holder.rb b/app/models/concerns/microservice_registration_holder.rb index 3708040881..450f8e1df7 100644 --- a/app/models/concerns/microservice_registration_holder.rb +++ b/app/models/concerns/microservice_registration_holder.rb @@ -27,9 +27,9 @@ def microservice_registrations # we hydrate each model with the microservice information directly from above ar_models.each do |ar_model| matching_ms_model = ms_models.find { |ms_model| ms_model['competition_id'] == ar_model.competition_id && ms_model['user_id'] == ar_model.user_id } - raise "No matching Microservice registration found. This should not happen!" unless matching_ms_model.present? + raise "No matching Microservice registration found. This should not happen!" if !matching_ms_model.present? && ar_model.is_competing? - ar_model.load_ms_model(matching_ms_model) + ar_model.load_ms_model(matching_ms_model) if matching_ms_model.present? end end end diff --git a/app/models/microservice_registration.rb b/app/models/microservice_registration.rb index 651c1619aa..9d34f7386a 100644 --- a/app/models/microservice_registration.rb +++ b/app/models/microservice_registration.rb @@ -4,19 +4,27 @@ class MicroserviceRegistration < ApplicationRecord belongs_to :competition, inverse_of: :microservice_registrations belongs_to :user, inverse_of: :microservice_registrations + has_many :assignments, as: :registration + has_many :wcif_extensions, as: :extendable, dependent: :delete_all + + serialize :roles, coder: YAML + delegate :name, :email, to: :user attr_accessor :ms_registration - attr_writer :competing_status, :event_ids + attr_writer :competing_status, :event_ids, :guests, :comments, :administrative_notes def load_ms_model(ms_model) self.ms_registration = ms_model self.competing_status = ms_model['competing_status'] + self.guests = ms_model['guests'] + + competing_lane = ms_model['lanes']&.find { |lane| lane['lane_name'] == 'competing' } - self.event_ids = ms_model['lanes']&.find do |lane| - lane['lane_name'] == 'competing' - end&.dig('lane_details', 'event_details')&.pluck('event_id') + self.event_ids = competing_lane&.dig('lane_details', 'event_details')&.pluck('event_id') + self.comments = competing_lane&.dig('lane_details', 'comment') + self.administrative_notes = competing_lane&.dig('lane_details', 'admin_comment') end def ms_loaded? @@ -30,15 +38,47 @@ def ms_loaded? end def competing_status + # Treat non-competing registrations as accepted, see also `registration.rb` + return "accepted" unless self.is_competing? + self.read_ms_data :competing_status end alias :status :competing_status + def wcif_status + return "deleted" if self.deleted? + return "pending" if self.pending? + + self.competing_status + end + def event_ids + return [] unless self.is_competing? + self.read_ms_data :event_ids end + def guests + return 0 unless self.is_competing? + + self.read_ms_data :guests + end + + def comments + # nil is not allowed here, see WCIF spec! + return '' unless self.is_competing? + + self.read_ms_data :comments + end + + def administrative_notes + # nil is not allowed here, see WCIF spec! + return '' unless self.is_competing? + + self.read_ms_data :administrative_notes + end + def accepted? self.status == "accepted" end @@ -46,4 +86,24 @@ def accepted? def deleted? self.status == "cancelled" end + + def pending? + # WCIF interprets "pending" as "not approved to compete yet" + # which is why these two statuses collapse into one. + self.status == "pending" || self.status == "waiting_list" + end + + def to_wcif(authorized: false) + authorized_fields = { + "guests" => guests, + "comments" => comments || '', + "administrativeNotes" => administrative_notes || '', + } + { + "wcaRegistrationId" => id, + "eventIds" => event_ids.sort, + "status" => wcif_status, + "isCompeting" => is_competing?, + }.merge(authorized ? authorized_fields : {}) + end end diff --git a/app/models/registration.rb b/app/models/registration.rb index e09ae40694..ab740433f9 100644 --- a/app/models/registration.rb +++ b/app/models/registration.rb @@ -16,7 +16,7 @@ class Registration < ApplicationRecord has_many :registration_payments has_many :competition_events, through: :registration_competition_events has_many :events, through: :competition_events - has_many :assignments, dependent: :delete_all + has_many :assignments, as: :registration, dependent: :delete_all has_many :wcif_extensions, as: :extendable, dependent: :delete_all has_many :stripe_payment_intents, as: :holder, dependent: :delete_all diff --git a/db/migrate/20240306134217_make_assignments_table_registration_polymorphic.rb b/db/migrate/20240306134217_make_assignments_table_registration_polymorphic.rb new file mode 100644 index 0000000000..a6ea9110b2 --- /dev/null +++ b/db/migrate/20240306134217_make_assignments_table_registration_polymorphic.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class MakeAssignmentsTableRegistrationPolymorphic < ActiveRecord::Migration[7.1] + def change + add_column :assignments, :registration_type, :string, after: :registration_id + + remove_index :assignments, column: :registration_id + add_index :assignments, [:registration_id, :registration_type] + + Assignment.update_all(registration_type: 'Registration') + end +end diff --git a/db/migrate/20240307181801_add_staff_dummy_fields_to_microservice_registrations.rb b/db/migrate/20240307181801_add_staff_dummy_fields_to_microservice_registrations.rb new file mode 100644 index 0000000000..30e3796c03 --- /dev/null +++ b/db/migrate/20240307181801_add_staff_dummy_fields_to_microservice_registrations.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class AddStaffDummyFieldsToMicroserviceRegistrations < ActiveRecord::Migration[7.1] + def change + add_column :microservice_registrations, :roles, :text, after: :user_id, null: true + add_column :microservice_registrations, :is_competing, :boolean, after: :roles, default: true, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index e2a4281037..2985b41aa1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -549,10 +549,11 @@ create_table "assignments", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| t.bigint "registration_id" + t.string "registration_type" t.bigint "schedule_activity_id" t.integer "station_number" t.string "assignment_code", null: false - t.index ["registration_id"], name: "index_assignments_on_registration_id" + t.index ["registration_id", "registration_type"], name: "index_assignments_on_registration_id_and_registration_type" t.index ["schedule_activity_id"], name: "index_assignments_on_schedule_activity_id" end @@ -776,6 +777,8 @@ create_table "microservice_registrations", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| t.string "competition_id" t.integer "user_id" + t.text "roles" + t.boolean "is_competing", default: true, null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["competition_id", "user_id"], name: "index_microservice_registrations_on_competition_id_and_user_id", unique: true diff --git a/lib/database_dumper.rb b/lib/database_dumper.rb index 8370db1f41..c3d3474dfe 100644 --- a/lib/database_dumper.rb +++ b/lib/database_dumper.rb @@ -642,7 +642,20 @@ def self.actions_to_column_sanitizers(columns_by_action) }, ), }.freeze, - "microservice_registrations" => :skip_all_rows, + "microservice_registrations" => { + where_clause: JOIN_WHERE_VISIBLE_COMP, + column_sanitizers: actions_to_column_sanitizers( + copy: %w( + id + competition_id + user_id + roles + is_competing + created_at + updated_at + ), + ), + }.freeze, "sanity_checks" => :skip_all_rows, "sanity_check_categories" => :skip_all_rows, "sanity_check_exclusions" => :skip_all_rows, @@ -851,6 +864,7 @@ def self.actions_to_column_sanitizers(columns_by_action) copy: %w( id registration_id + registration_type schedule_activity_id station_number assignment_code