require "bearcat" require "canvas_sync/version" require "canvas_sync/engine" require "canvas_sync/job" require "canvas_sync/sidekiq_job" require "canvas_sync/api_syncable" require "canvas_sync/jobs/report_starter" require "canvas_sync/jobs/report_checker" require "canvas_sync/jobs/report_processor_job" require "canvas_sync/jobs/sync_provisioning_report_job" require "canvas_sync/jobs/sync_simple_table_job.rb" require "canvas_sync/jobs/sync_assignments_job" require "canvas_sync/jobs/sync_submissions_job" require "canvas_sync/jobs/sync_assignment_groups_job" require "canvas_sync/jobs/sync_context_modules_job" require "canvas_sync/jobs/sync_context_module_items_job" require "canvas_sync/jobs/sync_terms_job" require "canvas_sync/jobs/sync_users_job" require "canvas_sync/jobs/sync_roles_job" require "canvas_sync/jobs/sync_admins_job" require "canvas_sync/config" Dir[File.dirname(__FILE__) + "/canvas_sync/processors/*.rb"].each { |file| require file } Dir[File.dirname(__FILE__) + "/canvas_sync/importers/*.rb"].each { |file| require file } Dir[File.dirname(__FILE__) + "/canvas_sync/generators/*.rb"].each { |file| require file } module CanvasSync SUPPORTED_MODELS = %w[ users courses accounts terms enrollments sections assignments submissions roles admins assignment_groups context_modules context_module_items xlist ].freeze SUPPORTED_LIVE_EVENTS = %w[ course enrollment submission assignment user syllabus grade module module_item course_section ].freeze SUPPORTED_NON_PROV_REPORTS = %w[ graded_submissions ].freeze class << self # Runs a standard provisioning sync job with no extra report types. # Terms will be synced first using the API. If you are syncing users/roles/admins # and have also specified a Term scope, Users/Roles/Admins will by synced first, before # every other model (as Users/Roles/Admins are never scoped to Term). # # @param models [Array] A list of models to sync. e.g., ['users', 'courses']. # must be one of SUPPORTED_MODELS # @param term_scope [Symbol, nil] An optional symbol representing a scope that exists on the Term model. # The provisioning report will be run for each of the terms contained in that scope. # @param legacy_support [Boolean | Array, false] This enables legacy_support, where rows are not bulk inserted. # For this to work your models must have a `create_or_udpate_from_csv` class method that takes a row # and inserts it into the database. If an array of model names is provided then only those models will use legacy support. # @param account_id [Integer, nil] This optional parameter can be used if your Term creation and # canvas_sync_client methods require an account ID. def provisioning_sync(models, term_scope: nil, legacy_support: false, account_id: nil) validate_models!(models) invoke_next(default_provisioning_report_chain(models, term_scope, legacy_support, account_id)) end # Runs a report different from provisioning sync job with no extra report types. # # @param reports_mapping [Array] An Array of hash that list the model and params with their report you # want to import: # [{model: 'submissions', report_name: 'my_report_name_csv', params: { "parameters[include_deleted]" => true } }, ...] # @param term_scope [Symbol, nil] An optional symbol representing a scope that exists on the Term model. # The provisioning report will be run for each of the terms contained in that scope. # @param account_id [Integer, nil] This optional parameter can be used if your Term creation and # canvas_sync_client methods require an account ID. def simple_report_sync(reports_mapping, term_scope: nil, account_id: nil) invoke_next(simple_report_chain(reports_mapping, term_scope, account_id)) end # Runs a chain of ordered jobs # # See the README for usage and examples # # @param job_chain [Hash] def process_jobs(job_chain) invoke_next(job_chain) end # Invokes the next job in a chain of jobs. # # This should typically be called automatically by the gem where necessary. # # @param job_chain [Hash] A chain of jobs to execute def invoke_next(job_chain) return if job_chain[:jobs].empty? # Make sure all job classes are serialized as strings job_chain[:jobs].each { |job| job[:job] = job[:job].to_s } duped_job_chain = Marshal.load(Marshal.dump(job_chain)) jobs = duped_job_chain[:jobs] next_job = jobs.shift next_job_class = next_job[:job].constantize next_job_class.perform_later(duped_job_chain, next_job[:options]) end # Syn any report to an specific set of models # # @param reports_mapping [Array] List of reports with their specific model and params # @param term_scope [String] # @param account_id [Integer, nil] This optional parameter can be used if your Term creation and # canvas_sync_client methods require an account ID. def simple_report_chain(reports_mapping, term_scope=nil, account_id=nil) jobs = reports_mapping.map do |report| { job: CanvasSync::Jobs::SyncSimpleTableJob.to_s, options: { report_name: report[:report_name], model: report[:model], mapping: report[:model], klass: report[:model].singularize.capitalize.to_s, term_scope: term_scope, params: report[:params] } } end global_options = {} global_options[:account_id] = account_id if account_id.present? { jobs: jobs, global_options: global_options } end # Syncs terms, users/roles/admins if necessary, then the rest of the specified models. # # @param models [Array] # @param term_scope [String] # @param legacy_support [Boolean, false] This enables legacy_support, where rows are not bulk inserted. # For this to work your models must have a `create_or_udpate_from_csv` class method that takes a row # and inserts it into the database. # @param account_id [Integer, nil] This optional parameter can be used if your Term creation and # canvas_sync_client methods require an account ID. # @return [Hash] def default_provisioning_report_chain(models, term_scope=nil, legacy_support=false, account_id=nil) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/LineLength term_scope = term_scope.to_s if term_scope ############################## # Pre provisioning report jobs ############################## # Always sync Terms first pre_provisioning_jobs = [{ job: CanvasSync::Jobs::SyncTermsJob.to_s, options: {} }] # Users, roles, and admins are synced before provisioning because they cannot be scoped to term if models.include?("users") && term_scope.present? pre_provisioning_jobs.push(job: CanvasSync::Jobs::SyncUsersJob.to_s, options: {}) models -= ["users"] end if models.include?("roles") pre_provisioning_jobs.push(job: CanvasSync::Jobs::SyncRolesJob.to_s, options: {}) models -= ["roles"] end if models.include?("admins") pre_provisioning_jobs.push(job: CanvasSync::Jobs::SyncAdminsJob.to_s, options: {}) models -= ["admins"] end ############################### # Post provisioning report jobs ############################### post_provisioning_jobs = [] if models.include?("assignments") post_provisioning_jobs.push(job: CanvasSync::Jobs::SyncAssignmentsJob.to_s, options: {}) models -= ["assignments"] end if models.include?("submissions") post_provisioning_jobs.push(job: CanvasSync::Jobs::SyncSubmissionsJob.to_s, options: {}) models -= ["submissions"] end if models.include?("assignment_groups") post_provisioning_jobs.push(job: CanvasSync::Jobs::SyncAssignmentGroupsJob.to_s, options: {}) models -= ["assignment_groups"] end if models.include?("context_modules") post_provisioning_jobs.push(job: CanvasSync::Jobs::SyncContextModulesJob.to_s, options: {}) models -= ["context_modules"] end if models.include?("context_module_items") post_provisioning_jobs.push(job: CanvasSync::Jobs::SyncContextModuleItemsJob.to_s, options: {}) models -= ["context_module_items"] end provisioning_job = { job: CanvasSync::Jobs::SyncProvisioningReportJob.to_s, options: { term_scope: term_scope, models: models }, } jobs = pre_provisioning_jobs + Array.wrap(provisioning_job) + post_provisioning_jobs global_options = { legacy_support: legacy_support } global_options[:account_id] = account_id if account_id.present? { jobs: jobs, global_options: global_options } end # Calls the canvas_sync_client in your app. If you have specified an account # ID when starting the job it will pass the account ID to your canvas_sync_client method. # # @param options [Hash] def get_canvas_sync_client(options) if options[:account_id] canvas_sync_client(options[:account_id]) else canvas_sync_client end end # Configure options for CanvasSync. See config.rb for valid configuration options. # # Example: # # CanvasSync.configure do |config| # config.classes_to_only_log_errors_on << "Blah" # end def configure yield config config end # Returns the CanvasSync config def config @config ||= CanvasSync::Config.new end def validate_models!(models) invalid = models - SUPPORTED_MODELS return if invalid.empty? raise "Invalid model(s) specified: #{invalid.join(', ')}. Only #{SUPPORTED_MODELS.join(', ')} are supported." end def validate_live_events!(events) invalid = events - SUPPORTED_LIVE_EVENTS return if invalid.empty? raise "Invalid live event(s) specified: #{invalid.join(', ')}. Only #{SUPPORTED_LIVE_EVENTS.join(', ')} are supported." end end end