coalescing_panda 4.0.11 → 4.1.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/app/assets/javascripts/coalescing_panda/canvas_batch.js.coffee +7 -5
- data/app/assets/stylesheets/coalescing_panda/application.css.scss +1 -0
- data/app/controllers/coalescing_panda/canvas_batches_controller.rb +0 -10
- data/app/controllers/coalescing_panda/lti_controller.rb +2 -2
- data/app/controllers/coalescing_panda/oauth2_controller.rb +1 -3
- data/app/models/coalescing_panda/assignment.rb +0 -2
- data/app/models/coalescing_panda/canvas_batch.rb +0 -4
- data/app/models/coalescing_panda/course.rb +0 -2
- data/app/models/coalescing_panda/group.rb +2 -3
- data/app/models/coalescing_panda/lti_account.rb +0 -1
- data/app/models/coalescing_panda/user.rb +0 -1
- data/app/models/coalescing_panda/workers/course_miner.rb +58 -141
- data/app/models/concerns/single_table_polymorphic.rb +1 -1
- data/app/views/coalescing_panda/canvas_batches/_canvas_batch.html.haml +10 -22
- data/app/views/coalescing_panda/canvas_batches/_canvas_batch_flash.html.haml +1 -1
- data/config/routes.rb +1 -3
- data/lib/coalescing_panda/controller_helpers.rb +25 -22
- data/lib/coalescing_panda/version.rb +1 -1
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/schema.rb +82 -120
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +229 -589
- data/spec/dummy/log/test.log +60790 -39979
- data/spec/models/coalescing_panda/assignment_spec.rb +0 -1
- data/spec/models/coalescing_panda/course_spec.rb +0 -5
- data/spec/models/coalescing_panda/workers/course_miner_spec.rb +10 -57
- metadata +57 -80
- data/app/assets/javascripts/bootstrap/bootstrap-datepicker.js +0 -1247
- data/app/assets/javascripts/bootstrap/bootstrap.js +0 -6
- data/app/assets/stylesheets/bootstrap/bootstrap-datepicker.css +0 -449
- data/app/assets/stylesheets/bootstrap/bootstrap-overrides.css.scss +0 -61
- data/app/assets/stylesheets/bootstrap/bootstrap-responsive.css +0 -9
- data/app/assets/stylesheets/bootstrap/bootstrap.css.scss +0 -9
- data/app/assets/stylesheets/coalescing_panda/application.css +0 -16
- data/app/assets/stylesheets/coalescing_panda/oauth2.css +0 -0
- data/app/assets/stylesheets/coalescing_panda/progress.css.scss +0 -97
- data/app/models/coalescing_panda/assignment_group.rb +0 -11
- data/app/models/coalescing_panda/group_category.rb +0 -11
- data/db/migrate/20150506183335_create_coalescing_panda_assignment_groups.rb +0 -18
- data/db/migrate/20150506192717_add_assignment_group_id_to_assignments.rb +0 -5
- data/db/migrate/20150526144713_add_account_to_canvas_batches.rb +0 -5
- data/db/migrate/20150602205257_add_option_to_canvas_batches.rb +0 -5
- data/db/migrate/20150708192717_add_group_moderator_to_group_memberships.rb +0 -5
- data/db/migrate/20150709192717_add_leader_id_to_groups.rb +0 -6
- data/db/migrate/20150714205405_create_coalescing_panda_group_categories.rb +0 -16
- data/db/migrate/20160521205405_add_graded_at_to_coalescing_panda_submissions.rb +0 -5
- data/lib/coalescing_panda/bearcat_uri.rb +0 -20
- data/spec/factories/assignment_groups.rb +0 -14
- data/spec/models/coalescing_panda/assignment_group_spec.rb +0 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 049b4be87e2a94883db4321af5513b3de1292d95
|
4
|
+
data.tar.gz: 18cb2a2a9965e4d80e96e8bb5c2d342c4a1e7911
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a71dcd02c993571e306894b3ba2a19ece4877ef108fb7475811a113d7c1442d38f2dfea109c94fb17dd43f5a63e18300dcfd805fc5411cc302fc3118858ade05
|
7
|
+
data.tar.gz: 38e4fc6d47585987d5611fa1544ca6792d357de01c9c8b18d169cd9fca210635434c4f161410de7c04ef367454d3642e77f16667927550805e4c383e108c3f06
|
@@ -7,7 +7,7 @@ window.CoalescingPanda.CanvasBatchProgress = class CanvasBatchProgress
|
|
7
7
|
batch = $('#batch-progress').data('batch')
|
8
8
|
url = $('#batch-progress').data('url')
|
9
9
|
window.clearPath = $('#batch-progress').data('clear-path')
|
10
|
-
if batch &&
|
10
|
+
if batch && batch.status != "Completed" || batch.status != "Error"
|
11
11
|
window.batchInterval = setInterval(getBatchStatus, 3000, batch.id, url, successCallback, errorCallback)
|
12
12
|
|
13
13
|
getBatchStatus = (id, url, successCallback, errorCallback) ->
|
@@ -17,14 +17,12 @@ window.CoalescingPanda.CanvasBatchProgress = class CanvasBatchProgress
|
|
17
17
|
$('#batch-progress').html(data)
|
18
18
|
setFlashMessages()
|
19
19
|
batch = $('#batch-info').data('batch')
|
20
|
-
if batch
|
20
|
+
if batch.status == "Completed"
|
21
21
|
clearIntervalAndBatch(data, batch)
|
22
22
|
successCallback() if successCallback != undefined
|
23
|
-
else if batch
|
23
|
+
else if batch.status == 'Error'
|
24
24
|
clearIntervalAndBatch(data, batch)
|
25
25
|
errorCallback() if errorCallback != undefined
|
26
|
-
else if batch && batch.status == "Canceled"
|
27
|
-
clearIntervalAndBatch(data, batch)
|
28
26
|
|
29
27
|
error: (message) ->
|
30
28
|
$('#batch-progress').html('Batch status request failed')
|
@@ -47,3 +45,7 @@ window.CoalescingPanda.CanvasBatchProgress = class CanvasBatchProgress
|
|
47
45
|
if window.messages != undefined
|
48
46
|
for key of window.messages
|
49
47
|
$(".batch-message-#{key}").text("#{window.messages[key]}")
|
48
|
+
|
49
|
+
$ ->
|
50
|
+
$("#batch-container").unbind().bind "batchStarted", (event, data) ->
|
51
|
+
new CanvasBatchProgress()
|
@@ -0,0 +1 @@
|
|
1
|
+
@import "launch";
|
@@ -7,16 +7,6 @@ module CoalescingPanda
|
|
7
7
|
render @batch
|
8
8
|
end
|
9
9
|
|
10
|
-
def retrigger
|
11
|
-
@batch = CanvasBatch.find(params[:id])
|
12
|
-
@batch.status = 'Queued'
|
13
|
-
@batch.save
|
14
|
-
worker = CoalescingPanda::Workers::CourseMiner.new(@batch.context, @batch.options)
|
15
|
-
session[:canvas_batch_id] = worker.batch.id
|
16
|
-
worker.start(true)
|
17
|
-
redirect_to :back
|
18
|
-
end
|
19
|
-
|
20
10
|
def clear_batch_session
|
21
11
|
session[:canvas_batch_id] = nil
|
22
12
|
render nothing: true
|
@@ -51,12 +51,12 @@ module CoalescingPanda
|
|
51
51
|
if %w(course account user).include?(name)
|
52
52
|
tail = '_navigation' unless name.include? '_navigation'
|
53
53
|
end
|
54
|
-
(
|
54
|
+
(name+tail).to_sym
|
55
55
|
end
|
56
56
|
|
57
57
|
def ext_params(options)
|
58
58
|
url = options.delete(:url)
|
59
|
-
options[:url] = main_app.send(
|
59
|
+
options[:url] = main_app.send(url+'_url')
|
60
60
|
options
|
61
61
|
end
|
62
62
|
|
@@ -13,9 +13,7 @@ module CoalescingPanda
|
|
13
13
|
client_key = lti_account.oauth2_client_key
|
14
14
|
user_id = params[:user_id]
|
15
15
|
api_domain = params[:api_domain]
|
16
|
-
|
17
|
-
Rails.logger.info "Creating Bearcat client for auth token retrieval pointed to: #{prefix}"
|
18
|
-
client = Bearcat::Client.new(prefix: prefix)
|
16
|
+
client = Bearcat::Client.new(prefix: oauth2_protocol+'://'+api_domain)
|
19
17
|
token = client.retrieve_token(client_id, coalescing_panda.oauth2_redirect_url, client_key, params['code'])
|
20
18
|
CanvasApiAuth.where('user_id = ? and api_domain = ?', user_id, api_domain).first_or_create do |auth|
|
21
19
|
auth.api_token = token
|
@@ -1,8 +1,6 @@
|
|
1
1
|
module CoalescingPanda
|
2
2
|
class Assignment < ActiveRecord::Base
|
3
3
|
belongs_to :course, foreign_key: :coalescing_panda_course_id, class_name: 'CoalescingPanda::Course'
|
4
|
-
belongs_to :assignment_group, foreign_key: :coalescing_panda_assignment_group_id, class_name: 'CoalescingPanda::AssignmentGroup'
|
5
|
-
belongs_to :group_category, foreign_key: :coalescing_panda_group_category_id, class_name: 'CoalescingPanda::GroupCategory'
|
6
4
|
has_many :submissions, foreign_key: :coalescing_panda_assignment_id, class_name: 'CoalescingPanda::Submission', dependent: :destroy
|
7
5
|
|
8
6
|
delegate :account, to: :course
|
@@ -1,10 +1,6 @@
|
|
1
1
|
module CoalescingPanda
|
2
2
|
class CanvasBatch < ActiveRecord::Base
|
3
|
-
serialize :options
|
4
|
-
|
5
|
-
belongs_to :account, foreign_key: :coalescing_panda_lti_account_id, class_name: 'CoalescingPanda::LtiAccount'
|
6
3
|
belongs_to :context, polymorphic: true
|
7
|
-
|
8
4
|
default_scope { order('created_at DESC') }
|
9
5
|
end
|
10
6
|
end
|
@@ -8,10 +8,8 @@ module CoalescingPanda
|
|
8
8
|
has_many :submissions, through: :assignments, dependent: :destroy
|
9
9
|
has_many :users, through: :sections, source: :users, class_name: 'CoalescingPanda::User'
|
10
10
|
has_many :groups, :as => :context, class_name: 'CoalescingPanda::Group', dependent: :destroy
|
11
|
-
has_many :group_categories, :as => :context, class_name: 'CoalescingPanda::GroupCategory', dependent: :destroy
|
12
11
|
has_many :group_memberships, through: :groups, source: :group_memberships, class_name: 'CoalescingPanda::GroupMembership', dependent: :destroy
|
13
12
|
has_many :canvas_batches, as: :context, dependent: :destroy
|
14
|
-
has_many :assignment_groups, foreign_key: :coalescing_panda_course_id, class_name: 'CoalescingPanda::AssignmentGroup', dependent: :destroy
|
15
13
|
|
16
14
|
validates :coalescing_panda_lti_account_id, presence: true
|
17
15
|
validates :canvas_course_id, presence: true
|
@@ -3,10 +3,9 @@ module CoalescingPanda
|
|
3
3
|
belongs_to :context, :polymorphic => true
|
4
4
|
include SingleTablePolymorphic
|
5
5
|
|
6
|
-
|
7
|
-
belongs_to :group_category, foreign_key: :coalescing_panda_group_category_id, class_name: 'CoalescingPanda::GroupCategory'
|
8
|
-
has_many :group_memberships, foreign_key: :coalescing_panda_group_id, class_name: 'CoalescingPanda::GroupMembership', dependent: :destroy
|
6
|
+
has_many :group_memberships, dependent: :destroy, foreign_key: :coalescing_panda_group_id, class_name: 'CoalescingPanda::GroupMembership', dependent: :destroy
|
9
7
|
validates :group_category_id, presence: true
|
10
8
|
validates :canvas_group_id, presence: true
|
9
|
+
validates :coalescing_panda_user_id, presence: true
|
11
10
|
end
|
12
11
|
end
|
@@ -6,7 +6,6 @@ module CoalescingPanda
|
|
6
6
|
has_many :terms, foreign_key: :coalescing_panda_lti_account_id, class_name: 'CoalescingPanda::Term'
|
7
7
|
has_many :courses, foreign_key: :coalescing_panda_lti_account_id, class_name: 'CoalescingPanda::Course'
|
8
8
|
has_many :users, foreign_key: :coalescing_panda_lti_account_id, class_name: 'CoalescingPanda::User'
|
9
|
-
has_many :canvas_batches, foreign_key: :coalescing_panda_lti_account_id, class_name: 'CoalescingPanda::CanvasBatch'
|
10
9
|
has_many :sections, through: :courses
|
11
10
|
has_many :enrollments, through: :sections
|
12
11
|
has_many :assignments, through: :courses
|
@@ -3,7 +3,6 @@ module CoalescingPanda
|
|
3
3
|
belongs_to :account, foreign_key: :coalescing_panda_lti_account_id, class_name: 'CoalescingPanda::LtiAccount'
|
4
4
|
has_many :enrollments, foreign_key: :coalescing_panda_user_id, class_name: 'CoalescingPanda::Enrollment', dependent: :destroy
|
5
5
|
has_many :submissions, foreign_key: :coalescing_panda_user_id, class_name: 'CoalescingPanda::Submission', dependent: :destroy
|
6
|
-
has_many :leader_groups, foreign_key: :leader_id, class_name: 'CoalescingPanda::Group'
|
7
6
|
has_many :sections, through: :enrollments
|
8
7
|
has_many :courses, through: :sections
|
9
8
|
|
@@ -1,54 +1,32 @@
|
|
1
1
|
class CoalescingPanda::Workers::CourseMiner
|
2
|
-
SUPPORTED_MODELS = [:sections, :users, :enrollments, :
|
3
|
-
COMPLETED_STATUSES = ['Completed', 'Error']
|
4
|
-
RUNNING_STATUSES = ['Queued', 'Started']
|
2
|
+
SUPPORTED_MODELS = [:sections, :users, :enrollments, :assignments, :submissions, :groups, :group_memberships] #ORDER MATTERS!!
|
5
3
|
|
6
|
-
attr_accessor :options, :account, :course, :batch, :course_section_ids, :enrollment_ids, :assignment_ids, :
|
4
|
+
attr_accessor :options, :account, :course, :batch, :course_section_ids, :enrollment_ids, :assignment_ids, :group_ids, :user_ids
|
7
5
|
|
8
6
|
def initialize(course, options = [])
|
9
7
|
@course = course
|
10
8
|
@account = course.account
|
11
9
|
@options = options
|
12
|
-
@batch =
|
10
|
+
@batch = CoalescingPanda::CanvasBatch.create(context: course, status: "Queued")
|
13
11
|
@course_section_ids = []
|
14
12
|
@enrollment_ids = []
|
15
13
|
@assignment_ids = []
|
16
|
-
@assignment_group_ids = []
|
17
14
|
@group_ids = []
|
18
|
-
@group_membership_ids = []
|
19
15
|
@user_ids = []
|
20
16
|
end
|
21
17
|
|
22
|
-
def setup_batch
|
23
|
-
batch = account.canvas_batches.where(context: course).first
|
24
|
-
if batch.present? and RUNNING_STATUSES.include?(batch.status)
|
25
|
-
batch
|
26
|
-
else
|
27
|
-
batch = account.canvas_batches.create(context: course, status: "Queued")
|
28
|
-
end
|
29
|
-
batch.update_attributes(options: options)
|
30
|
-
batch
|
31
|
-
end
|
32
|
-
|
33
18
|
def api_client
|
34
19
|
@api_client ||= Bearcat::Client.new(prefix: account.settings[:base_url], token: account.settings[:account_admin_api_token])
|
35
20
|
end
|
36
21
|
|
37
|
-
def start
|
38
|
-
unless forced
|
39
|
-
return unless batch.status == 'Queued' # don't start if there is already a running job
|
40
|
-
return unless should_download?
|
41
|
-
end
|
42
|
-
|
22
|
+
def start
|
43
23
|
begin
|
44
24
|
batch.update_attributes(status: "Started", percent_complete: 0)
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
process_api_data(model_key.to_sym)
|
25
|
+
SUPPORTED_MODELS.each_with_index do |model_key, index|
|
26
|
+
index += 1
|
27
|
+
process_api_data(model_key.to_sym) if options.include?(model_key)
|
49
28
|
percent_complete = (index/(options.count.nonzero? || 1).to_f * 100).round(1)
|
50
29
|
batch.update_attributes(percent_complete: percent_complete)
|
51
|
-
index += 1
|
52
30
|
end
|
53
31
|
batch.update_attributes(status: "Completed", percent_complete: 100)
|
54
32
|
rescue => e
|
@@ -57,19 +35,8 @@ class CoalescingPanda::Workers::CourseMiner
|
|
57
35
|
end
|
58
36
|
handle_asynchronously :start
|
59
37
|
|
60
|
-
def should_download?
|
61
|
-
return true unless account.settings[:canvas_download_interval].present?
|
62
|
-
return true unless last_completed_batch = account.canvas_batches.where(context: course, status: 'Completed').order('updated_at ASC').first
|
63
|
-
should_download = last_completed_batch.updated_at < Time.zone.now - account.settings[:canvas_download_interval].minutes
|
64
|
-
batch.update_attributes(status: 'Canceled') unless should_download
|
65
|
-
should_download
|
66
|
-
end
|
67
|
-
|
68
38
|
def process_api_data(key)
|
69
39
|
case key
|
70
|
-
when :assignment_groups
|
71
|
-
collection = api_client.list_assignment_groups(course.canvas_course_id).all_pages!
|
72
|
-
sync_assignment_groups(collection)
|
73
40
|
when :sections
|
74
41
|
collection = api_client.course_sections(course.canvas_course_id).all_pages!
|
75
42
|
sync_sections(collection)
|
@@ -85,21 +52,14 @@ class CoalescingPanda::Workers::CourseMiner
|
|
85
52
|
when :submissions
|
86
53
|
collection = []
|
87
54
|
course.assignments.each do |assignment|
|
88
|
-
|
89
|
-
|
90
|
-
collection << submissions
|
91
|
-
end
|
92
|
-
rescue StandardError => e
|
93
|
-
Rails.logger.info("Failed to retrieve submissions for course #{course.id} and assignment #{assignment.id}: #{e}")
|
55
|
+
api_client.get_course_submissions(course.canvas_course_id, assignment.canvas_assignment_id).all_pages!.each do |submissions|
|
56
|
+
collection << submissions
|
94
57
|
end
|
95
58
|
end
|
96
59
|
sync_submissions(collection)
|
97
60
|
when :groups
|
98
61
|
collection = api_client.course_groups(course.canvas_course_id).all_pages!
|
99
62
|
sync_groups(collection)
|
100
|
-
when :group_categories
|
101
|
-
collection = api_client.list_group_categories('courses', course.canvas_course_id).all_pages!
|
102
|
-
sync_group_categories(collection)
|
103
63
|
when :group_memberships
|
104
64
|
collection = []
|
105
65
|
course.groups.each do |group|
|
@@ -113,40 +73,25 @@ class CoalescingPanda::Workers::CourseMiner
|
|
113
73
|
end
|
114
74
|
end
|
115
75
|
|
116
|
-
def sync_assignment_groups(collection)
|
117
|
-
collection.each do |values|
|
118
|
-
begin
|
119
|
-
values['canvas_assignment_group_id'] = values['id'].to_s
|
120
|
-
assignment_group = course.assignment_groups.where(canvas_assignment_group_id: values['canvas_assignment_group_id']).first_or_initialize
|
121
|
-
assignment_group.assign_attributes(standard_attributes(assignment_group, values))
|
122
|
-
assignment_group.save(validate: false)
|
123
|
-
assignment_group_ids << assignment_group.id
|
124
|
-
rescue => e
|
125
|
-
Rails.logger.error "Error syncing assignment group: #{values} Error: #{e}"
|
126
|
-
end
|
127
|
-
end
|
128
|
-
course.assignment_groups.where.not(id: assignment_group_ids).destroy_all
|
129
|
-
end
|
130
|
-
|
131
76
|
def sync_sections(collection)
|
132
|
-
|
133
|
-
|
77
|
+
begin
|
78
|
+
collection.each do |values|
|
134
79
|
values['course_section_id'] = values['id'].to_s
|
135
80
|
section = course.sections.where(canvas_section_id: values['course_section_id']).first_or_initialize
|
136
81
|
section.assign_attributes(standard_attributes(section, values))
|
137
82
|
section.sis_id = values['sis_section_id']
|
138
83
|
section.save(validate: false)
|
139
84
|
course_section_ids << section.id
|
140
|
-
rescue => e
|
141
|
-
Rails.logger.error "Error syncing section: #{values} Error: #{e}"
|
142
85
|
end
|
86
|
+
course.sections.where.not(id: course_section_ids).destroy_all
|
87
|
+
rescue => e
|
88
|
+
Rails.logger.error "Error syncing sections: #{e}"
|
143
89
|
end
|
144
|
-
course.sections.where.not(id: course_section_ids).destroy_all
|
145
90
|
end
|
146
91
|
|
147
92
|
def sync_users(collection)
|
148
|
-
|
149
|
-
|
93
|
+
begin
|
94
|
+
collection.each do |values|
|
150
95
|
values['canvas_user_id'] = values["id"].to_s
|
151
96
|
user = account.users.where(canvas_user_id: values['canvas_user_id']).first_or_initialize
|
152
97
|
user.coalescing_panda_lti_account_id = account.id
|
@@ -154,132 +99,104 @@ class CoalescingPanda::Workers::CourseMiner
|
|
154
99
|
user.sis_id = values['sis_user_id'].to_s
|
155
100
|
user_ids << user.id
|
156
101
|
user.save(validate: false)
|
157
|
-
rescue => e
|
158
|
-
Rails.logger.error "Error syncing user: #{values} Error: #{e}"
|
159
102
|
end
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
103
|
+
removed_users = course.users.where.not(id: user_ids)
|
104
|
+
removed_users.each do |user|
|
105
|
+
user.enrollments.each do |enrollment|
|
106
|
+
course.submissions.where(coalescing_panda_user_id: enrollment.user.id).destroy_all
|
107
|
+
enrollment.destroy
|
108
|
+
end
|
166
109
|
end
|
110
|
+
removed_users.destroy_all
|
111
|
+
rescue => e
|
112
|
+
Rails.logger.error "Error syncing users: #{e}"
|
167
113
|
end
|
168
|
-
removed_users.destroy_all
|
169
114
|
end
|
170
115
|
|
171
116
|
def sync_enrollments(collection)
|
172
|
-
|
173
|
-
|
117
|
+
begin
|
118
|
+
collection.each do |values|
|
174
119
|
values['canvas_enrollment_id'] = values['id'].to_s
|
175
|
-
|
176
|
-
enrollment = section.enrollments.where(canvas_enrollment_id: values['canvas_enrollment_id']).first_or_initialize
|
120
|
+
enrollment = course.enrollments.where(canvas_enrollment_id: values['canvas_enrollment_id']).first_or_initialize
|
177
121
|
enrollment.section = course.sections.find_by(canvas_section_id: values['course_section_id'].to_s)
|
178
122
|
enrollment.user = account.users.find_by(canvas_user_id: values['user_id'].to_s)
|
179
123
|
values['workflow_state'] = values["enrollment_state"]
|
180
124
|
values['enrollment_type'] = values['type']
|
181
125
|
enrollment.assign_attributes(standard_attributes(enrollment, values))
|
182
|
-
enrollment.save
|
126
|
+
enrollment.save(validate: false)
|
183
127
|
enrollment_ids << enrollment.id
|
184
|
-
rescue => e
|
185
|
-
Rails.logger.error "Error syncing enrollment: #{values} Error: #{e}"
|
186
128
|
end
|
129
|
+
removed_enrollments = course.enrollments.where.not(id: enrollment_ids)
|
130
|
+
removed_enrollments.each do |enrollment|
|
131
|
+
course.submissions.where(coalescing_panda_user_id: enrollment.user.id).destroy_all
|
132
|
+
end
|
133
|
+
removed_enrollments.destroy_all
|
134
|
+
rescue => e
|
135
|
+
Rails.logger.error "Error syncing enrollments: #{e}"
|
187
136
|
end
|
188
|
-
removed_enrollments = course.enrollments.where.not(id: enrollment_ids)
|
189
|
-
removed_enrollments.each do |enrollment|
|
190
|
-
course.submissions.where(coalescing_panda_user_id: enrollment.user.id).destroy_all
|
191
|
-
end
|
192
|
-
removed_enrollments.destroy_all
|
193
137
|
end
|
194
138
|
|
195
139
|
def sync_assignments(collection)
|
196
|
-
|
197
|
-
|
140
|
+
begin
|
141
|
+
collection.each do |values|
|
198
142
|
values['canvas_assignment_id'] = values['id'].to_s
|
199
143
|
assignment = course.assignments.where(canvas_assignment_id: values['canvas_assignment_id']).first_or_initialize
|
200
|
-
assignment_group = course.assignment_groups.find_by(canvas_assignment_group_id: values['assignment_group_id'].to_s)
|
201
|
-
group_category = course.group_categories.find_by(canvas_group_category_id: values['group_category_id'])
|
202
|
-
assignment.coalescing_panda_assignment_group_id = assignment_group.id if assignment_group
|
203
|
-
assignment.coalescing_panda_group_category_id = group_category.id if group_category
|
204
144
|
assignment.assign_attributes(standard_attributes(assignment, values))
|
205
145
|
assignment.save(validate: false)
|
206
146
|
assignment_ids << assignment.id
|
207
|
-
rescue => e
|
208
|
-
Rails.logger.error "Error syncing assignment: #{values} Error: #{e}"
|
209
147
|
end
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
148
|
+
course.assignments.where.not(id: assignment_ids).each do |assignment|
|
149
|
+
assignment.submissions.destroy_all
|
150
|
+
assignment.destroy!
|
151
|
+
end
|
152
|
+
rescue => e
|
153
|
+
Rails.logger.error "Error syncing assignments: #{e}"
|
214
154
|
end
|
215
155
|
end
|
216
156
|
|
217
157
|
def sync_submissions(collection)
|
218
|
-
|
219
|
-
|
158
|
+
begin
|
159
|
+
collection.each do |values|
|
220
160
|
values['canvas_submission_id'] = values['id'].to_s
|
221
161
|
submission = course.submissions.where(canvas_submission_id: values['canvas_submission_id']).first_or_initialize
|
222
162
|
submission.user = course.users.find_by(canvas_user_id: values['user_id'].to_s)
|
223
163
|
submission.assignment = course.assignments.find_by(canvas_assignment_id: values['assignment_id'].to_s)
|
224
164
|
submission.assign_attributes(standard_attributes(submission, values))
|
225
165
|
submission.save(validate: false)
|
226
|
-
rescue => e
|
227
|
-
Rails.logger.error "Error syncing submission: #{values} Error: #{e}"
|
228
|
-
end
|
229
|
-
end
|
230
|
-
end
|
231
|
-
|
232
|
-
def sync_group_categories(collection)
|
233
|
-
collection.each do |values|
|
234
|
-
begin
|
235
|
-
values['canvas_group_category_id'] = values['id'].to_s
|
236
|
-
values.delete('context_type') #assume only course for now
|
237
|
-
category = course.group_categories.where(canvas_group_category_id: values['canvas_group_category_id']).first_or_initialize
|
238
|
-
category.assign_attributes(standard_attributes(category, values))
|
239
|
-
category.save(validate: false)
|
240
|
-
rescue => e
|
241
|
-
Rails.logger.error "Error syncing group categories: #{values} Error: #{e.message} - #{e.backtrace}"
|
242
166
|
end
|
167
|
+
rescue => e
|
168
|
+
Rails.logger.error "Error syncing submissions: #{e}"
|
243
169
|
end
|
244
170
|
end
|
245
171
|
|
246
172
|
def sync_groups(collection)
|
247
|
-
|
248
|
-
|
173
|
+
begin
|
174
|
+
collection.each do |values|
|
249
175
|
values['canvas_group_id'] = values['id'].to_s
|
250
176
|
group = course.groups.where(canvas_group_id: values['canvas_group_id']).first_or_initialize
|
251
|
-
group_category = course.group_categories.find_by(canvas_group_category_id: values['group_category_id'])
|
252
|
-
if values['leader']
|
253
|
-
group.leader = course.users.find_by(canvas_user_id: values['leader']['id'].to_s)
|
254
|
-
else
|
255
|
-
group.leader = nil
|
256
|
-
end
|
257
|
-
group.coalescing_panda_group_category_id = group_category.id if group_category
|
258
177
|
group.assign_attributes(standard_attributes(group, values))
|
259
178
|
group.save(validate: false)
|
260
179
|
group_ids << group.id
|
261
|
-
rescue => e
|
262
|
-
Rails.logger.error "Error syncing group: #{values} Error: #{e}"
|
263
180
|
end
|
181
|
+
course.groups.where.not(id: group_ids).destroy_all
|
182
|
+
rescue => e
|
183
|
+
Rails.logger.error "Error syncing groups: #{e}"
|
264
184
|
end
|
265
|
-
course.groups.where.not(id: group_ids).destroy_all
|
266
185
|
end
|
267
186
|
|
268
187
|
def sync_group_memberships(collection)
|
269
|
-
|
270
|
-
|
188
|
+
begin
|
189
|
+
collection.each do |values|
|
271
190
|
values['canvas_group_membership_id'] = values['id'].to_s
|
272
191
|
group_membership = course.group_memberships.where(canvas_group_membership_id: values['canvas_group_membership_id']).first_or_initialize
|
273
192
|
group_membership.group = course.groups.find_by(canvas_group_id: values['group_id'].to_s)
|
274
193
|
group_membership.user = course.users.find_by(canvas_user_id: values['user_id'].to_s)
|
275
194
|
group_membership.assign_attributes(standard_attributes(group_membership, values))
|
276
195
|
group_membership.save(validate: false)
|
277
|
-
group_membership_ids << group_membership.id
|
278
|
-
rescue => e
|
279
|
-
Rails.logger.error "Error syncing group memebership: #{values} Error: #{e}"
|
280
196
|
end
|
197
|
+
rescue => e
|
198
|
+
Rails.logger.error "Error syncing group memberships: #{e}"
|
281
199
|
end
|
282
|
-
course.group_memberships.where.not(id: group_membership_ids).destroy_all
|
283
200
|
end
|
284
201
|
|
285
202
|
def standard_attributes(record, attributes)
|
@@ -287,4 +204,4 @@ class CoalescingPanda::Workers::CourseMiner
|
|
287
204
|
new_attributes.delete('id')
|
288
205
|
new_attributes.delete_if { |key, value| !record.attributes.include?(key) }
|
289
206
|
end
|
290
|
-
end
|
207
|
+
end
|
@@ -1,32 +1,20 @@
|
|
1
1
|
#batch-info{data: {batch: @batch.to_json}}
|
2
|
-
-
|
3
|
-
%
|
2
|
+
-if @batch.status == "Queued"
|
3
|
+
%h6.batch-message-queued Data is queued for download from Canvas.
|
4
4
|
|
5
|
-
-
|
5
|
+
-if @batch.status == "Completed"
|
6
6
|
.alert.alert-success
|
7
7
|
%button.close{"data-dismiss" => "alert", :type => "button"} ×
|
8
8
|
%span.batch-message-completed
|
9
9
|
Data successfully downloaded from Canvas.
|
10
10
|
|
11
|
-
-
|
12
|
-
%h6.batch-message-started
|
13
|
-
|
14
|
-
.
|
15
|
-
.bar{:style => "width: #{@batch.percent_complete}%;"}
|
11
|
+
-if @batch.status == "Started"
|
12
|
+
%h6.batch-message-started Downloading data from Canvas
|
13
|
+
.progress.progress-striped.active
|
14
|
+
.bar{:style => "width: #{@batch.percent_complete}%;"}
|
16
15
|
|
17
|
-
|
18
|
-
- if @batch.status == "Error"
|
16
|
+
-if @batch.status == "Error"
|
19
17
|
.alert.alert-block.alert-error
|
20
18
|
%button.close{"data-dismiss" => "alert", :type => "button"} ×
|
21
|
-
%
|
22
|
-
= @batch.message
|
23
|
-
|
24
|
-
- if @batch.status == "Canceled"
|
25
|
-
.alert.alert-block.alert-info
|
26
|
-
%button.close{"data-dismiss" => "alert", :type => "button"} ×
|
27
|
-
%span.batch-message-retrigger
|
28
|
-
Canvas data last downloaded
|
29
|
-
= @batch.updated_at
|
30
|
-
.pull-right
|
31
|
-
= link_to "Retrigger", retrigger_canvas_batch_path(@batch), method: :post, class: "btn btn-default"
|
32
|
-
.clearfix
|
19
|
+
%h6.batch-message-error Data failed to download from Canvas
|
20
|
+
= @batch.message
|
@@ -1,4 +1,4 @@
|
|
1
1
|
- if current_batch.present?
|
2
2
|
- path = CoalescingPanda::Engine.routes.url_helpers.canvas_batch_path(current_batch)
|
3
|
-
-
|
3
|
+
-clear_path = CoalescingPanda::Engine.routes.url_helpers.clear_batch_session_path
|
4
4
|
#batch-progress{data: {batch: current_batch.try(:to_json), url: path, clear_path: clear_path} }
|
data/config/routes.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
CoalescingPanda::Engine.routes.draw do
|
2
|
-
resources :canvas_batches, only: [:show
|
3
|
-
post :retrigger, on: :member
|
4
|
-
end
|
2
|
+
resources :canvas_batches, only: [:show]
|
5
3
|
post '/canvas_batches/clear_batch_session', as: :clear_batch_session
|
6
4
|
|
7
5
|
get '/oauth2/redirect' => 'oauth2#redirect'
|
@@ -7,21 +7,27 @@ module CoalescingPanda
|
|
7
7
|
if lti_authorize!(*roles)
|
8
8
|
user_id = params['user_id']
|
9
9
|
launch_presentation_return_url = @lti_account.settings[:launch_presentation_return_url] || params['launch_presentation_return_url']
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
uri = URI.parse(launch_presentation_return_url)
|
11
|
+
api_domain = uri.host
|
12
|
+
api_domain = "#{api_domain}:#{uri.port.to_s}" if uri.port
|
13
|
+
scheme = uri.scheme + '://'
|
14
|
+
@lti_params = params.to_hash
|
15
|
+
session['user_id'] = user_id
|
16
|
+
session['uri'] = launch_presentation_return_url
|
17
|
+
session['lis_person_sourcedid'] = params['lis_person_sourcedid']
|
18
|
+
session['oauth_consumer_key'] = params['oauth_consumer_key']
|
19
|
+
session['custom_canvas_account_id'] = params['custom_canvas_account_id']
|
13
20
|
|
14
|
-
if token = CanvasApiAuth.where('user_id = ? and api_domain = ?', user_id,
|
15
|
-
@client = Bearcat::Client.new(token: token, prefix:
|
21
|
+
if token = CanvasApiAuth.where('user_id = ? and api_domain = ?', user_id, api_domain).pluck(:api_token).first
|
22
|
+
@client = Bearcat::Client.new(token: token, prefix: scheme+api_domain)
|
16
23
|
elsif @lti_account = params['oauth_consumer_key'] && LtiAccount.find_by_key(params['oauth_consumer_key'])
|
17
24
|
client_id = @lti_account.oauth2_client_id
|
18
|
-
client = Bearcat::Client.new(prefix:
|
25
|
+
client = Bearcat::Client.new(prefix: scheme+api_domain)
|
19
26
|
session['state'] = SecureRandom.hex(32)
|
20
|
-
|
21
|
-
|
22
|
-
|
27
|
+
@canvas_url = client.auth_redirect_url(client_id,
|
28
|
+
coalescing_panda.oauth2_redirect_url({key: params['oauth_consumer_key'],
|
29
|
+
user_id: user_id, api_domain: api_domain, state: session['state']}))
|
23
30
|
#delete the added params so the original oauth sig still works
|
24
|
-
@lti_params = params.to_hash
|
25
31
|
@lti_params.delete('action')
|
26
32
|
@lti_params.delete('controller')
|
27
33
|
render 'coalescing_panda/oauth2/oauth2', layout: 'coalescing_panda/application'
|
@@ -29,14 +35,6 @@ module CoalescingPanda
|
|
29
35
|
end
|
30
36
|
end
|
31
37
|
|
32
|
-
def set_session(launch_presentation_return_url)
|
33
|
-
session['user_id'] = params['user_id']
|
34
|
-
session['uri'] = launch_presentation_return_url
|
35
|
-
session['lis_person_sourcedid'] = params['lis_person_sourcedid']
|
36
|
-
session['oauth_consumer_key'] = params['oauth_consumer_key']
|
37
|
-
session['custom_canvas_account_id'] = params['custom_canvas_account_id']
|
38
|
-
end
|
39
|
-
|
40
38
|
def have_session?
|
41
39
|
if params['tool_consumer_instance_guid'] && session['user_id'] != params['user_id']
|
42
40
|
reset_session
|
@@ -45,9 +43,12 @@ module CoalescingPanda
|
|
45
43
|
end
|
46
44
|
|
47
45
|
if (session['user_id'] && session['uri'])
|
48
|
-
uri =
|
49
|
-
|
50
|
-
|
46
|
+
uri = URI.parse(session['uri'])
|
47
|
+
api_domain = uri.host
|
48
|
+
api_domain = "#{api_domain}:#{uri.port.to_s}" if uri.port
|
49
|
+
scheme = uri.scheme + '://'
|
50
|
+
token = CanvasApiAuth.where('user_id = ? and api_domain = ?', session['user_id'], api_domain).pluck(:api_token).first
|
51
|
+
@client = Bearcat::Client.new(token: token, prefix: scheme+api_domain) if token
|
51
52
|
end
|
52
53
|
|
53
54
|
@lti_account = LtiAccount.find_by_key(session['oauth_consumer_key']) if session['oauth_consumer_key']
|
@@ -66,7 +67,9 @@ module CoalescingPanda
|
|
66
67
|
logger.info 'not authorized on roles' if !authorized
|
67
68
|
authorized = authorized && @lti_account.validate_nonce(params['oauth_nonce'], DateTime.strptime(params['oauth_timestamp'], '%s'))
|
68
69
|
logger.info 'not authorized on nonce' if !authorized
|
69
|
-
|
70
|
+
if !authorized
|
71
|
+
render :text => 'Invalid Credentials, please contact your Administrator.', :status => :unauthorized
|
72
|
+
end
|
70
73
|
authorized
|
71
74
|
end
|
72
75
|
|