coalescing_panda 4.0.4 → 4.0.5

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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/coalescing_panda/canvas_batch.js.coffee +5 -7
  3. data/app/assets/stylesheets/coalescing_panda/progress.css.scss +97 -0
  4. data/app/controllers/coalescing_panda/canvas_batches_controller.rb +10 -0
  5. data/app/controllers/coalescing_panda/lti_controller.rb +2 -2
  6. data/app/controllers/coalescing_panda/oauth2_controller.rb +3 -1
  7. data/app/models/coalescing_panda/assignment.rb +2 -0
  8. data/app/models/coalescing_panda/assignment_group.rb +11 -0
  9. data/app/models/coalescing_panda/canvas_batch.rb +4 -0
  10. data/app/models/coalescing_panda/course.rb +2 -0
  11. data/app/models/coalescing_panda/group.rb +3 -2
  12. data/app/models/coalescing_panda/group_category.rb +11 -0
  13. data/app/models/coalescing_panda/lti_account.rb +1 -0
  14. data/app/models/coalescing_panda/user.rb +1 -0
  15. data/app/models/coalescing_panda/workers/course_miner.rb +132 -56
  16. data/app/models/concerns/single_table_polymorphic.rb +1 -1
  17. data/app/views/coalescing_panda/canvas_batches/_canvas_batch.html.haml +22 -10
  18. data/app/views/coalescing_panda/canvas_batches/_canvas_batch_flash.html.haml +1 -1
  19. data/config/routes.rb +3 -1
  20. data/db/migrate/20150506183335_create_coalescing_panda_assignment_groups.rb +18 -0
  21. data/db/migrate/20150506192717_add_assignment_group_id_to_assignments.rb +5 -0
  22. data/db/migrate/20150526144713_add_account_to_canvas_batches.rb +5 -0
  23. data/db/migrate/20150602205257_add_option_to_canvas_batches.rb +5 -0
  24. data/db/migrate/20150708192717_add_group_moderator_to_group_memberships.rb +5 -0
  25. data/db/migrate/20150709192717_add_leader_id_to_groups.rb +6 -0
  26. data/db/migrate/20150714205405_create_coalescing_panda_group_categories.rb +16 -0
  27. data/lib/coalescing_panda/bearcat_uri.rb +20 -0
  28. data/lib/coalescing_panda/controller_helpers.rb +22 -25
  29. data/lib/coalescing_panda/version.rb +1 -1
  30. data/spec/dummy/db/development.sqlite3 +0 -0
  31. data/spec/dummy/db/schema.rb +120 -82
  32. data/spec/dummy/db/test.sqlite3 +0 -0
  33. data/spec/dummy/log/development.log +628 -0
  34. data/spec/dummy/log/test.log +42958 -0
  35. data/spec/factories/assignment_groups.rb +14 -0
  36. data/spec/models/coalescing_panda/assignment_group_spec.rb +32 -0
  37. data/spec/models/coalescing_panda/assignment_spec.rb +1 -0
  38. data/spec/models/coalescing_panda/course_spec.rb +5 -0
  39. data/spec/models/coalescing_panda/workers/course_miner_spec.rb +57 -10
  40. metadata +25 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: eb5da44106384c3b615090a856967e4900211f4f
4
- data.tar.gz: 6ef8ab6716ecec434679ef70b4a494d48320545b
3
+ metadata.gz: 426f1e67e2f5b5e67c512ddf08d4218e69195c58
4
+ data.tar.gz: 1fa762f16649d117921743c0f6d1ad08897a2a71
5
5
  SHA512:
6
- metadata.gz: 8a15c122d6b85d8829766dac6b8aa6626cb38b4af08902b1ebf22057a56085cdd226014898089e768bf703d2a7f993b6d19f390316c02b03281d2c36d1878df0
7
- data.tar.gz: ffbaafd12ead0f87670030f4ae4e9d527caf6ca58332bd8414dbe517f6b2c72910575365976c369d9859f592f39cd7d7ebdf4993208556c85d1627f6fd6c466e
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+tail).to_sym
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+'_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
- client = Bearcat::Client.new(prefix: oauth2_protocol+'://'+api_domain)
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
- has_many :group_memberships, dependent: :destroy, foreign_key: :coalescing_panda_group_id, class_name: 'CoalescingPanda::GroupMembership', dependent: :destroy
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 = CoalescingPanda::CanvasBatch.create(context: course, status: "Queued")
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
- 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)
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
- begin
78
- collection.each do |values|
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
- begin
94
- collection.each do |values|
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
- 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
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
- begin
118
- collection.each do |values|
167
+ collection.each do |values|
168
+ begin
119
169
  values['canvas_enrollment_id'] = values['id'].to_s
120
- enrollment = course.enrollments.where(canvas_enrollment_id: values['canvas_enrollment_id']).first_or_initialize
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
- begin
141
- collection.each do |values|
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
- 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}"
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
- begin
159
- collection.each do |values|
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
- begin
174
- collection.each do |values|
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
- begin
189
- collection.each do |values|
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
@@ -8,4 +8,4 @@ module SingleTablePolymorphic
8
8
  end
9
9
  end
10
10
  end
11
- end
11
+ end
@@ -1,20 +1,32 @@
1
1
  #batch-info{data: {batch: @batch.to_json}}
2
- -if @batch.status == "Queued"
3
- %h6.batch-message-queued Data is queued for download from Canvas.
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 Downloading data from Canvas
13
- .progress.progress-striped.active
14
- .bar{:style => "width: #{@batch.percent_complete}%;"}
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
- -if @batch.status == "Error"
17
+
18
+ - if @batch.status == "Error"
17
19
  .alert.alert-block.alert-error
18
20
  %button.close{"data-dismiss" => "alert", :type => "button"} ×
19
- %h6.batch-message-error Data failed to download from Canvas
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'