canvas_sync 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +178 -0
- data/Rakefile +23 -0
- data/lib/canvas_sync.rb +96 -0
- data/lib/canvas_sync/generators/install_generator.rb +54 -0
- data/lib/canvas_sync/generators/templates/course.rb +8 -0
- data/lib/canvas_sync/generators/templates/create_courses.rb +21 -0
- data/lib/canvas_sync/generators/templates/create_enrollments.rb +26 -0
- data/lib/canvas_sync/generators/templates/create_sections.rb +20 -0
- data/lib/canvas_sync/generators/templates/create_terms.rb +18 -0
- data/lib/canvas_sync/generators/templates/create_users.rb +18 -0
- data/lib/canvas_sync/generators/templates/enrollment.rb +8 -0
- data/lib/canvas_sync/generators/templates/section.rb +7 -0
- data/lib/canvas_sync/generators/templates/term.rb +28 -0
- data/lib/canvas_sync/generators/templates/user.rb +6 -0
- data/lib/canvas_sync/importers/bulk_importer.rb +54 -0
- data/lib/canvas_sync/jobs/application_job.rb +25 -0
- data/lib/canvas_sync/jobs/report_checker.rb +48 -0
- data/lib/canvas_sync/jobs/report_processor_job.rb +36 -0
- data/lib/canvas_sync/jobs/report_starter.rb +29 -0
- data/lib/canvas_sync/jobs/sync_provisioning_report_job.rb +58 -0
- data/lib/canvas_sync/jobs/sync_terms_job.rb +23 -0
- data/lib/canvas_sync/jobs/sync_users_job.rb +32 -0
- data/lib/canvas_sync/processors/provisioning_report_processor.rb +118 -0
- data/lib/canvas_sync/version.rb +3 -0
- data/spec/canvas_sync/canvas_sync_spec.rb +60 -0
- data/spec/canvas_sync/jobs/report_checker_spec.rb +62 -0
- data/spec/canvas_sync/jobs/report_processor_job_spec.rb +30 -0
- data/spec/canvas_sync/jobs/report_starter_spec.rb +27 -0
- data/spec/canvas_sync/jobs/sync_provisioning_report_job_spec.rb +81 -0
- data/spec/canvas_sync/jobs/sync_terms_job_spec.rb +18 -0
- data/spec/canvas_sync/jobs/sync_users_job_spec.rb +18 -0
- data/spec/canvas_sync/models/course_spec.rb +30 -0
- data/spec/canvas_sync/models/enrollment_spec.rb +30 -0
- data/spec/canvas_sync/models/section_spec.rb +24 -0
- data/spec/canvas_sync/models/term_spec.rb +71 -0
- data/spec/canvas_sync/models/user_spec.rb +18 -0
- data/spec/canvas_sync/processors/provisioning_report_processor_spec.rb +41 -0
- data/spec/dummy/README.rdoc +1 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/models/application_record.rb +3 -0
- data/spec/dummy/app/models/course.rb +14 -0
- data/spec/dummy/app/models/enrollment.rb +14 -0
- data/spec/dummy/app/models/section.rb +13 -0
- data/spec/dummy/app/models/term.rb +34 -0
- data/spec/dummy/app/models/user.rb +12 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +26 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +41 -0
- data/spec/dummy/config/environments/test.rb +42 -0
- data/spec/dummy/config/initializers/assets.rb +11 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/routes.rb +2 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20170831220702_create_courses.rb +27 -0
- data/spec/dummy/db/migrate/20170831221129_create_users.rb +24 -0
- data/spec/dummy/db/migrate/20170905192509_create_enrollments.rb +32 -0
- data/spec/dummy/db/migrate/20170906193506_create_terms.rb +24 -0
- data/spec/dummy/db/migrate/20170906203438_create_sections.rb +26 -0
- data/spec/dummy/db/schema.rb +88 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +828 -0
- data/spec/dummy/log/test.log +14582 -0
- data/spec/factories/course_factory.rb +10 -0
- data/spec/factories/enrollment_factory.rb +5 -0
- data/spec/factories/section_factory.rb +5 -0
- data/spec/factories/term_factory.rb +10 -0
- data/spec/factories/user_factory.rb +9 -0
- data/spec/spec_helper.rb +46 -0
- data/spec/support/fake_canvas.rb +22 -0
- data/spec/support/fixtures/canvas_responses/terms.json +64 -0
- data/spec/support/fixtures/reports/courses.csv +3 -0
- data/spec/support/fixtures/reports/enrollments.csv +3 -0
- data/spec/support/fixtures/reports/provisioning_csv +0 -0
- data/spec/support/fixtures/reports/provisioning_csv_unzipped/courses.csv +3 -0
- data/spec/support/fixtures/reports/provisioning_csv_unzipped/users.csv +4 -0
- data/spec/support/fixtures/reports/sections.csv +3 -0
- data/spec/support/fixtures/reports/users.csv +4 -0
- metadata +423 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
<%= autogenerated_migration_warning %>
|
2
|
+
|
3
|
+
class CreateUsers < ActiveRecord::Migration[5.1]
|
4
|
+
def change
|
5
|
+
create_table :users do |t|
|
6
|
+
t.bigint :canvas_user_id, null: false
|
7
|
+
t.string :sis_id
|
8
|
+
t.string :email
|
9
|
+
t.string :first_name
|
10
|
+
t.string :last_name
|
11
|
+
t.string :status
|
12
|
+
|
13
|
+
t.timestamps
|
14
|
+
end
|
15
|
+
|
16
|
+
add_index :users, :canvas_user_id, unique: true
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
<%= autogenerated_model_warning %>
|
2
|
+
|
3
|
+
class Enrollment < ApplicationRecord
|
4
|
+
validates :canvas_enrollment_id, uniqueness: true, presence: true
|
5
|
+
belongs_to :user, primary_key: :canvas_user_id, foreign_key: :canvas_user_id, optional: true
|
6
|
+
belongs_to :course, primary_key: :canvas_course_id, foreign_key: :canvas_course_id, optional: true
|
7
|
+
belongs_to :section, primary_key: :canvas_section_id, foreign_key: :canvas_section_id, optional: true
|
8
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
<%= autogenerated_model_warning %>
|
2
|
+
|
3
|
+
class Section < ApplicationRecord
|
4
|
+
validates :canvas_section_id, uniqueness: true, presence: true
|
5
|
+
belongs_to :course, primary_key: :canvas_course_id, foreign_key: :canvas_course_id, optional: true
|
6
|
+
has_many :enrollments, primary_key: :canvas_section_id, foreign_key: :canvas_section_id
|
7
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
<%= autogenerated_model_warning %>
|
2
|
+
|
3
|
+
class Term < ApplicationRecord
|
4
|
+
validates :canvas_term_id, uniqueness: true, presence: true
|
5
|
+
has_many :courses, foreign_key: :canvas_term_id, primary_key: :canvas_term_id
|
6
|
+
|
7
|
+
# This is a sample scope created by the CanvasSync gem; feel
|
8
|
+
# free to customize it for your tool's requirements.
|
9
|
+
scope :active, -> {
|
10
|
+
where(workflow_state: 'active')
|
11
|
+
.where("start_at <= ? OR start_at IS NULL", 15.days.from_now)
|
12
|
+
.where("end_at >= ? OR end_at IS NULL", 15.days.ago)
|
13
|
+
}
|
14
|
+
|
15
|
+
def self.create_or_update(term_params)
|
16
|
+
term = Term.find_or_initialize_by(canvas_term_id: term_params['id'])
|
17
|
+
|
18
|
+
term.assign_attributes(name: term_params['name'],
|
19
|
+
start_at: term_params['start_at'],
|
20
|
+
end_at: term_params['end_at'],
|
21
|
+
workflow_state: term_params['workflow_state'],
|
22
|
+
grading_period_group_id: term_params['grading_period_group_id'],
|
23
|
+
sis_id: term_params['sis_term_id'])
|
24
|
+
|
25
|
+
term.save! if term.changed?
|
26
|
+
term
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module CanvasSync
|
2
|
+
module Importers
|
3
|
+
class BulkImporter
|
4
|
+
# The batch import size can be customized by setting
|
5
|
+
# the 'BULK_IMPORTER_BATCH_SIZE' environment variable
|
6
|
+
DEFAULT_BATCH_SIZE = 10_000
|
7
|
+
|
8
|
+
# Does a bulk import of a set of models using the activerecord-import gem.
|
9
|
+
#
|
10
|
+
# @param report_file_path [String] path to the report CSV
|
11
|
+
# @param mapping [Hash] a hash of the values to import. The keys are the CSV column names and
|
12
|
+
# the values are the database column names. {CanvasSync::Processors::ProvisioningReportProcessor::USERS_CSV_MAPPING Example}
|
13
|
+
# @param klass [Object] e.g., User
|
14
|
+
# @param conflict_target [Symbol] represents the database column that will determine if we need to update
|
15
|
+
# or insert a given row. e.g.,: canvas_user_id
|
16
|
+
# @yieldparam [Array] row if a block is passed in it will yield the current row from the CSV.
|
17
|
+
# This can be used if you need to filter or massage the data in any way.
|
18
|
+
def self.import(report_file_path, mapping, klass, conflict_target)
|
19
|
+
csv_column_names = mapping.keys
|
20
|
+
database_column_names = mapping.values
|
21
|
+
rows = []
|
22
|
+
|
23
|
+
CSV.foreach(report_file_path, headers: true, header_converters: :symbol) do |row|
|
24
|
+
row = yield(row) if block_given?
|
25
|
+
next if row.nil?
|
26
|
+
rows << csv_column_names.map { |column| row[column] }
|
27
|
+
|
28
|
+
if rows.length >= batch_size
|
29
|
+
perform_import(klass, database_column_names, rows, conflict_target)
|
30
|
+
rows = []
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
perform_import(klass, database_column_names, rows, conflict_target)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def self.perform_import(klass, database_column_names, rows, conflict_target)
|
40
|
+
return if rows.length == 0
|
41
|
+
database_column_names = database_column_names.dup
|
42
|
+
klass.import(database_column_names, rows, validate: false, on_duplicate_key_update: {
|
43
|
+
conflict_target: conflict_target,
|
44
|
+
columns: database_column_names
|
45
|
+
})
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.batch_size
|
49
|
+
batch_size = ENV['BULK_IMPORTER_BATCH_SIZE'].to_i
|
50
|
+
batch_size > 0 ? batch_size : DEFAULT_BATCH_SIZE
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "active_job"
|
2
|
+
|
3
|
+
module CanvasSync
|
4
|
+
module Jobs
|
5
|
+
# Inherit from this class to build a Job that integrates
|
6
|
+
# well with this gem.
|
7
|
+
class ApplicationJob < ActiveJob::Base
|
8
|
+
# Generates a Bearcat::Client used for Canvas API calls
|
9
|
+
#
|
10
|
+
# @param canvas_base_url [String]
|
11
|
+
# @param canvas_api_token [String]
|
12
|
+
# @return [Bearcat::Client]
|
13
|
+
def canvas_client(canvas_base_url, canvas_api_token)
|
14
|
+
Bearcat::Client.new(token: canvas_api_token, prefix: canvas_base_url)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns the amount of time to wait between report status checks
|
18
|
+
#
|
19
|
+
# @return [Integer]
|
20
|
+
def report_checker_wait_time
|
21
|
+
Rails.env.development? ? 1.second : 30.seconds
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module CanvasSync
|
2
|
+
module Jobs
|
3
|
+
# ActiveJob class used to check the status of a pending Canvas report.
|
4
|
+
# Re-enqueues itself if the report is still processing on Canvas.
|
5
|
+
# Enqueues the ReportProcessor when the report has completed.
|
6
|
+
class ReportChecker < ApplicationJob
|
7
|
+
# @param canvas_base_url [String]
|
8
|
+
# @param canvas_api_token [String]
|
9
|
+
# @param job_chain [Hash]
|
10
|
+
# @param report_name [Hash] e.g., 'provisioning_csv'
|
11
|
+
# @param report_id [Integer]
|
12
|
+
# @param processor [String] a stringified report processor class name
|
13
|
+
# @param options [Hash] hash of options that will be passed to the job processor
|
14
|
+
# @return [nil]
|
15
|
+
def perform(canvas_base_url, canvas_api_token, job_chain, report_name, report_id, processor, options)
|
16
|
+
report_status = canvas_client(canvas_base_url, canvas_api_token).report_status('self', report_name, report_id)
|
17
|
+
|
18
|
+
case report_status['status'].downcase
|
19
|
+
when 'complete'
|
20
|
+
CanvasSync::Jobs::ReportProcessorJob.perform_later(
|
21
|
+
canvas_base_url,
|
22
|
+
canvas_api_token,
|
23
|
+
job_chain,
|
24
|
+
report_name,
|
25
|
+
report_status['attachment']['url'],
|
26
|
+
processor,
|
27
|
+
options
|
28
|
+
)
|
29
|
+
when 'error', 'deleted'
|
30
|
+
message = "Report failed to process; status was #{report_status} for report_name: #{report_name}, report_id: #{report_id}"
|
31
|
+
Rails.logger.error(message)
|
32
|
+
raise message
|
33
|
+
else
|
34
|
+
CanvasSync::Jobs::ReportChecker.set(wait: report_checker_wait_time)
|
35
|
+
.perform_later(
|
36
|
+
canvas_base_url,
|
37
|
+
canvas_api_token,
|
38
|
+
job_chain,
|
39
|
+
report_name,
|
40
|
+
report_id,
|
41
|
+
processor,
|
42
|
+
options
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
|
3
|
+
module CanvasSync
|
4
|
+
module Jobs
|
5
|
+
# ActiveJob class that wraps around a report processor. This job will
|
6
|
+
# download the report, and then pass the file path and options into the
|
7
|
+
# process method on the processor.
|
8
|
+
class ReportProcessorJob < ApplicationJob
|
9
|
+
# @param canvas_base_url [String]
|
10
|
+
# @param canvas_api_token [String]
|
11
|
+
# @param job_chain [Hash]
|
12
|
+
# @param report_name [Hash] e.g., 'provisioning_csv'
|
13
|
+
# @param report_url [String]
|
14
|
+
# @param processor [String] a stringified report processor class name
|
15
|
+
# @param options [Hash] hash of options that will be passed to the job processor
|
16
|
+
# @return [nil]
|
17
|
+
def perform(canvas_base_url, canvas_api_token, job_chain, report_name, report_url, processor, options)
|
18
|
+
download(report_name, report_url) do |file_path|
|
19
|
+
processor.constantize.process(file_path, options)
|
20
|
+
end
|
21
|
+
|
22
|
+
CanvasSync.invoke_next(canvas_base_url, canvas_api_token, job_chain)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def download(report_name, report_url)
|
28
|
+
Dir.mktmpdir do |dir|
|
29
|
+
file_path = "#{dir}/#{report_name}"
|
30
|
+
IO.copy_stream(open(report_url), file_path)
|
31
|
+
yield file_path
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module CanvasSync
|
2
|
+
module Jobs
|
3
|
+
# Starts a Canvas report and enqueues a ReportChecker
|
4
|
+
class ReportStarter < ApplicationJob
|
5
|
+
# @param canvas_base_url [String]
|
6
|
+
# @param canvas_api_token [String]
|
7
|
+
# @param job_chain [Hash]
|
8
|
+
# @param report_name [Hash] e.g., 'provisioning_csv'
|
9
|
+
# @param report_params [Hash] The Canvas report parameters
|
10
|
+
# @param processor [String] a stringified report processor class name
|
11
|
+
# @param options [Hash] hash of options that will be passed to the job processor
|
12
|
+
# @return [nil]
|
13
|
+
def perform(canvas_base_url, canvas_api_token, job_chain, report_name, report_params, processor, options)
|
14
|
+
client = canvas_client(canvas_base_url, canvas_api_token)
|
15
|
+
report = client.start_report('self', report_name, report_params)
|
16
|
+
|
17
|
+
CanvasSync::Jobs::ReportChecker.set(wait: report_checker_wait_time).perform_later(
|
18
|
+
canvas_base_url,
|
19
|
+
canvas_api_token,
|
20
|
+
job_chain,
|
21
|
+
report_name,
|
22
|
+
report['id'],
|
23
|
+
processor,
|
24
|
+
options
|
25
|
+
)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module CanvasSync
|
2
|
+
module Jobs
|
3
|
+
# ActiveJob class that starts a Canvas provisioning report
|
4
|
+
class SyncProvisioningReportJob < ApplicationJob
|
5
|
+
# @param canvas_base_url [String]
|
6
|
+
# @param canvas_api_token [String]
|
7
|
+
# @param job_chain [Hash]
|
8
|
+
# @param options [Hash] If options contains a :term_scope a seperate provisioning report
|
9
|
+
# will be started for each term in that scope. :models should be an array of
|
10
|
+
# models to sync.
|
11
|
+
def perform(canvas_base_url, canvas_api_token, job_chain, options)
|
12
|
+
@canvas_base_url = canvas_base_url
|
13
|
+
@canvas_api_token = canvas_api_token
|
14
|
+
@job_chain = job_chain
|
15
|
+
|
16
|
+
if options[:term_scope]
|
17
|
+
Term.send(options[:term_scope]).find_each do |term|
|
18
|
+
# Deep copy the options so each report gets the correct term id passed into
|
19
|
+
# its options with no side effects
|
20
|
+
duped_options = Marshal.load(Marshal.dump(options))
|
21
|
+
duped_options[:canvas_term_id] = term.canvas_term_id
|
22
|
+
start_report(report_params(options, term.canvas_term_id), duped_options)
|
23
|
+
end
|
24
|
+
else
|
25
|
+
start_report(report_params(options), options)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def start_report(report_params, options)
|
32
|
+
CanvasSync::Jobs::ReportStarter.perform_later(
|
33
|
+
@canvas_base_url,
|
34
|
+
@canvas_api_token,
|
35
|
+
@job_chain,
|
36
|
+
'proservices_provisioning_csv',
|
37
|
+
report_params,
|
38
|
+
CanvasSync::Processors::ProvisioningReportProcessor.to_s,
|
39
|
+
options
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
def report_params(options, canvas_term_id=nil)
|
44
|
+
params = {
|
45
|
+
"parameters[include_deleted]" => true
|
46
|
+
}
|
47
|
+
|
48
|
+
options[:models].each do |model|
|
49
|
+
params["parameters[#{model}]"] = true
|
50
|
+
end
|
51
|
+
|
52
|
+
params["parameters[enrollment_term_id]"] = canvas_term_id if canvas_term_id
|
53
|
+
|
54
|
+
params
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module CanvasSync
|
2
|
+
module Jobs
|
3
|
+
class SyncTermsJob < ApplicationJob
|
4
|
+
# Syncs Terms using the Canvas API
|
5
|
+
#
|
6
|
+
# Terms are pre-synced so that provisioning reports can be scoped to term.
|
7
|
+
#
|
8
|
+
# @param canvas_base_url [String]
|
9
|
+
# @param canvas_api_token [String]
|
10
|
+
# @param job_chain [Hash]
|
11
|
+
# @param options [Hash]
|
12
|
+
def perform(canvas_base_url, canvas_api_token, job_chain, options)
|
13
|
+
client = canvas_client(canvas_base_url, canvas_api_token)
|
14
|
+
|
15
|
+
client.terms('self').all_pages!.each do |term_params|
|
16
|
+
Term.create_or_update(term_params)
|
17
|
+
end
|
18
|
+
|
19
|
+
CanvasSync.invoke_next(canvas_base_url, canvas_api_token, job_chain)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module CanvasSync
|
2
|
+
module Jobs
|
3
|
+
class SyncUsersJob < ReportStarter
|
4
|
+
REPORT_PARAMS = {
|
5
|
+
'parameters[users]' => true,
|
6
|
+
'parameters[include_deleted]' => true
|
7
|
+
}
|
8
|
+
|
9
|
+
# Starts a provisioning report for just users.
|
10
|
+
#
|
11
|
+
# Provisioning reports do not scope users by term, so when we are
|
12
|
+
# running provisioning by term we sync users first so we don't duplicate
|
13
|
+
# the work of syncing all users for each term.
|
14
|
+
#
|
15
|
+
# @param canvas_base_url [String]
|
16
|
+
# @param canvas_api_token [String]
|
17
|
+
# @param job_chain [Hash]
|
18
|
+
# @param options [Hash]
|
19
|
+
def perform(canvas_base_url, canvas_api_token, job_chain, options)
|
20
|
+
super(
|
21
|
+
canvas_base_url,
|
22
|
+
canvas_api_token,
|
23
|
+
job_chain,
|
24
|
+
'proservices_provisioning_csv',
|
25
|
+
REPORT_PARAMS,
|
26
|
+
CanvasSync::Processors::ProvisioningReportProcessor.to_s,
|
27
|
+
{ models: ['users'] }
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'csv'
|
2
|
+
require 'activerecord-import'
|
3
|
+
require 'zip'
|
4
|
+
|
5
|
+
module CanvasSync
|
6
|
+
module Processors
|
7
|
+
class ProvisioningReportProcessor
|
8
|
+
# Used by the {CanvasSync::Importers::BulkImporter bulk importer}. The keys are
|
9
|
+
# CSV columns and the values are the database columns.
|
10
|
+
USERS_CSV_MAPPING = {
|
11
|
+
canvas_user_id: :canvas_user_id,
|
12
|
+
user_id: :sis_id,
|
13
|
+
email: :email,
|
14
|
+
first_name: :first_name,
|
15
|
+
last_name: :last_name,
|
16
|
+
status: :status
|
17
|
+
}
|
18
|
+
|
19
|
+
COURSES_CSV_MAPPING = {
|
20
|
+
canvas_course_id: :canvas_course_id,
|
21
|
+
course_id: :sis_id,
|
22
|
+
short_name: :short_name,
|
23
|
+
long_name: :long_name,
|
24
|
+
canvas_account_id: :canvas_account_id,
|
25
|
+
canvas_term_id: :canvas_term_id,
|
26
|
+
term_id: :term_sis_id,
|
27
|
+
start_date: :start_date,
|
28
|
+
end_date: :end_date
|
29
|
+
}
|
30
|
+
|
31
|
+
ENROLLMENTS_CSV_MAPPING = {
|
32
|
+
canvas_enrollment_id: :canvas_enrollment_id,
|
33
|
+
canvas_course_id: :canvas_course_id,
|
34
|
+
course_id: :course_sis_id,
|
35
|
+
canvas_user_id: :canvas_user_id,
|
36
|
+
user_id: :user_sis_id,
|
37
|
+
role: :role,
|
38
|
+
role_id: :role_id,
|
39
|
+
canvas_section_id: :canvas_section_id,
|
40
|
+
section_id: :section_sis_id,
|
41
|
+
status: :status,
|
42
|
+
base_role_type: :base_role_type
|
43
|
+
}
|
44
|
+
|
45
|
+
SECTIONS_CSV_MAPPING = {
|
46
|
+
canvas_section_id: :canvas_section_id,
|
47
|
+
section_id: :sis_id,
|
48
|
+
canvas_course_id: :canvas_course_id,
|
49
|
+
name: :name,
|
50
|
+
status: :status,
|
51
|
+
start_date: :start_date,
|
52
|
+
end_date: :end_date
|
53
|
+
}
|
54
|
+
|
55
|
+
# Processes a provisioning report using the bulk importer.
|
56
|
+
#
|
57
|
+
# options must contain a models key. If there is only one model
|
58
|
+
# Canvas downloads the single report directly as a CSV. If it's
|
59
|
+
# more than one model Canvas downloads a ZIP file, so we have to
|
60
|
+
# extract that and iterate through it for processing.
|
61
|
+
#
|
62
|
+
# @param report_file_path [String]
|
63
|
+
# @param options [Hash]
|
64
|
+
def self.process(report_file_path, options)
|
65
|
+
if options[:models].length == 1
|
66
|
+
send("process_#{options[:models][0]}", report_file_path)
|
67
|
+
else
|
68
|
+
unzipped_file_path = extract(report_file_path)
|
69
|
+
Dir[unzipped_file_path + "/*.csv"].each do |file_path|
|
70
|
+
model_name = file_path.split("/").last.split(".").first
|
71
|
+
send("process_#{model_name}", file_path)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def self.extract(file_path)
|
79
|
+
unzipped_file_path = "#{file_path}_unzipped"
|
80
|
+
|
81
|
+
Zip::File.open(file_path) do |zip_file|
|
82
|
+
zip_file.each do |f|
|
83
|
+
f_path = File.join(unzipped_file_path, f.name)
|
84
|
+
FileUtils.mkdir_p(File.dirname(f_path))
|
85
|
+
zip_file.extract(f, f_path) unless File.exist?(f_path)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
unzipped_file_path
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.process_users(report_file_path)
|
93
|
+
rows = {}
|
94
|
+
|
95
|
+
# Users can show up more than once in a report if they have multiple logins. This breaks
|
96
|
+
# the bulk import, so the block we pass into the importer filters out users we've already
|
97
|
+
# encountered.
|
98
|
+
CanvasSync::Importers::BulkImporter.import(report_file_path, USERS_CSV_MAPPING, User, :canvas_user_id) do |row|
|
99
|
+
next nil if rows[row[:canvas_user_id]]
|
100
|
+
rows[row[:canvas_user_id]] = true
|
101
|
+
row
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.process_courses(report_file_path)
|
106
|
+
CanvasSync::Importers::BulkImporter.import(report_file_path, COURSES_CSV_MAPPING, Course, :canvas_course_id)
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.process_enrollments(report_file_path)
|
110
|
+
CanvasSync::Importers::BulkImporter.import(report_file_path, ENROLLMENTS_CSV_MAPPING, Enrollment, :canvas_enrollment_id)
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.process_sections(report_file_path)
|
114
|
+
CanvasSync::Importers::BulkImporter.import(report_file_path, SECTIONS_CSV_MAPPING, Section, :canvas_section_id)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|