canvas_sync 0.17.0.beta11 → 0.17.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +14 -0
- data/db/migrate/20201030210836_add_full_sync_to_canvas_sync_sync_batch.rb +7 -0
- data/lib/canvas_sync.rb +40 -7
- data/lib/canvas_sync/importers/bulk_importer.rb +9 -6
- data/lib/canvas_sync/job_batches/chain_builder.rb +22 -2
- data/lib/canvas_sync/jobs/begin_sync_chain_job.rb +51 -3
- data/lib/canvas_sync/jobs/report_starter.rb +1 -0
- data/lib/canvas_sync/processors/assignment_groups_processor.rb +3 -2
- data/lib/canvas_sync/processors/assignments_processor.rb +3 -2
- data/lib/canvas_sync/processors/context_module_items_processor.rb +3 -2
- data/lib/canvas_sync/processors/context_modules_processor.rb +3 -2
- data/lib/canvas_sync/processors/normal_processor.rb +2 -1
- data/lib/canvas_sync/processors/provisioning_report_processor.rb +10 -2
- data/lib/canvas_sync/processors/submissions_processor.rb +3 -2
- data/lib/canvas_sync/version.rb +1 -1
- data/spec/canvas_sync/canvas_sync_spec.rb +10 -10
- data/spec/canvas_sync/jobs/sync_provisioning_report_job_spec.rb +5 -7
- data/spec/dummy/db/schema.rb +4 -1
- data/spec/dummy/log/development.log +474 -0
- data/spec/dummy/log/test.log +14962 -0
- metadata +5 -5
- data/lib/canvas_sync/concerns/role/launch_querying.rb +0 -42
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d8629b92b654035d7c4480345bbf938d385b186c66acd5b4e30f8d76d6dcb109
|
4
|
+
data.tar.gz: 0ef5bae856795745c2f8c8f845533148028076df62f76a395b315ae8b55679ff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c08b8308faa3af77ba3828ef122d20e08d2dcf9f5a57e26f2274f000c23fa7d870638e9c2c37256139db6e63d9bc21e7b1497d5b257696c11c224ec9c77ae92e
|
7
|
+
data.tar.gz: 63ddbbaf804dd4e547885c9817c7793646fae10c6f54d76bcf82643cc821d3f72c2a3b76a085e7720c1ec50360cd327f8498481c2d6d04cd829677d0d732259a
|
data/README.md
CHANGED
@@ -103,6 +103,20 @@ It may be one of the following values:
|
|
103
103
|
* An ISO-8601 Date - Will pass the supplied date ad the `updated_after` param for the requested reports
|
104
104
|
* `true` (Default) - Will use the start date of the last successful sync
|
105
105
|
|
106
|
+
If `updated_after` is true, CanvasSync will, by default, perform a full sync every other Sunday.
|
107
|
+
This logic can be customized by passing `full_sync_every` parameter.
|
108
|
+
If you pass a date to `updated_after`, this logic will be disabled unless you explicitly pass a `full_sync_every` parameter.
|
109
|
+
`full_sync_every` accepts the following format strings:
|
110
|
+
- `15%` - Each sync will have a 15% chance of running a full sync
|
111
|
+
- `10 days` - A full sync will be run every 10 days
|
112
|
+
- `sunday` - A full sync will run every Sunday
|
113
|
+
- `saturday/4` - A full sync will run every fourth Saturday
|
114
|
+
|
115
|
+
#### Multiple Sync Chains
|
116
|
+
If your app uses multiple Sync Chains, you may run into issues with the automatic `updated_after` and `full_sync_every` logic.
|
117
|
+
You can fix this by using custom logic or by setting the `batch_genre` parameter when creating the Job Chain. Chains will only
|
118
|
+
use chains of the same genre when computing `updated_after` and `full_sync_every`.
|
119
|
+
|
106
120
|
### Extensible chain
|
107
121
|
It is sometimes desired to extend or customize the chain of jobs that are run with CanvasSync.
|
108
122
|
This can be achieved with the following pattern:
|
@@ -0,0 +1,7 @@
|
|
1
|
+
class AddFullSyncToCanvasSyncSyncBatch < CanvasSync::MiscHelper::MigrationClass
|
2
|
+
def change
|
3
|
+
add_column :canvas_sync_sync_batches, :full_sync, :boolean, default: false
|
4
|
+
add_column :canvas_sync_sync_batches, :batch_genre, :string
|
5
|
+
add_column :canvas_sync_sync_batches, :batch_bid, :string
|
6
|
+
end
|
7
|
+
end
|
data/lib/canvas_sync.rb
CHANGED
@@ -42,6 +42,22 @@ module CanvasSync
|
|
42
42
|
xlist
|
43
43
|
].freeze
|
44
44
|
|
45
|
+
SUPPORTED_TERM_SCOPE_MODELS = %w[
|
46
|
+
assignments
|
47
|
+
submissions
|
48
|
+
assignment_groups
|
49
|
+
context_modules
|
50
|
+
context_module_items
|
51
|
+
].freeze
|
52
|
+
|
53
|
+
DEFAULT_TERM_SCOPE_MODELS = %w[
|
54
|
+
assignments
|
55
|
+
submissions
|
56
|
+
assignment_groups
|
57
|
+
context_modules
|
58
|
+
context_module_items
|
59
|
+
].freeze
|
60
|
+
|
45
61
|
SUPPORTED_LIVE_EVENTS = %w[
|
46
62
|
course
|
47
63
|
enrollment
|
@@ -106,7 +122,17 @@ module CanvasSync
|
|
106
122
|
# @param account_id [Integer, nil] This optional parameter can be used if your Term creation and
|
107
123
|
# canvas_sync_client methods require an account ID.
|
108
124
|
# @return [Hash]
|
109
|
-
def default_provisioning_report_chain(
|
125
|
+
def default_provisioning_report_chain(
|
126
|
+
models,
|
127
|
+
term_scope: nil,
|
128
|
+
term_scoped_models: DEFAULT_TERM_SCOPE_MODELS,
|
129
|
+
legacy_support: false,
|
130
|
+
account_id: nil,
|
131
|
+
updated_after: nil,
|
132
|
+
full_sync_every: nil,
|
133
|
+
batch_genre: nil,
|
134
|
+
options: {}
|
135
|
+
) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/LineLength
|
110
136
|
return unless models.present?
|
111
137
|
models.map! &:to_s
|
112
138
|
term_scope = term_scope.to_s if term_scope
|
@@ -156,6 +182,10 @@ module CanvasSync
|
|
156
182
|
try_add_model_job.call('roles')
|
157
183
|
try_add_model_job.call('admins')
|
158
184
|
|
185
|
+
(SUPPORTED_TERM_SCOPE_MODELS - term_scoped_models).each do |mdl|
|
186
|
+
try_add_model_job.call(mdl)
|
187
|
+
end
|
188
|
+
|
159
189
|
###############################
|
160
190
|
# Per-term provisioning jobs
|
161
191
|
###############################
|
@@ -165,11 +195,9 @@ module CanvasSync
|
|
165
195
|
current_chain << per_term_chain
|
166
196
|
current_chain = per_term_chain
|
167
197
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
try_add_model_job.call('context_modules')
|
172
|
-
try_add_model_job.call('context_module_items')
|
198
|
+
term_scoped_models.each do |mdl|
|
199
|
+
try_add_model_job.call(mdl)
|
200
|
+
end
|
173
201
|
|
174
202
|
current_chain.insert(
|
175
203
|
generate_provisioning_jobs(models, options, only_split: ['users'])
|
@@ -179,7 +207,12 @@ module CanvasSync
|
|
179
207
|
# Wrap it all up
|
180
208
|
###############################
|
181
209
|
|
182
|
-
global_options = {
|
210
|
+
global_options = {
|
211
|
+
legacy_support: legacy_support,
|
212
|
+
updated_after: updated_after,
|
213
|
+
full_sync_every: full_sync_every,
|
214
|
+
batch_genre: batch_genre,
|
215
|
+
}
|
183
216
|
global_options[:account_id] = account_id if account_id.present?
|
184
217
|
global_options.merge!(options[:global]) if options[:global].present?
|
185
218
|
|
@@ -28,7 +28,7 @@ module CanvasSync
|
|
28
28
|
database_column_names = mapping.values.map { |value| value[:database_column_name] }
|
29
29
|
rows = []
|
30
30
|
row_ids = {}
|
31
|
-
database_conflict_column_name =
|
31
|
+
database_conflict_column_name = conflict_target ? mapping[conflict_target][:database_column_name] : nil
|
32
32
|
|
33
33
|
CSV.foreach(report_file_path, headers: true, header_converters: :symbol) do |row|
|
34
34
|
row = yield(row) if block_given?
|
@@ -64,13 +64,12 @@ module CanvasSync
|
|
64
64
|
columns = columns.dup
|
65
65
|
|
66
66
|
update_conditions = {
|
67
|
-
condition: condition_sql(klass, columns),
|
67
|
+
condition: condition_sql(klass, columns, import_args[:sync_start_time]),
|
68
68
|
columns: columns
|
69
69
|
}
|
70
|
-
update_conditions[:conflict_target] = conflict_target if conflict_target
|
70
|
+
update_conditions[:conflict_target] = conflict_target if conflict_target
|
71
71
|
|
72
72
|
options = { validate: false, on_duplicate_key_update: update_conditions }.merge(import_args)
|
73
|
-
|
74
73
|
options.delete(:on_duplicate_key_update) if options.key?(:on_duplicate_key_ignore)
|
75
74
|
klass.import(columns, rows, options)
|
76
75
|
end
|
@@ -85,10 +84,14 @@ module CanvasSync
|
|
85
84
|
# started_at = Time.now
|
86
85
|
# run_the_users_sync!
|
87
86
|
# changed = User.where("updated_at >= ?", started_at)
|
88
|
-
def self.condition_sql(klass, columns)
|
87
|
+
def self.condition_sql(klass, columns, report_start)
|
89
88
|
columns_str = columns.map { |c| "#{klass.quoted_table_name}.#{c}" }.join(", ")
|
90
89
|
excluded_str = columns.map { |c| "EXCLUDED.#{c}" }.join(", ")
|
91
|
-
"(#{columns_str}) IS DISTINCT FROM (#{excluded_str})"
|
90
|
+
condition_sql = "(#{columns_str}) IS DISTINCT FROM (#{excluded_str})"
|
91
|
+
if klass.column_names.include?("updated_at") && report_start
|
92
|
+
condition_sql += " AND #{klass.quoted_table_name}.updated_at < '#{report_start}'"
|
93
|
+
end
|
94
|
+
condition_sql
|
92
95
|
end
|
93
96
|
|
94
97
|
def self.batch_size
|
@@ -60,10 +60,12 @@ module CanvasSync
|
|
60
60
|
raise "Could not find a \"#{relative_to}\" job in the chain" if matching_jobs.count == 0
|
61
61
|
raise "Found multiple \"#{relative_to}\" jobs in the chain" if matching_jobs.count > 1
|
62
62
|
|
63
|
-
|
64
|
-
|
63
|
+
relative_job, sub_index = matching_jobs[0]
|
64
|
+
parent_job = find_parent_job(relative_job)
|
65
65
|
needed_parent_type = placement == :with ? ConcurrentBatchJob : SerialBatchJob
|
66
66
|
|
67
|
+
chain = self.class.get_chain_parameter(parent_job)
|
68
|
+
|
67
69
|
if parent_job[:job] != needed_parent_type
|
68
70
|
old_job = chain[sub_index]
|
69
71
|
parent_job = chain[sub_index] = {
|
@@ -129,6 +131,24 @@ module CanvasSync
|
|
129
131
|
end
|
130
132
|
end
|
131
133
|
|
134
|
+
def find_parent_job(job_def)
|
135
|
+
iterate_job_tree do |job, path|
|
136
|
+
return path[-1] if job == job_def
|
137
|
+
end
|
138
|
+
nil
|
139
|
+
end
|
140
|
+
|
141
|
+
def iterate_job_tree(root: self.base_job, path: [], &blk)
|
142
|
+
blk.call(root, path)
|
143
|
+
|
144
|
+
if self.class._job_type_definitions[root[:job]]
|
145
|
+
sub_jobs = self.class.get_chain_parameter(root)
|
146
|
+
sub_jobs.each_with_index do |sub_job, i|
|
147
|
+
iterate_job_tree(root: sub_job, path: [*path, root], &blk)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
132
152
|
class << self
|
133
153
|
def _job_type_definitions
|
134
154
|
@job_type_definitions ||= {}
|
@@ -1,28 +1,76 @@
|
|
1
1
|
module CanvasSync
|
2
2
|
module Jobs
|
3
3
|
class BeginSyncChainJob < CanvasSync::Job
|
4
|
+
attr_reader :globals
|
5
|
+
|
4
6
|
def perform(chain_definition, globals = {})
|
7
|
+
@globals = globals
|
8
|
+
|
5
9
|
if !globals[:updated_after].present? || globals[:updated_after] == true
|
6
|
-
last_batch = SyncBatch.where(status: 'completed').last
|
10
|
+
last_batch = SyncBatch.where(status: 'completed', batch_genre: genre).last
|
11
|
+
globals[:full_sync_every] ||= "sunday/2"
|
7
12
|
globals[:updated_after] = last_batch&.started_at&.iso8601
|
8
13
|
end
|
9
14
|
|
15
|
+
if should_full_sync?(globals[:full_sync_every])
|
16
|
+
globals[:updated_after] = nil
|
17
|
+
end
|
18
|
+
|
10
19
|
sync_batch = SyncBatch.create!(
|
11
20
|
started_at: DateTime.now,
|
12
|
-
|
21
|
+
full_sync: globals[:updated_after] == nil,
|
22
|
+
batch_genre: genre,
|
23
|
+
status: 'processing',
|
13
24
|
)
|
14
25
|
|
15
26
|
JobBatches::Batch.new.tap do |b|
|
16
|
-
b.description = "CanvasSync Root Batch"
|
27
|
+
b.description = "CanvasSync Root Batch (SyncBatch##{sync_batch.id})"
|
17
28
|
b.on(:complete, "#{self.class.to_s}.batch_completed", sync_batch_id: sync_batch.id)
|
18
29
|
b.on(:success, "#{self.class.to_s}.batch_completed", sync_batch_id: sync_batch.id)
|
19
30
|
b.context = globals
|
20
31
|
b.jobs do
|
21
32
|
JobBatches::SerialBatchJob.perform_now(chain_definition)
|
22
33
|
end
|
34
|
+
sync_batch.update(batch_bid: b.bid)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def should_full_sync?(opt)
|
39
|
+
return true unless last_full_sync.present?
|
40
|
+
return false unless opt.is_a?(String)
|
41
|
+
|
42
|
+
case r.strip
|
43
|
+
when %r{^(sunday|monday|tuesday|wednesday|thursday|friday|saturday)(?:/(\d+))?$}
|
44
|
+
m = Regexp.last_match
|
45
|
+
day = m[1]
|
46
|
+
skip = m[2] || "1"
|
47
|
+
Date.new.send(:"#{day}?") && last_full_sync.end_of_day <= (skip.to_i.weeks.ago.end_of_day)
|
48
|
+
when opt.match?(%r{^(\d+)\%$})
|
49
|
+
m = Regexp.last_match
|
50
|
+
rand(100) < m[1].to_i
|
51
|
+
when opt.match?(%r{^(\d+) ?days$})
|
52
|
+
m = Regexp.last_match
|
53
|
+
last_full_sync.end_of_day <= m[1].to_i.days.ago.end_of_day
|
54
|
+
when opt.match?(%r{^(\d+)$}) # N.days is converted to a string of seconds
|
55
|
+
m = Regexp.last_match
|
56
|
+
last_full_sync.end_of_day <= m[1].to_i.seconds.ago.end_of_day
|
57
|
+
else
|
58
|
+
false
|
23
59
|
end
|
24
60
|
end
|
25
61
|
|
62
|
+
def last_full_sync_record
|
63
|
+
@last_full_sync_record ||= SyncBatch.where(status: 'completed', full_sync: true, batch_genre: genre).last
|
64
|
+
end
|
65
|
+
|
66
|
+
def last_full_sync
|
67
|
+
last_full_sync_record&.started_at
|
68
|
+
end
|
69
|
+
|
70
|
+
def genre
|
71
|
+
globals[:batch_genre] || "default"
|
72
|
+
end
|
73
|
+
|
26
74
|
def self.batch_completed(status, options)
|
27
75
|
sbatch = SyncBatch.find(options['sync_batch_id'])
|
28
76
|
sbatch.update!(
|
@@ -11,6 +11,7 @@ module CanvasSync
|
|
11
11
|
# @return [nil]
|
12
12
|
def perform(report_name, report_params, processor, options, allow_redownloads: false)
|
13
13
|
account_id = options[:account_id] || batch_context[:account_id] || "self"
|
14
|
+
options[:sync_start_time] = DateTime.now.utc.iso8601
|
14
15
|
|
15
16
|
report_id = start_report(account_id, report_name, report_params)
|
16
17
|
# TODO: Restore report caching support (does nayone actually use it?)
|
@@ -8,15 +8,16 @@ module CanvasSync
|
|
8
8
|
# @param options [Hash]
|
9
9
|
class AssignmentGroupsProcessor < ReportProcessor
|
10
10
|
def self.process(report_file_path, _options, report_id)
|
11
|
-
new(report_file_path)
|
11
|
+
new(report_file_path, _options)
|
12
12
|
end
|
13
13
|
|
14
|
-
def initialize(report_file_path)
|
14
|
+
def initialize(report_file_path, options)
|
15
15
|
CanvasSync::Importers::BulkImporter.import(
|
16
16
|
report_file_path,
|
17
17
|
mapping[:assignment_groups][:report_columns],
|
18
18
|
AssignmentGroup,
|
19
19
|
mapping[:assignment_groups][:conflict_target].to_sym,
|
20
|
+
import_args: options
|
20
21
|
)
|
21
22
|
end
|
22
23
|
end
|
@@ -8,15 +8,16 @@ module CanvasSync
|
|
8
8
|
# @param options [Hash]
|
9
9
|
class AssignmentsProcessor < ReportProcessor
|
10
10
|
def self.process(report_file_path, _options, report_id)
|
11
|
-
new(report_file_path)
|
11
|
+
new(report_file_path, _options)
|
12
12
|
end
|
13
13
|
|
14
|
-
def initialize(report_file_path)
|
14
|
+
def initialize(report_file_path, options)
|
15
15
|
CanvasSync::Importers::BulkImporter.import(
|
16
16
|
report_file_path,
|
17
17
|
mapping[:assignments][:report_columns],
|
18
18
|
Assignment,
|
19
19
|
mapping[:assignments][:conflict_target].to_sym,
|
20
|
+
import_args: options
|
20
21
|
)
|
21
22
|
end
|
22
23
|
end
|
@@ -8,15 +8,16 @@ module CanvasSync
|
|
8
8
|
# @param options [Hash]
|
9
9
|
class ContextModuleItemsProcessor < ReportProcessor
|
10
10
|
def self.process(report_file_path, _options, report_id)
|
11
|
-
new(report_file_path)
|
11
|
+
new(report_file_path, _options)
|
12
12
|
end
|
13
13
|
|
14
|
-
def initialize(report_file_path)
|
14
|
+
def initialize(report_file_path, options)
|
15
15
|
CanvasSync::Importers::BulkImporter.import(
|
16
16
|
report_file_path,
|
17
17
|
mapping[:context_module_items][:report_columns],
|
18
18
|
ContextModuleItem,
|
19
19
|
mapping[:context_module_items][:conflict_target].to_sym,
|
20
|
+
import_args: options
|
20
21
|
)
|
21
22
|
end
|
22
23
|
end
|
@@ -8,15 +8,16 @@ module CanvasSync
|
|
8
8
|
# @param options [Hash]
|
9
9
|
class ContextModulesProcessor < ReportProcessor
|
10
10
|
def self.process(report_file_path, _options, report_id)
|
11
|
-
new(report_file_path)
|
11
|
+
new(report_file_path, _options)
|
12
12
|
end
|
13
13
|
|
14
|
-
def initialize(report_file_path)
|
14
|
+
def initialize(report_file_path, options)
|
15
15
|
CanvasSync::Importers::BulkImporter.import(
|
16
16
|
report_file_path,
|
17
17
|
mapping[:context_modules][:report_columns],
|
18
18
|
ContextModule,
|
19
19
|
mapping[:context_modules][:conflict_target].to_sym,
|
20
|
+
import_args: options
|
20
21
|
)
|
21
22
|
end
|
22
23
|
end
|
@@ -18,7 +18,8 @@ module CanvasSync
|
|
18
18
|
report_file_path,
|
19
19
|
mapping[options[:mapping].to_sym][:report_columns],
|
20
20
|
options[:klass].constantize,
|
21
|
-
conflict_target ? conflict_target.to_sym : conflict_target
|
21
|
+
conflict_target ? conflict_target.to_sym : conflict_target,
|
22
|
+
import_args: options
|
22
23
|
)
|
23
24
|
end
|
24
25
|
end
|
@@ -21,7 +21,6 @@ module CanvasSync
|
|
21
21
|
|
22
22
|
def initialize(report_file_path, options) # rubocop:disable Metrics/AbcSize
|
23
23
|
@options = options
|
24
|
-
|
25
24
|
if options[:models].length == 1
|
26
25
|
run_import(options[:models][0], report_file_path)
|
27
26
|
else
|
@@ -75,6 +74,7 @@ module CanvasSync
|
|
75
74
|
mapping[:users][:report_columns],
|
76
75
|
User,
|
77
76
|
mapping[:users][:conflict_target].to_sym,
|
77
|
+
import_args: @options
|
78
78
|
)
|
79
79
|
end
|
80
80
|
|
@@ -84,6 +84,7 @@ module CanvasSync
|
|
84
84
|
mapping[:pseudonyms][:report_columns],
|
85
85
|
Pseudonym,
|
86
86
|
mapping[:pseudonyms][:conflict_target].to_sym,
|
87
|
+
import_args: @options
|
87
88
|
)
|
88
89
|
end
|
89
90
|
|
@@ -92,7 +93,8 @@ module CanvasSync
|
|
92
93
|
report_file_path,
|
93
94
|
mapping[:accounts][:report_columns],
|
94
95
|
Account,
|
95
|
-
mapping[:accounts][:conflict_target].to_sym
|
96
|
+
mapping[:accounts][:conflict_target].to_sym,
|
97
|
+
import_args: @options
|
96
98
|
)
|
97
99
|
end
|
98
100
|
|
@@ -102,6 +104,7 @@ module CanvasSync
|
|
102
104
|
mapping[:courses][:report_columns],
|
103
105
|
Course,
|
104
106
|
mapping[:courses][:conflict_target].to_sym,
|
107
|
+
import_args: @options
|
105
108
|
)
|
106
109
|
end
|
107
110
|
|
@@ -111,6 +114,7 @@ module CanvasSync
|
|
111
114
|
mapping[:enrollments][:report_columns],
|
112
115
|
Enrollment,
|
113
116
|
mapping[:enrollments][:conflict_target].to_sym,
|
117
|
+
import_args: @options
|
114
118
|
)
|
115
119
|
end
|
116
120
|
|
@@ -120,6 +124,7 @@ module CanvasSync
|
|
120
124
|
mapping[:sections][:report_columns],
|
121
125
|
Section,
|
122
126
|
mapping[:sections][:conflict_target].to_sym,
|
127
|
+
import_args: @options
|
123
128
|
)
|
124
129
|
end
|
125
130
|
|
@@ -129,6 +134,7 @@ module CanvasSync
|
|
129
134
|
mapping[:xlist][:report_columns],
|
130
135
|
Section,
|
131
136
|
mapping[:xlist][:conflict_target].to_sym,
|
137
|
+
import_args: @options
|
132
138
|
)
|
133
139
|
end
|
134
140
|
|
@@ -138,6 +144,7 @@ module CanvasSync
|
|
138
144
|
mapping[:groups][:report_columns],
|
139
145
|
Group,
|
140
146
|
mapping[:groups][:conflict_target].to_sym,
|
147
|
+
import_args: @options
|
141
148
|
)
|
142
149
|
end
|
143
150
|
|
@@ -148,6 +155,7 @@ module CanvasSync
|
|
148
155
|
mapping[:group_memberships][:report_columns],
|
149
156
|
GroupMembership,
|
150
157
|
mapping[:group_memberships][:conflict_target].to_sym,
|
158
|
+
import_args: @options
|
151
159
|
)
|
152
160
|
end
|
153
161
|
end
|