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.
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'