require "csv" require "activerecord-import" require "zip" require_relative "./report_processor" module CanvasSync module Processors class ProvisioningReportProcessor < ReportProcessor # Processes a provisioning report using the bulk importer. # # options must contain a models key. If there is only one model # Canvas downloads the single report directly as a CSV. If it's # more than one model Canvas downloads a ZIP file, so we have to # extract that and iterate through it for processing. # # @param report_file_path [String] # @param options [Hash] def self.process(report_file_path, options, report_id) new(report_file_path, options) end def initialize(report_file_path, options) # rubocop:disable Metrics/AbcSize @options = options if options[:models].length == 1 run_import(options[:models][0], report_file_path) else unzipped_file_path = extract(report_file_path) Dir[unzipped_file_path + "/*.csv"].sort.each do |file_path| model_name = file_path.split("/").last.split(".").first run_import(model_name, file_path) end end end private def extract(file_path) unzipped_file_path = "#{file_path}_unzipped" Zip::File.open(file_path) do |zip_file| zip_file.each do |f| f_path = File.join(unzipped_file_path, f.name) FileUtils.mkdir_p(File.dirname(f_path)) zip_file.extract(f, f_path) unless File.exist?(f_path) end end unzipped_file_path end def run_import(model_name, report_file_path) if legacy?(model_name) CanvasSync::Importers::LegacyImporter.import( report_file_path, model_name.singularize.capitalize.constantize, @options[:account_id], @options, ) else send("bulk_process_#{model_name}", report_file_path) end end def legacy?(model_name) opt = @options[:legacy_support] return false if opt == false || opt.nil? return true if opt == true return opt.include?(model_name) end def bulk_process_users(report_file_path) do_bulk_import(report_file_path, User, options: @options) end def bulk_process_user_observers(report_file_path) do_bulk_import(report_file_path, UserObserver, options: @options) end def bulk_process_pseudonyms(report_file_path) do_bulk_import(report_file_path, Pseudonym, options: @options) end def bulk_process_accounts(report_file_path) do_bulk_import(report_file_path, Account, options: @options) end def bulk_process_courses(report_file_path) do_bulk_import(report_file_path, Course, options: @options) end def bulk_process_enrollments(report_file_path) do_bulk_import(report_file_path, Enrollment, options: @options) end def bulk_process_sections(report_file_path) do_bulk_import(report_file_path, Section, options: @options) end def bulk_process_xlist(report_file_path) do_bulk_import(report_file_path, Section, options: @options, mapping_key: :xlist) end def bulk_process_groups(report_file_path) do_bulk_import(report_file_path, Group, options: @options) end def bulk_process_grading_periods(report_file_path) do_bulk_import(report_file_path, GradingPeriod, options: @options) end def bulk_process_grading_period_groups(report_file_path) do_bulk_import(report_file_path, GradingPeriodGroup, options: @options) end # Note that group membership is singular because we override the model name param in sync_provisioning_report_job def bulk_process_group_membership(report_file_path) do_bulk_import(report_file_path, GroupMembership, options: @options) end def bulk_process_learning_outcomes(report_file_path) do_bulk_import(report_file_path, LearningOutcome, options: @options) do |row| row[:root_account_ids] = JSON.parse row[:root_account_ids] row end end def bulk_process_course_nicknames(report_file_path) exists = [] do_bulk_import(report_file_path, CourseNickname, options: @options) do |row| exists << row[:user_preference_value_id] row end # Canvas does not soft delete UserPreferenceValues so there's no point in doing the same CourseNickname.where.not(canvas_user_preference_value_id: exists).delete_all end end end end