canvas_sync 0.17.0.beta11 → 0.17.0
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 +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
|