coalescing_panda 4.0.4 → 4.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/javascripts/coalescing_panda/canvas_batch.js.coffee +5 -7
- data/app/assets/stylesheets/coalescing_panda/progress.css.scss +97 -0
- data/app/controllers/coalescing_panda/canvas_batches_controller.rb +10 -0
- data/app/controllers/coalescing_panda/lti_controller.rb +2 -2
- data/app/controllers/coalescing_panda/oauth2_controller.rb +3 -1
- data/app/models/coalescing_panda/assignment.rb +2 -0
- data/app/models/coalescing_panda/assignment_group.rb +11 -0
- data/app/models/coalescing_panda/canvas_batch.rb +4 -0
- data/app/models/coalescing_panda/course.rb +2 -0
- data/app/models/coalescing_panda/group.rb +3 -2
- data/app/models/coalescing_panda/group_category.rb +11 -0
- data/app/models/coalescing_panda/lti_account.rb +1 -0
- data/app/models/coalescing_panda/user.rb +1 -0
- data/app/models/coalescing_panda/workers/course_miner.rb +132 -56
- data/app/models/concerns/single_table_polymorphic.rb +1 -1
- data/app/views/coalescing_panda/canvas_batches/_canvas_batch.html.haml +22 -10
- data/app/views/coalescing_panda/canvas_batches/_canvas_batch_flash.html.haml +1 -1
- data/config/routes.rb +3 -1
- data/db/migrate/20150506183335_create_coalescing_panda_assignment_groups.rb +18 -0
- data/db/migrate/20150506192717_add_assignment_group_id_to_assignments.rb +5 -0
- data/db/migrate/20150526144713_add_account_to_canvas_batches.rb +5 -0
- data/db/migrate/20150602205257_add_option_to_canvas_batches.rb +5 -0
- data/db/migrate/20150708192717_add_group_moderator_to_group_memberships.rb +5 -0
- data/db/migrate/20150709192717_add_leader_id_to_groups.rb +6 -0
- data/db/migrate/20150714205405_create_coalescing_panda_group_categories.rb +16 -0
- data/lib/coalescing_panda/bearcat_uri.rb +20 -0
- data/lib/coalescing_panda/controller_helpers.rb +22 -25
- data/lib/coalescing_panda/version.rb +1 -1
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/schema.rb +120 -82
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +628 -0
- data/spec/dummy/log/test.log +42958 -0
- data/spec/factories/assignment_groups.rb +14 -0
- data/spec/models/coalescing_panda/assignment_group_spec.rb +32 -0
- data/spec/models/coalescing_panda/assignment_spec.rb +1 -0
- data/spec/models/coalescing_panda/course_spec.rb +5 -0
- data/spec/models/coalescing_panda/workers/course_miner_spec.rb +57 -10
- metadata +25 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 426f1e67e2f5b5e67c512ddf08d4218e69195c58
|
4
|
+
data.tar.gz: 1fa762f16649d117921743c0f6d1ad08897a2a71
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 008f2c9346cf73643171662a546e5f9749beb2b0215b297b253e3e130ef64b1014b69bd39d1222f711222e68ef712a0a9454d30437b7d7fbe7635f7e38c8f1a0
|
7
|
+
data.tar.gz: b9cf5e7c0907f2e0aff5dca5ead001ab65144977ddd06ab232dee32a5c12441d8f37f06b3767a5c1bd765177b8bc0f6969727282ef77bf2e8a7dc5d767b1d0f7
|
@@ -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 && batch.status != "Completed" || batch.status != "Error"
|
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,12 +17,14 @@ window.CoalescingPanda.CanvasBatchProgress = class CanvasBatchProgress
|
|
17
17
|
$('#batch-progress').html(data)
|
18
18
|
setFlashMessages()
|
19
19
|
batch = $('#batch-info').data('batch')
|
20
|
-
if batch.status == "Completed"
|
20
|
+
if batch && batch.status == "Completed"
|
21
21
|
clearIntervalAndBatch(data, batch)
|
22
22
|
successCallback() if successCallback != undefined
|
23
|
-
else if batch.status == 'Error'
|
23
|
+
else if batch && 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)
|
26
28
|
|
27
29
|
error: (message) ->
|
28
30
|
$('#batch-progress').html('Batch status request failed')
|
@@ -45,7 +47,3 @@ window.CoalescingPanda.CanvasBatchProgress = class CanvasBatchProgress
|
|
45
47
|
if window.messages != undefined
|
46
48
|
for key of window.messages
|
47
49
|
$(".batch-message-#{key}").text("#{window.messages[key]}")
|
48
|
-
|
49
|
-
$ ->
|
50
|
-
$("#batch-container").unbind().bind "batchStarted", (event, data) ->
|
51
|
-
new CanvasBatchProgress()
|
@@ -0,0 +1,97 @@
|
|
1
|
+
.progress {
|
2
|
+
overflow: hidden;
|
3
|
+
height: 20px;
|
4
|
+
margin-bottom: 20px;
|
5
|
+
background-color: #f5f5f5;
|
6
|
+
border-radius: 4px;
|
7
|
+
-webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
|
8
|
+
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
|
9
|
+
}
|
10
|
+
.progress-bar {
|
11
|
+
float: left;
|
12
|
+
width: 0%;
|
13
|
+
height: 100%;
|
14
|
+
font-size: 12px;
|
15
|
+
line-height: 20px;
|
16
|
+
color: #ffffff;
|
17
|
+
text-align: center;
|
18
|
+
background-color: #337ab7;
|
19
|
+
-webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
|
20
|
+
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
|
21
|
+
-webkit-transition: width 0.6s ease;
|
22
|
+
-o-transition: width 0.6s ease;
|
23
|
+
transition: width 0.6s ease;
|
24
|
+
}
|
25
|
+
|
26
|
+
.alert {
|
27
|
+
padding: 8px 35px 8px 14px;
|
28
|
+
margin-bottom: 20px;
|
29
|
+
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
|
30
|
+
background-color: #fcf8e3;
|
31
|
+
border: 1px solid #fbeed5;
|
32
|
+
-webkit-border-radius: 4px;
|
33
|
+
-moz-border-radius: 4px;
|
34
|
+
border-radius: 4px;
|
35
|
+
}
|
36
|
+
|
37
|
+
.alert,
|
38
|
+
.alert h4 {
|
39
|
+
color: #c09853;
|
40
|
+
}
|
41
|
+
|
42
|
+
.alert h4 {
|
43
|
+
margin: 0;
|
44
|
+
}
|
45
|
+
|
46
|
+
.alert .close {
|
47
|
+
position: relative;
|
48
|
+
top: -2px;
|
49
|
+
right: -21px;
|
50
|
+
line-height: 20px;
|
51
|
+
}
|
52
|
+
|
53
|
+
.alert-success {
|
54
|
+
color: #468847;
|
55
|
+
background-color: #dff0d8;
|
56
|
+
border-color: #d6e9c6;
|
57
|
+
}
|
58
|
+
|
59
|
+
.alert-success h4 {
|
60
|
+
color: #468847;
|
61
|
+
}
|
62
|
+
|
63
|
+
.alert-danger,
|
64
|
+
.alert-error {
|
65
|
+
color: #b94a48;
|
66
|
+
background-color: #f2dede;
|
67
|
+
border-color: #eed3d7;
|
68
|
+
}
|
69
|
+
|
70
|
+
.alert-danger h4,
|
71
|
+
.alert-error h4 {
|
72
|
+
color: #b94a48;
|
73
|
+
}
|
74
|
+
|
75
|
+
.alert-info {
|
76
|
+
color: #3a87ad;
|
77
|
+
background-color: #d9edf7;
|
78
|
+
border-color: #bce8f1;
|
79
|
+
}
|
80
|
+
|
81
|
+
.alert-info h4 {
|
82
|
+
color: #3a87ad;
|
83
|
+
}
|
84
|
+
|
85
|
+
.alert-block {
|
86
|
+
padding-top: 14px;
|
87
|
+
padding-bottom: 14px;
|
88
|
+
}
|
89
|
+
|
90
|
+
.alert-block > p,
|
91
|
+
.alert-block > ul {
|
92
|
+
margin-bottom: 0;
|
93
|
+
}
|
94
|
+
|
95
|
+
.alert-block p + p {
|
96
|
+
margin-top: 5px;
|
97
|
+
}
|
@@ -7,6 +7,16 @@ 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
|
+
|
10
20
|
def clear_batch_session
|
11
21
|
session[:canvas_batch_id] = nil
|
12
22
|
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
|
-
(name
|
54
|
+
([name, tail].join).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(url
|
59
|
+
options[:url] = main_app.send([url,'_url'].join)
|
60
60
|
options
|
61
61
|
end
|
62
62
|
|
@@ -13,7 +13,9 @@ 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
|
-
|
16
|
+
prefix = [oauth2_protocol, '://', api_domain].join
|
17
|
+
Rails.logger.info "Creating Bearcat client for auth token retrieval pointed to: #{prefix}"
|
18
|
+
client = Bearcat::Client.new(prefix: prefix)
|
17
19
|
token = client.retrieve_token(client_id, coalescing_panda.oauth2_redirect_url, client_key, params['code'])
|
18
20
|
CanvasApiAuth.where('user_id = ? and api_domain = ?', user_id, api_domain).first_or_create do |auth|
|
19
21
|
auth.api_token = token
|
@@ -1,6 +1,8 @@
|
|
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'
|
4
6
|
has_many :submissions, foreign_key: :coalescing_panda_assignment_id, class_name: 'CoalescingPanda::Submission', dependent: :destroy
|
5
7
|
|
6
8
|
delegate :account, to: :course
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module CoalescingPanda
|
2
|
+
class AssignmentGroup < ActiveRecord::Base
|
3
|
+
belongs_to :course, foreign_key: :coalescing_panda_course_id, class_name: 'CoalescingPanda::Course'
|
4
|
+
has_many :assignments, foreign_key: :coalescing_panda_assignment_group_id, class_name: 'CoalescingPanda::Assignment', dependent: :destroy
|
5
|
+
|
6
|
+
delegate :account, to: :course
|
7
|
+
|
8
|
+
validates :coalescing_panda_course_id, presence: true
|
9
|
+
validates :canvas_assignment_group_id, presence: true
|
10
|
+
end
|
11
|
+
end
|
@@ -1,6 +1,10 @@
|
|
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'
|
3
6
|
belongs_to :context, polymorphic: true
|
7
|
+
|
4
8
|
default_scope { order('created_at DESC') }
|
5
9
|
end
|
6
10
|
end
|
@@ -8,8 +8,10 @@ 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
|
11
12
|
has_many :group_memberships, through: :groups, source: :group_memberships, class_name: 'CoalescingPanda::GroupMembership', dependent: :destroy
|
12
13
|
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
|
13
15
|
|
14
16
|
validates :coalescing_panda_lti_account_id, presence: true
|
15
17
|
validates :canvas_course_id, presence: true
|
@@ -3,9 +3,10 @@ module CoalescingPanda
|
|
3
3
|
belongs_to :context, :polymorphic => true
|
4
4
|
include SingleTablePolymorphic
|
5
5
|
|
6
|
-
|
6
|
+
belongs_to :leader, foreign_key: :leader_id, class_name: 'CoalescingPanda::User'
|
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
|
7
9
|
validates :group_category_id, presence: true
|
8
10
|
validates :canvas_group_id, presence: true
|
9
|
-
validates :coalescing_panda_user_id, presence: true
|
10
11
|
end
|
11
12
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module CoalescingPanda
|
2
|
+
class GroupCategory < ActiveRecord::Base
|
3
|
+
belongs_to :context, :polymorphic => true
|
4
|
+
include SingleTablePolymorphic
|
5
|
+
|
6
|
+
belongs_to :leader, foreign_key: :leader_id, class_name: 'CoalescingPanda::User'
|
7
|
+
has_many :groups, foreign_key: :coalescing_panda_group_category_id, class_name: 'CoalescingPanda::Group'
|
8
|
+
has_many :assignments, foreign_key: :coalescing_panda_group_category_id, class_name: 'CoalescingPanda::Assignment'
|
9
|
+
validates :canvas_group_category_id, presence: true
|
10
|
+
end
|
11
|
+
end
|
@@ -6,6 +6,7 @@ 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'
|
9
10
|
has_many :sections, through: :courses
|
10
11
|
has_many :enrollments, through: :sections
|
11
12
|
has_many :assignments, through: :courses
|
@@ -3,6 +3,7 @@ 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'
|
6
7
|
has_many :sections, through: :enrollments
|
7
8
|
has_many :courses, through: :sections
|
8
9
|
|
@@ -1,32 +1,53 @@
|
|
1
1
|
class CoalescingPanda::Workers::CourseMiner
|
2
|
-
SUPPORTED_MODELS = [:sections, :users, :enrollments, :assignments, :submissions, :groups, :group_memberships] #ORDER MATTERS!!
|
2
|
+
SUPPORTED_MODELS = [:sections, :users, :enrollments, :assignment_groups, :group_categories, :assignments, :submissions, :groups, :group_memberships] #ORDER MATTERS!!
|
3
|
+
COMPLETED_STATUSES = ['Completed', 'Error']
|
4
|
+
RUNNING_STATUSES = ['Queued', 'Started']
|
3
5
|
|
4
|
-
attr_accessor :options, :account, :course, :batch, :course_section_ids, :enrollment_ids, :assignment_ids, :group_ids, :user_ids
|
6
|
+
attr_accessor :options, :account, :course, :batch, :course_section_ids, :enrollment_ids, :assignment_ids, :assignment_group_ids, :group_ids, :user_ids
|
5
7
|
|
6
8
|
def initialize(course, options = [])
|
7
9
|
@course = course
|
8
10
|
@account = course.account
|
9
11
|
@options = options
|
10
|
-
@batch =
|
12
|
+
@batch = setup_batch
|
11
13
|
@course_section_ids = []
|
12
14
|
@enrollment_ids = []
|
13
15
|
@assignment_ids = []
|
16
|
+
@assignment_group_ids = []
|
14
17
|
@group_ids = []
|
15
18
|
@user_ids = []
|
16
19
|
end
|
17
20
|
|
21
|
+
def setup_batch
|
22
|
+
batch = account.canvas_batches.where(context: course).first
|
23
|
+
if batch.present? and RUNNING_STATUSES.include?(batch.status)
|
24
|
+
batch
|
25
|
+
else
|
26
|
+
batch = account.canvas_batches.create(context: course, status: "Queued")
|
27
|
+
end
|
28
|
+
batch.update_attributes(options: options)
|
29
|
+
batch
|
30
|
+
end
|
31
|
+
|
18
32
|
def api_client
|
19
33
|
@api_client ||= Bearcat::Client.new(prefix: account.settings[:base_url], token: account.settings[:account_admin_api_token])
|
20
34
|
end
|
21
35
|
|
22
|
-
def start
|
36
|
+
def start(forced = false)
|
37
|
+
unless forced
|
38
|
+
return unless batch.status == 'Queued' # don't start if there is already a running job
|
39
|
+
return unless should_download?
|
40
|
+
end
|
41
|
+
|
23
42
|
begin
|
24
43
|
batch.update_attributes(status: "Started", percent_complete: 0)
|
25
|
-
|
26
|
-
|
27
|
-
|
44
|
+
index = 1
|
45
|
+
SUPPORTED_MODELS.each do |model_key|
|
46
|
+
next unless options.include?(model_key)
|
47
|
+
process_api_data(model_key.to_sym)
|
28
48
|
percent_complete = (index/(options.count.nonzero? || 1).to_f * 100).round(1)
|
29
49
|
batch.update_attributes(percent_complete: percent_complete)
|
50
|
+
index += 1
|
30
51
|
end
|
31
52
|
batch.update_attributes(status: "Completed", percent_complete: 100)
|
32
53
|
rescue => e
|
@@ -35,8 +56,19 @@ class CoalescingPanda::Workers::CourseMiner
|
|
35
56
|
end
|
36
57
|
handle_asynchronously :start
|
37
58
|
|
59
|
+
def should_download?
|
60
|
+
return true unless account.settings[:canvas_download_interval].present?
|
61
|
+
return true unless last_completed_batch = account.canvas_batches.where(context: course, status: 'Completed').order('updated_at ASC').first
|
62
|
+
should_download = last_completed_batch.updated_at < Time.zone.now - account.settings[:canvas_download_interval].minutes
|
63
|
+
batch.update_attributes(status: 'Canceled') unless should_download
|
64
|
+
should_download
|
65
|
+
end
|
66
|
+
|
38
67
|
def process_api_data(key)
|
39
68
|
case key
|
69
|
+
when :assignment_groups
|
70
|
+
collection = api_client.list_assignment_groups(course.canvas_course_id).all_pages!
|
71
|
+
sync_assignment_groups(collection)
|
40
72
|
when :sections
|
41
73
|
collection = api_client.course_sections(course.canvas_course_id).all_pages!
|
42
74
|
sync_sections(collection)
|
@@ -60,6 +92,9 @@ class CoalescingPanda::Workers::CourseMiner
|
|
60
92
|
when :groups
|
61
93
|
collection = api_client.course_groups(course.canvas_course_id).all_pages!
|
62
94
|
sync_groups(collection)
|
95
|
+
when :group_categories
|
96
|
+
collection = api_client.list_group_categories('courses', course.canvas_course_id).all_pages!
|
97
|
+
sync_group_categories(collection)
|
63
98
|
when :group_memberships
|
64
99
|
collection = []
|
65
100
|
course.groups.each do |group|
|
@@ -73,25 +108,40 @@ class CoalescingPanda::Workers::CourseMiner
|
|
73
108
|
end
|
74
109
|
end
|
75
110
|
|
111
|
+
def sync_assignment_groups(collection)
|
112
|
+
collection.each do |values|
|
113
|
+
begin
|
114
|
+
values['canvas_assignment_group_id'] = values['id'].to_s
|
115
|
+
assignment_group = course.assignment_groups.where(canvas_assignment_group_id: values['canvas_assignment_group_id']).first_or_initialize
|
116
|
+
assignment_group.assign_attributes(standard_attributes(assignment_group, values))
|
117
|
+
assignment_group.save(validate: false)
|
118
|
+
assignment_group_ids << assignment_group.id
|
119
|
+
rescue => e
|
120
|
+
Rails.logger.error "Error syncing assignment group: #{values} Error: #{e}"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
course.assignment_groups.where.not(id: assignment_group_ids).destroy_all
|
124
|
+
end
|
125
|
+
|
76
126
|
def sync_sections(collection)
|
77
|
-
|
78
|
-
|
127
|
+
collection.each do |values|
|
128
|
+
begin
|
79
129
|
values['course_section_id'] = values['id'].to_s
|
80
130
|
section = course.sections.where(canvas_section_id: values['course_section_id']).first_or_initialize
|
81
131
|
section.assign_attributes(standard_attributes(section, values))
|
82
132
|
section.sis_id = values['sis_section_id']
|
83
133
|
section.save(validate: false)
|
84
134
|
course_section_ids << section.id
|
135
|
+
rescue => e
|
136
|
+
Rails.logger.error "Error syncing section: #{values} Error: #{e}"
|
85
137
|
end
|
86
|
-
course.sections.where.not(id: course_section_ids).destroy_all
|
87
|
-
rescue => e
|
88
|
-
Rails.logger.error "Error syncing sections: #{e}"
|
89
138
|
end
|
139
|
+
course.sections.where.not(id: course_section_ids).destroy_all
|
90
140
|
end
|
91
141
|
|
92
142
|
def sync_users(collection)
|
93
|
-
|
94
|
-
|
143
|
+
collection.each do |values|
|
144
|
+
begin
|
95
145
|
values['canvas_user_id'] = values["id"].to_s
|
96
146
|
user = account.users.where(canvas_user_id: values['canvas_user_id']).first_or_initialize
|
97
147
|
user.coalescing_panda_lti_account_id = account.id
|
@@ -99,103 +149,129 @@ class CoalescingPanda::Workers::CourseMiner
|
|
99
149
|
user.sis_id = values['sis_user_id'].to_s
|
100
150
|
user_ids << user.id
|
101
151
|
user.save(validate: false)
|
152
|
+
rescue => e
|
153
|
+
Rails.logger.error "Error syncing user: #{values} Error: #{e}"
|
102
154
|
end
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
155
|
+
end
|
156
|
+
removed_users = course.users.where.not(id: user_ids)
|
157
|
+
removed_users.each do |user|
|
158
|
+
user.enrollments.each do |enrollment|
|
159
|
+
course.submissions.where(coalescing_panda_user_id: enrollment.user.id).destroy_all
|
160
|
+
enrollment.destroy
|
109
161
|
end
|
110
|
-
removed_users.destroy_all
|
111
|
-
rescue => e
|
112
|
-
Rails.logger.error "Error syncing users: #{e}"
|
113
162
|
end
|
163
|
+
removed_users.destroy_all
|
114
164
|
end
|
115
165
|
|
116
166
|
def sync_enrollments(collection)
|
117
|
-
|
118
|
-
|
167
|
+
collection.each do |values|
|
168
|
+
begin
|
119
169
|
values['canvas_enrollment_id'] = values['id'].to_s
|
120
|
-
|
170
|
+
section = course.sections.find_by(canvas_section_id: values['course_section_id'].to_s)
|
171
|
+
enrollment = section.enrollments.where(canvas_enrollment_id: values['canvas_enrollment_id']).first_or_initialize
|
121
172
|
enrollment.section = course.sections.find_by(canvas_section_id: values['course_section_id'].to_s)
|
122
173
|
enrollment.user = account.users.find_by(canvas_user_id: values['user_id'].to_s)
|
123
174
|
values['workflow_state'] = values["enrollment_state"]
|
124
175
|
values['enrollment_type'] = values['type']
|
125
176
|
enrollment.assign_attributes(standard_attributes(enrollment, values))
|
126
|
-
enrollment.save(validate: false)
|
177
|
+
enrollment.save!(validate: false)
|
127
178
|
enrollment_ids << enrollment.id
|
179
|
+
rescue => e
|
180
|
+
Rails.logger.error "Error syncing enrollment: #{values} Error: #{e}"
|
128
181
|
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}"
|
136
182
|
end
|
183
|
+
removed_enrollments = course.enrollments.where.not(id: enrollment_ids)
|
184
|
+
removed_enrollments.each do |enrollment|
|
185
|
+
course.submissions.where(coalescing_panda_user_id: enrollment.user.id).destroy_all
|
186
|
+
end
|
187
|
+
removed_enrollments.destroy_all
|
137
188
|
end
|
138
189
|
|
139
190
|
def sync_assignments(collection)
|
140
|
-
|
141
|
-
|
191
|
+
collection.each do |values|
|
192
|
+
begin
|
142
193
|
values['canvas_assignment_id'] = values['id'].to_s
|
143
194
|
assignment = course.assignments.where(canvas_assignment_id: values['canvas_assignment_id']).first_or_initialize
|
195
|
+
assignment_group = course.assignment_groups.find_by(canvas_assignment_group_id: values['assignment_group_id'].to_s)
|
196
|
+
group_category = course.group_categories.find_by(canvas_group_category_id: values['group_category_id'])
|
197
|
+
assignment.coalescing_panda_assignment_group_id = assignment_group.id if assignment_group
|
198
|
+
assignment.coalescing_panda_group_category_id = group_category.id if group_category
|
144
199
|
assignment.assign_attributes(standard_attributes(assignment, values))
|
145
200
|
assignment.save(validate: false)
|
146
201
|
assignment_ids << assignment.id
|
202
|
+
rescue => e
|
203
|
+
Rails.logger.error "Error syncing assignment: #{values} Error: #{e}"
|
147
204
|
end
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
rescue => e
|
153
|
-
Rails.logger.error "Error syncing assignments: #{e}"
|
205
|
+
end
|
206
|
+
course.assignments.where.not(id: assignment_ids).each do |assignment|
|
207
|
+
assignment.submissions.destroy_all
|
208
|
+
assignment.destroy!
|
154
209
|
end
|
155
210
|
end
|
156
211
|
|
157
212
|
def sync_submissions(collection)
|
158
|
-
|
159
|
-
|
213
|
+
collection.each do |values|
|
214
|
+
begin
|
160
215
|
values['canvas_submission_id'] = values['id'].to_s
|
161
216
|
submission = course.submissions.where(canvas_submission_id: values['canvas_submission_id']).first_or_initialize
|
162
217
|
submission.user = course.users.find_by(canvas_user_id: values['user_id'].to_s)
|
163
218
|
submission.assignment = course.assignments.find_by(canvas_assignment_id: values['assignment_id'].to_s)
|
164
219
|
submission.assign_attributes(standard_attributes(submission, values))
|
165
220
|
submission.save(validate: false)
|
221
|
+
rescue => e
|
222
|
+
Rails.logger.error "Error syncing submission: #{values} Error: #{e}"
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def sync_group_categories(collection)
|
228
|
+
collection.each do |values|
|
229
|
+
begin
|
230
|
+
values['canvas_group_category_id'] = values['id'].to_s
|
231
|
+
values.delete('context_type') #assume only course for now
|
232
|
+
category = course.group_categories.where(canvas_group_category_id: values['canvas_group_category_id']).first_or_initialize
|
233
|
+
category.assign_attributes(standard_attributes(category, values))
|
234
|
+
category.save(validate: false)
|
235
|
+
rescue => e
|
236
|
+
Rails.logger.error "Error syncing group categories: #{values} Error: #{e.message} - #{e.backtrace}"
|
166
237
|
end
|
167
|
-
rescue => e
|
168
|
-
Rails.logger.error "Error syncing submissions: #{e}"
|
169
238
|
end
|
170
239
|
end
|
171
240
|
|
172
241
|
def sync_groups(collection)
|
173
|
-
|
174
|
-
|
242
|
+
collection.each do |values|
|
243
|
+
begin
|
175
244
|
values['canvas_group_id'] = values['id'].to_s
|
176
245
|
group = course.groups.where(canvas_group_id: values['canvas_group_id']).first_or_initialize
|
246
|
+
group_category = course.group_categories.find_by(canvas_group_category_id: values['group_category_id'])
|
247
|
+
if values['leader']
|
248
|
+
group.leader = course.users.find_by(canvas_user_id: values['leader']['id'].to_s)
|
249
|
+
else
|
250
|
+
group.leader = nil
|
251
|
+
end
|
252
|
+
group.coalescing_panda_group_category_id = group_category.id if group_category
|
177
253
|
group.assign_attributes(standard_attributes(group, values))
|
178
254
|
group.save(validate: false)
|
179
255
|
group_ids << group.id
|
256
|
+
rescue => e
|
257
|
+
Rails.logger.error "Error syncing group: #{values} Error: #{e}"
|
180
258
|
end
|
181
|
-
course.groups.where.not(id: group_ids).destroy_all
|
182
|
-
rescue => e
|
183
|
-
Rails.logger.error "Error syncing groups: #{e}"
|
184
259
|
end
|
260
|
+
course.groups.where.not(id: group_ids).destroy_all
|
185
261
|
end
|
186
262
|
|
187
263
|
def sync_group_memberships(collection)
|
188
|
-
|
189
|
-
|
264
|
+
collection.each do |values|
|
265
|
+
begin
|
190
266
|
values['canvas_group_membership_id'] = values['id'].to_s
|
191
267
|
group_membership = course.group_memberships.where(canvas_group_membership_id: values['canvas_group_membership_id']).first_or_initialize
|
192
268
|
group_membership.group = course.groups.find_by(canvas_group_id: values['group_id'].to_s)
|
193
269
|
group_membership.user = course.users.find_by(canvas_user_id: values['user_id'].to_s)
|
194
270
|
group_membership.assign_attributes(standard_attributes(group_membership, values))
|
195
271
|
group_membership.save(validate: false)
|
272
|
+
rescue => e
|
273
|
+
Rails.logger.error "Error syncing group memebership: #{values} Error: #{e}"
|
196
274
|
end
|
197
|
-
rescue => e
|
198
|
-
Rails.logger.error "Error syncing group memberships: #{e}"
|
199
275
|
end
|
200
276
|
end
|
201
277
|
|
@@ -204,4 +280,4 @@ class CoalescingPanda::Workers::CourseMiner
|
|
204
280
|
new_attributes.delete('id')
|
205
281
|
new_attributes.delete_if { |key, value| !record.attributes.include?(key) }
|
206
282
|
end
|
207
|
-
end
|
283
|
+
end
|
@@ -1,20 +1,32 @@
|
|
1
1
|
#batch-info{data: {batch: @batch.to_json}}
|
2
|
-
-if @batch.status == "Queued"
|
3
|
-
%
|
2
|
+
- if @batch.status == "Queued"
|
3
|
+
%span.batch-message-queued Data is queued for download from Canvas.
|
4
4
|
|
5
|
-
-if @batch.status == "Completed"
|
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
|
-
-if @batch.status == "Started"
|
12
|
-
%h6.batch-message-started
|
13
|
-
|
14
|
-
.
|
11
|
+
- if @batch.status == "Started"
|
12
|
+
%h6.batch-message-started
|
13
|
+
Downloading data from Canvas
|
14
|
+
.progress.progress-striped.active
|
15
|
+
.bar{:style => "width: #{@batch.percent_complete}%;"}
|
15
16
|
|
16
|
-
|
17
|
+
|
18
|
+
- if @batch.status == "Error"
|
17
19
|
.alert.alert-block.alert-error
|
18
20
|
%button.close{"data-dismiss" => "alert", :type => "button"} ×
|
19
|
-
%
|
20
|
-
= @batch.message
|
21
|
+
%span.batch-message-error Data failed to download from Canvas
|
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
|
@@ -1,4 +1,4 @@
|
|
1
1
|
- if current_batch.present?
|
2
2
|
- path = CoalescingPanda::Engine.routes.url_helpers.canvas_batch_path(current_batch)
|
3
|
-
-clear_path = CoalescingPanda::Engine.routes.url_helpers.clear_batch_session_path
|
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,5 +1,7 @@
|
|
1
1
|
CoalescingPanda::Engine.routes.draw do
|
2
|
-
resources :canvas_batches, only: [:show]
|
2
|
+
resources :canvas_batches, only: [:show, :update] do
|
3
|
+
post :retrigger, on: :member
|
4
|
+
end
|
3
5
|
post '/canvas_batches/clear_batch_session', as: :clear_batch_session
|
4
6
|
|
5
7
|
get '/oauth2/redirect' => 'oauth2#redirect'
|