require "bearcat" require "canvas_sync/version" require "canvas_sync/engine" require "canvas_sync/job" 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_assignments_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" 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 terms enrollments sections assignments roles admins xlist].freeze SUPPORTED_LIVE_EVENTS = %w[course enrollment submission assignment user syllabus grade].freeze # 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, 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. def self.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 chain of ordered jobs # # See the README for usage and examples # # @param job_chain [Hash] def self.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 self.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 # 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 self.default_provisioning_report_chain(models, term_scope=nil, legacy_support=false, account_id=nil) term_scope = term_scope.to_s if term_scope # Always sync Terms first jobs = [{ job: CanvasSync::Jobs::SyncTermsJob.to_s, options: {} }] if models.include?('users') && term_scope.present? # Sync all users first when scoping by term, because users cannot be scoped to term jobs.push({ job: CanvasSync::Jobs::SyncUsersJob.to_s, options: {} }) models = models - ['users'] end if models.include?('roles') # Sync all roles first when scoping by term, because roles cannot be scoped to term jobs.push({ job: CanvasSync::Jobs::SyncRolesJob.to_s, options: {} }) models = models - ['roles'] end if models.include?('admins') # Sync all admins first when scoping by term, because admins cannot be scoped to term jobs.push({ job: CanvasSync::Jobs::SyncAdminsJob.to_s, options: {} }) models = models - ['admins'] end jobs.push(job: CanvasSync::Jobs::SyncProvisioningReportJob.to_s, options: { term_scope: term_scope, models: models }) if models.include?("assignments") jobs.push(job: CanvasSync::Jobs::SyncAssignmentsJob.to_s, options: {}) end 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 self.get_canvas_sync_client(options) if options[:account_id] canvas_sync_client(options[:account_id]) else canvas_sync_client end end private def self.validate_models!(models) invalid = models - SUPPORTED_MODELS if invalid.length > 0 raise "Invalid model(s) specified: #{invalid.join(', ')}. Only #{SUPPORTED_MODELS.join(', ')} are supported." end end def self.validate_live_events!(events) invalid = events - SUPPORTED_LIVE_EVENTS if invalid.length > 0 raise "Invalid live event(s) specified: #{invalid.join(', ')}. Only #{SUPPORTED_LIVE_EVENTS.join(', ')} are supported." end end end