canvas_sync 0.16.4 → 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (260) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +235 -151
  3. data/app/controllers/{api → canvas_sync/api}/v1/health_check_controller.rb +1 -1
  4. data/app/controllers/canvas_sync/api/v1/live_events_controller.rb +122 -0
  5. data/app/models/canvas_sync/sync_batch.rb +5 -0
  6. data/config/initializers/apartment.rb +10 -1
  7. data/config/routes.rb +7 -0
  8. data/db/migrate/20170915210836_create_canvas_sync_job_log.rb +12 -31
  9. data/db/migrate/20180725155729_add_job_id_to_canvas_sync_job_logs.rb +4 -13
  10. data/db/migrate/20190916154829_add_fork_count_to_canvas_sync_job_logs.rb +3 -11
  11. data/db/migrate/20201018210836_create_canvas_sync_sync_batches.rb +11 -0
  12. data/db/migrate/20201030210836_add_full_sync_to_canvas_sync_sync_batch.rb +7 -0
  13. data/lib/canvas_sync/batch_processor.rb +41 -0
  14. data/lib/canvas_sync/concerns/ability_helper.rb +72 -0
  15. data/lib/canvas_sync/concerns/account/ancestry.rb +2 -0
  16. data/lib/canvas_sync/concerns/account/base.rb +15 -0
  17. data/lib/canvas_sync/concerns/api_syncable.rb +17 -10
  18. data/lib/canvas_sync/concerns/live_event_sync.rb +46 -0
  19. data/lib/canvas_sync/concerns/role/base.rb +57 -0
  20. data/lib/canvas_sync/concerns/sync_mapping.rb +120 -0
  21. data/lib/canvas_sync/engine.rb +80 -0
  22. data/lib/canvas_sync/generators/install_generator.rb +1 -0
  23. data/lib/canvas_sync/generators/install_live_events_generator.rb +0 -1
  24. data/lib/canvas_sync/generators/templates/migrations/create_content_migrations.rb +24 -0
  25. data/lib/canvas_sync/generators/templates/migrations/create_course_nicknames.rb +17 -0
  26. data/lib/canvas_sync/generators/templates/migrations/create_grading_period_groups.rb +18 -0
  27. data/lib/canvas_sync/generators/templates/migrations/create_grading_periods.rb +22 -0
  28. data/lib/canvas_sync/generators/templates/migrations/create_learning_outcome_results.rb +46 -0
  29. data/lib/canvas_sync/generators/templates/migrations/create_learning_outcomes.rb +30 -0
  30. data/lib/canvas_sync/generators/templates/migrations/create_rubric_assessments.rb +31 -0
  31. data/lib/canvas_sync/generators/templates/migrations/create_rubric_associations.rb +36 -0
  32. data/lib/canvas_sync/generators/templates/migrations/create_rubrics.rb +38 -0
  33. data/lib/canvas_sync/generators/templates/migrations/create_user_observers.rb +17 -0
  34. data/lib/canvas_sync/generators/templates/migrations/create_users.rb +0 -1
  35. data/lib/canvas_sync/generators/templates/models/account.rb +3 -0
  36. data/lib/canvas_sync/generators/templates/models/admin.rb +2 -0
  37. data/lib/canvas_sync/generators/templates/models/assignment.rb +3 -0
  38. data/lib/canvas_sync/generators/templates/models/assignment_group.rb +3 -0
  39. data/lib/canvas_sync/generators/templates/models/content_migration.rb +12 -0
  40. data/lib/canvas_sync/generators/templates/models/context_module.rb +3 -0
  41. data/lib/canvas_sync/generators/templates/models/context_module_item.rb +3 -0
  42. data/lib/canvas_sync/generators/templates/models/course.rb +11 -0
  43. data/lib/canvas_sync/generators/templates/models/course_nickname.rb +13 -0
  44. data/lib/canvas_sync/generators/templates/models/enrollment.rb +14 -0
  45. data/lib/canvas_sync/generators/templates/models/grading_period.rb +10 -0
  46. data/lib/canvas_sync/generators/templates/models/grading_period_group.rb +9 -0
  47. data/lib/canvas_sync/generators/templates/models/group.rb +2 -0
  48. data/lib/canvas_sync/generators/templates/models/group_membership.rb +2 -0
  49. data/lib/canvas_sync/generators/templates/models/learning_outcome.rb +24 -0
  50. data/lib/canvas_sync/generators/templates/models/learning_outcome_result.rb +48 -0
  51. data/lib/canvas_sync/generators/templates/models/pseudonym.rb +2 -0
  52. data/lib/canvas_sync/generators/templates/models/role.rb +2 -0
  53. data/lib/canvas_sync/generators/templates/models/rubric.rb +29 -0
  54. data/lib/canvas_sync/generators/templates/models/rubric_assessment.rb +17 -0
  55. data/lib/canvas_sync/generators/templates/models/rubric_association.rb +14 -0
  56. data/lib/canvas_sync/generators/templates/models/section.rb +9 -0
  57. data/lib/canvas_sync/generators/templates/models/submission.rb +3 -0
  58. data/lib/canvas_sync/generators/templates/models/term.rb +3 -0
  59. data/lib/canvas_sync/generators/templates/models/user.rb +11 -0
  60. data/lib/canvas_sync/generators/templates/models/user_observer.rb +13 -0
  61. data/lib/canvas_sync/generators/templates/services/live_events/assignment_event.rb +1 -1
  62. data/lib/canvas_sync/generators/templates/services/live_events/assignment_group_event.rb +1 -1
  63. data/lib/canvas_sync/generators/templates/services/live_events/course_event.rb +1 -3
  64. data/lib/canvas_sync/generators/templates/services/live_events/course_section_event.rb +1 -1
  65. data/lib/canvas_sync/generators/templates/services/live_events/enrollment_event.rb +1 -1
  66. data/lib/canvas_sync/generators/templates/services/live_events/grade_event.rb +1 -1
  67. data/lib/canvas_sync/generators/templates/services/live_events/module_event.rb +1 -1
  68. data/lib/canvas_sync/generators/templates/services/live_events/module_item_event.rb +1 -1
  69. data/lib/canvas_sync/generators/templates/services/live_events/submission_event.rb +1 -1
  70. data/lib/canvas_sync/generators/templates/services/live_events/syllabus_event.rb +1 -1
  71. data/lib/canvas_sync/generators/templates/services/live_events/user_event.rb +1 -3
  72. data/lib/canvas_sync/importers/bulk_importer.rb +138 -31
  73. data/lib/canvas_sync/job.rb +7 -5
  74. data/lib/canvas_sync/job_batches/active_job.rb +108 -0
  75. data/lib/canvas_sync/job_batches/batch.rb +543 -0
  76. data/lib/canvas_sync/job_batches/callback.rb +149 -0
  77. data/lib/canvas_sync/job_batches/chain_builder.rb +249 -0
  78. data/lib/canvas_sync/job_batches/context_hash.rb +159 -0
  79. data/lib/canvas_sync/job_batches/hier_batch_ids.lua +25 -0
  80. data/lib/canvas_sync/job_batches/jobs/base_job.rb +7 -0
  81. data/lib/canvas_sync/job_batches/jobs/concurrent_batch_job.rb +22 -0
  82. data/lib/canvas_sync/job_batches/jobs/managed_batch_job.rb +170 -0
  83. data/lib/canvas_sync/job_batches/jobs/serial_batch_job.rb +22 -0
  84. data/lib/canvas_sync/job_batches/pool.rb +245 -0
  85. data/lib/canvas_sync/job_batches/pool_refill.lua +47 -0
  86. data/lib/canvas_sync/job_batches/redis_model.rb +69 -0
  87. data/lib/canvas_sync/job_batches/redis_script.rb +163 -0
  88. data/lib/canvas_sync/job_batches/schedule_callback.lua +14 -0
  89. data/lib/canvas_sync/job_batches/sidekiq/web/batches_assets/css/styles.less +182 -0
  90. data/lib/canvas_sync/job_batches/sidekiq/web/batches_assets/js/batch_tree.js +108 -0
  91. data/lib/canvas_sync/job_batches/sidekiq/web/batches_assets/js/util.js +2 -0
  92. data/lib/canvas_sync/job_batches/sidekiq/web/helpers.rb +41 -0
  93. data/lib/canvas_sync/job_batches/sidekiq/web/views/_batch_tree.erb +6 -0
  94. data/lib/canvas_sync/job_batches/sidekiq/web/views/_batches_table.erb +44 -0
  95. data/lib/canvas_sync/job_batches/sidekiq/web/views/_common.erb +13 -0
  96. data/lib/canvas_sync/job_batches/sidekiq/web/views/_jobs_table.erb +21 -0
  97. data/lib/canvas_sync/job_batches/sidekiq/web/views/_pagination.erb +26 -0
  98. data/lib/canvas_sync/job_batches/sidekiq/web/views/batch.erb +81 -0
  99. data/lib/canvas_sync/job_batches/sidekiq/web/views/batches.erb +23 -0
  100. data/lib/canvas_sync/job_batches/sidekiq/web/views/pool.erb +137 -0
  101. data/lib/canvas_sync/job_batches/sidekiq/web/views/pools.erb +47 -0
  102. data/lib/canvas_sync/job_batches/sidekiq/web.rb +218 -0
  103. data/lib/canvas_sync/job_batches/sidekiq.rb +136 -0
  104. data/lib/canvas_sync/job_batches/status.rb +91 -0
  105. data/lib/canvas_sync/jobs/begin_sync_chain_job.rb +99 -0
  106. data/lib/canvas_sync/jobs/canvas_process_waiter.rb +41 -0
  107. data/lib/canvas_sync/jobs/report_checker.rb +70 -8
  108. data/lib/canvas_sync/jobs/report_processor_job.rb +4 -7
  109. data/lib/canvas_sync/jobs/report_starter.rb +34 -20
  110. data/lib/canvas_sync/jobs/sync_accounts_job.rb +3 -5
  111. data/lib/canvas_sync/jobs/sync_admins_job.rb +2 -4
  112. data/lib/canvas_sync/jobs/sync_assignment_groups_job.rb +2 -4
  113. data/lib/canvas_sync/jobs/sync_assignments_job.rb +2 -4
  114. data/lib/canvas_sync/jobs/sync_content_migrations_job.rb +20 -0
  115. data/lib/canvas_sync/jobs/sync_context_module_items_job.rb +2 -4
  116. data/lib/canvas_sync/jobs/sync_context_modules_job.rb +2 -4
  117. data/lib/canvas_sync/jobs/sync_provisioning_report_job.rb +16 -50
  118. data/lib/canvas_sync/jobs/sync_roles_job.rb +2 -5
  119. data/lib/canvas_sync/jobs/sync_rubric_assessments_job.rb +15 -0
  120. data/lib/canvas_sync/jobs/sync_rubric_associations_job.rb +15 -0
  121. data/lib/canvas_sync/jobs/sync_rubrics_job.rb +15 -0
  122. data/lib/canvas_sync/jobs/sync_simple_table_job.rb +11 -32
  123. data/lib/canvas_sync/jobs/sync_submissions_job.rb +6 -4
  124. data/lib/canvas_sync/jobs/sync_terms_job.rb +9 -8
  125. data/lib/canvas_sync/jobs/term_batches_job.rb +50 -0
  126. data/lib/canvas_sync/{generators/templates/services/live_events/base_event.rb → live_events/base_handler.rb} +6 -10
  127. data/lib/canvas_sync/live_events/process_event_job.rb +26 -0
  128. data/lib/canvas_sync/live_events.rb +38 -0
  129. data/lib/canvas_sync/misc_helper.rb +63 -0
  130. data/lib/canvas_sync/processors/assignment_groups_processor.rb +3 -8
  131. data/lib/canvas_sync/processors/assignments_processor.rb +3 -8
  132. data/lib/canvas_sync/processors/content_migrations_processor.rb +19 -0
  133. data/lib/canvas_sync/processors/context_module_items_processor.rb +3 -8
  134. data/lib/canvas_sync/processors/context_modules_processor.rb +3 -8
  135. data/lib/canvas_sync/processors/model_mappings.yml +420 -0
  136. data/lib/canvas_sync/processors/normal_processor.rb +3 -3
  137. data/lib/canvas_sync/processors/provisioning_report_processor.rb +42 -55
  138. data/lib/canvas_sync/processors/report_processor.rb +15 -9
  139. data/lib/canvas_sync/processors/rubric_assessments_processor.rb +19 -0
  140. data/lib/canvas_sync/processors/rubric_associations_processor.rb +19 -0
  141. data/lib/canvas_sync/processors/rubrics_processor.rb +19 -0
  142. data/lib/canvas_sync/processors/submissions_processor.rb +3 -8
  143. data/lib/canvas_sync/record.rb +103 -0
  144. data/lib/canvas_sync/version.rb +1 -1
  145. data/lib/canvas_sync.rb +124 -125
  146. data/spec/canvas_sync/canvas_sync_spec.rb +224 -155
  147. data/spec/canvas_sync/jobs/canvas_process_waiter_spec.rb +34 -0
  148. data/spec/canvas_sync/jobs/job_spec.rb +9 -17
  149. data/spec/canvas_sync/jobs/report_checker_spec.rb +1 -3
  150. data/spec/canvas_sync/jobs/report_processor_job_spec.rb +0 -3
  151. data/spec/canvas_sync/jobs/report_starter_spec.rb +19 -28
  152. data/spec/canvas_sync/jobs/sync_admins_job_spec.rb +1 -4
  153. data/spec/canvas_sync/jobs/sync_assignment_groups_job_spec.rb +2 -1
  154. data/spec/canvas_sync/jobs/sync_assignments_job_spec.rb +3 -2
  155. data/spec/canvas_sync/jobs/sync_content_migrations_job_spec.rb +30 -0
  156. data/spec/canvas_sync/jobs/sync_context_module_items_job_spec.rb +3 -2
  157. data/spec/canvas_sync/jobs/sync_context_modules_job_spec.rb +3 -2
  158. data/spec/canvas_sync/jobs/sync_provisioning_report_job_spec.rb +7 -41
  159. data/spec/canvas_sync/jobs/sync_roles_job_spec.rb +1 -4
  160. data/spec/canvas_sync/jobs/sync_simple_table_job_spec.rb +5 -12
  161. data/spec/canvas_sync/jobs/sync_submissions_job_spec.rb +8 -2
  162. data/spec/canvas_sync/jobs/sync_terms_job_spec.rb +1 -4
  163. data/spec/canvas_sync/live_events/live_event_sync_spec.rb +27 -0
  164. data/spec/canvas_sync/live_events/live_events_controller_spec.rb +54 -0
  165. data/spec/canvas_sync/live_events/process_event_job_spec.rb +38 -0
  166. data/spec/canvas_sync/misc_helper_spec.rb +58 -0
  167. data/spec/canvas_sync/models/assignment_spec.rb +1 -1
  168. data/spec/canvas_sync/processors/content_migrations_processor_spec.rb +13 -0
  169. data/spec/canvas_sync/processors/provisioning_report_processor_spec.rb +101 -1
  170. data/spec/canvas_sync/processors/rubric_assessments_spec.rb +16 -0
  171. data/spec/canvas_sync/processors/rubric_associations_spec.rb +16 -0
  172. data/spec/canvas_sync/processors/rubrics_processor_spec.rb +17 -0
  173. data/spec/dummy/app/models/account.rb +6 -0
  174. data/spec/dummy/app/models/admin.rb +2 -0
  175. data/spec/dummy/app/models/assignment.rb +3 -0
  176. data/spec/dummy/app/models/assignment_group.rb +3 -0
  177. data/spec/dummy/app/models/content_migration.rb +18 -0
  178. data/spec/dummy/app/models/context_module.rb +3 -0
  179. data/spec/dummy/app/models/context_module_item.rb +3 -0
  180. data/spec/dummy/app/models/course.rb +11 -0
  181. data/spec/dummy/app/models/course_nickname.rb +19 -0
  182. data/spec/dummy/app/models/enrollment.rb +14 -0
  183. data/spec/dummy/app/models/grading_period.rb +16 -0
  184. data/spec/dummy/app/models/grading_period_group.rb +15 -0
  185. data/spec/dummy/app/models/group.rb +2 -0
  186. data/spec/dummy/app/models/group_membership.rb +2 -0
  187. data/spec/dummy/app/models/learning_outcome.rb +30 -0
  188. data/spec/dummy/app/models/learning_outcome_result.rb +54 -0
  189. data/spec/dummy/app/models/pseudonym.rb +16 -0
  190. data/spec/dummy/app/models/role.rb +2 -0
  191. data/spec/dummy/app/models/rubric.rb +35 -0
  192. data/spec/dummy/app/models/rubric_assessment.rb +22 -0
  193. data/spec/dummy/app/models/rubric_association.rb +20 -0
  194. data/spec/dummy/app/models/section.rb +9 -0
  195. data/spec/dummy/app/models/submission.rb +4 -0
  196. data/spec/dummy/app/models/term.rb +3 -0
  197. data/spec/dummy/app/models/user.rb +11 -0
  198. data/spec/dummy/app/models/user_observer.rb +19 -0
  199. data/spec/dummy/app/services/live_events/assignment_event.rb +1 -1
  200. data/spec/dummy/app/services/live_events/course_event.rb +1 -3
  201. data/spec/dummy/app/services/live_events/course_section_event.rb +1 -1
  202. data/spec/dummy/app/services/live_events/enrollment_event.rb +1 -1
  203. data/spec/dummy/app/services/live_events/grade_event.rb +1 -1
  204. data/spec/dummy/app/services/live_events/module_event.rb +1 -1
  205. data/spec/dummy/app/services/live_events/module_item_event.rb +1 -1
  206. data/spec/dummy/app/services/live_events/submission_event.rb +1 -1
  207. data/spec/dummy/app/services/live_events/syllabus_event.rb +1 -1
  208. data/spec/dummy/app/services/live_events/user_event.rb +1 -3
  209. data/spec/dummy/config/environments/test.rb +2 -0
  210. data/spec/dummy/config/routes.rb +1 -0
  211. data/spec/dummy/db/migrate/20201016181346_create_pseudonyms.rb +24 -0
  212. data/spec/dummy/db/migrate/20210907233329_create_user_observers.rb +23 -0
  213. data/spec/dummy/db/migrate/20210907233330_create_grading_periods.rb +28 -0
  214. data/spec/dummy/db/migrate/20211001184920_create_grading_period_groups.rb +24 -0
  215. data/spec/dummy/db/migrate/20220308072643_create_content_migrations.rb +30 -0
  216. data/spec/dummy/db/migrate/20220712210559_create_learning_outcomes.rb +36 -0
  217. data/spec/dummy/db/migrate/{20190702203620_create_users.rb → 20220926221926_create_users.rb} +0 -1
  218. data/spec/dummy/db/migrate/20240408223326_create_course_nicknames.rb +23 -0
  219. data/spec/dummy/db/migrate/20240509105100_create_rubrics.rb +44 -0
  220. data/spec/dummy/db/migrate/20240510094100_create_rubric_associations.rb +42 -0
  221. data/spec/dummy/db/migrate/20240510101100_create_rubric_assessments.rb +37 -0
  222. data/spec/dummy/db/migrate/20240523101010_create_learning_outcome_results.rb +52 -0
  223. data/spec/dummy/db/schema.rb +244 -5
  224. data/spec/factories/user_factory.rb +0 -1
  225. data/spec/job_batching/active_job_spec.rb +107 -0
  226. data/spec/job_batching/batch_spec.rb +489 -0
  227. data/spec/job_batching/callback_spec.rb +38 -0
  228. data/spec/job_batching/context_hash_spec.rb +54 -0
  229. data/spec/job_batching/flow_spec.rb +82 -0
  230. data/spec/job_batching/integration/fail_then_succeed.rb +42 -0
  231. data/spec/job_batching/integration/integration.rb +57 -0
  232. data/spec/job_batching/integration/nested.rb +88 -0
  233. data/spec/job_batching/integration/simple.rb +47 -0
  234. data/spec/job_batching/integration/workflow.rb +134 -0
  235. data/spec/job_batching/integration_helper.rb +50 -0
  236. data/spec/job_batching/pool_spec.rb +161 -0
  237. data/spec/job_batching/sidekiq_spec.rb +125 -0
  238. data/spec/job_batching/status_spec.rb +76 -0
  239. data/spec/job_batching/support/base_job.rb +14 -0
  240. data/spec/job_batching/support/sample_callback.rb +2 -0
  241. data/spec/spec_helper.rb +17 -0
  242. data/spec/support/fixtures/reports/content_migrations.csv +3 -0
  243. data/spec/support/fixtures/reports/course_nicknames.csv +3 -0
  244. data/spec/support/fixtures/reports/grading_period_groups.csv +2 -0
  245. data/spec/support/fixtures/reports/grading_periods.csv +3 -0
  246. data/spec/support/fixtures/reports/learning_outcome_results.csv +3 -0
  247. data/spec/support/fixtures/reports/learning_outcomes.csv +3 -0
  248. data/spec/support/fixtures/reports/provisioning_csv_unzipped/courses.csv +3 -0
  249. data/spec/support/fixtures/reports/provisioning_csv_unzipped/users.csv +4 -0
  250. data/spec/support/fixtures/reports/rubric_assessments.csv +3 -0
  251. data/spec/support/fixtures/reports/rubric_associations.csv +3 -0
  252. data/spec/support/fixtures/reports/rubrics.csv +3 -0
  253. data/spec/support/fixtures/reports/user_observers.csv +3 -0
  254. data/spec/support/fixtures/reports/users.csv +3 -2
  255. data/spec/support/fixtures/reports/xlist.csv +1 -1
  256. metadata +329 -27
  257. data/app/controllers/api/v1/live_events_controller.rb +0 -18
  258. data/lib/canvas_sync/job_chain.rb +0 -57
  259. data/lib/canvas_sync/jobs/fork_gather.rb +0 -59
  260. data/spec/canvas_sync/jobs/fork_gather_spec.rb +0 -73
@@ -0,0 +1,182 @@
1
+
2
+ @color-green: #25c766;
3
+ @color-darkred: #8f0000;
4
+ @color-red: #e03963;
5
+ @color-yellow: #c4c725;
6
+
7
+ .code-wrap.batch-context .args-extended {
8
+ white-space: pre;
9
+
10
+ .key {
11
+ white-space: pre-wrap;
12
+ margin-left: 2em;
13
+ text-indent: -2em;
14
+ display: inline-block;
15
+ }
16
+
17
+ .own {
18
+ color: @color-green;
19
+ }
20
+ }
21
+
22
+
23
+ .batch-tree {
24
+ .status-block {
25
+ .tree-stat {
26
+ margin: 0 4px;
27
+
28
+ &.pending {
29
+ color: @color-yellow;
30
+ }
31
+ &.failed {
32
+ color: @color-red;
33
+ }
34
+ &.dead {
35
+ color: @color-darkred;
36
+ }
37
+ &.success {
38
+ color: @color-green;
39
+ }
40
+ &.total {
41
+
42
+ }
43
+ }
44
+ }
45
+
46
+ .text-inactive {
47
+ color: darken(#fff, 35%);
48
+ font-size: 80%;
49
+ }
50
+
51
+ .tree-header {
52
+ position: relative;
53
+
54
+ .status-block {
55
+ position: absolute;
56
+ bottom: 0;
57
+ width: 100%;
58
+
59
+ margin-right: 8px;
60
+ font-size: 90%;
61
+ text-align: right;
62
+
63
+ .tree-stat {
64
+ font-style: italic;
65
+ }
66
+ }
67
+ }
68
+
69
+ .tree-entry {
70
+ > .header {
71
+ display: flex;
72
+ align-items: center;
73
+
74
+ .header-inner {
75
+ padding: 4px 0;
76
+ border-bottom: 1px dashed white;
77
+ display: flex;
78
+ align-items: center;
79
+ flex: 1;
80
+ }
81
+
82
+ &:hover {
83
+ background-color: rgba(0,0,0,0.20);
84
+ border-radius: 3px;
85
+ }
86
+
87
+ .row-toggle {
88
+ width: 16px;
89
+ height: 16px;
90
+ text-align: center;
91
+ align-self: center;
92
+ border-radius: 50%;
93
+ border: 1px solid #999;
94
+ text-decoration: none;
95
+ margin: 0 4px;
96
+ font-size: 16px;
97
+ line-height: 15px;
98
+
99
+ &.not_applicable {
100
+ opacity: 0;
101
+ pointer-events: none;
102
+ }
103
+ }
104
+
105
+ .main {
106
+ flex: 1;
107
+ display: flex;
108
+ align-items: baseline;
109
+
110
+ .bid {
111
+ font-family: monospace;
112
+ padding: 3px 6px;
113
+ background: rgba(0,0,0,0.2);
114
+ border-radius: 3px;
115
+ font-size: 12px;
116
+ margin: 0 12px 0 0;
117
+
118
+ &:hover {
119
+ .bid-goto {
120
+ display: inline-block;
121
+ padding: 0 0 0 4px;
122
+ font-size: 200%;
123
+ line-height: 10px;
124
+ vertical-align: sub;
125
+ text-decoration: dotted;
126
+ }
127
+ }
128
+
129
+ .bid-goto {
130
+ display: none;
131
+ }
132
+ }
133
+ }
134
+
135
+ .goto-link {
136
+ margin: 0 8px;
137
+ display: inline-block;
138
+ height: 16px;
139
+ font-size: 90%;
140
+ border-bottom: 1px dotted white;
141
+ }
142
+
143
+ .status-label {
144
+ font-family: monospace;
145
+ padding: 3px 6px;
146
+ background: rgba(0,0,0,0.2);
147
+ border-radius: 3px;
148
+ font-size: 12px;
149
+ margin: 0 12px 0 0;
150
+
151
+ &.deleted {
152
+ background: #99999933;
153
+ }
154
+ &.failed, &.complete {
155
+ background: #99000033;
156
+ }
157
+ &.success {
158
+ background: #00990033;
159
+ }
160
+ }
161
+
162
+ .status-block {
163
+ width: 12em;
164
+ text-align: center;
165
+ }
166
+ }
167
+
168
+ > .subitems {
169
+ padding-left: 16px;
170
+
171
+ >.load-more {
172
+ padding: 4px 0;
173
+ text-align: center;
174
+ border-bottom: 1px dashed white;
175
+ a {
176
+ border-bottom: 1px dotted white;
177
+ text-decoration: none;
178
+ }
179
+ }
180
+ }
181
+ }
182
+ }
@@ -0,0 +1,108 @@
1
+ import { h, Component, render } from 'https://unpkg.com/preact?module';
2
+ import htm from 'https://unpkg.com/htm?module';
3
+ import { root_url } from './util.js';
4
+
5
+ // Initialize htm with Preact
6
+ const html = htm.bind(h);
7
+
8
+ const StatusBlock = (props) => html`
9
+ <div class="status-block ${props.class || ''}">
10
+ ${props.title && props.title + ':'}
11
+ <span class="tree-stat pending">${props.pending_count}</span>
12
+ |
13
+ <span class="tree-stat failed">${props.failed_count}</span>
14
+ |
15
+ <span class="tree-stat dead">${props.dead_count}</span>
16
+ |
17
+ <span class="tree-stat success">${props.successful_count}</span>
18
+ /
19
+ <span class="tree-stat total">${props.total_count}</span>
20
+ </div>
21
+ `
22
+
23
+ class TreeLevel extends Component {
24
+ get bid() {
25
+ return this.props.data.bid;
26
+ }
27
+
28
+ get batch() {
29
+ return this.props.data;
30
+ }
31
+
32
+ load_more = async (event) => {
33
+ event.preventDefault();
34
+ const l = this.batch.batches.items.length;
35
+ const resp = await fetch(`${root_url}batches/${this.bid}/tree?slice=${l}:${l + 5 - 1}`)
36
+ const result = await resp.json()
37
+ const newEntries = result.batches.items;
38
+ for (let ent of newEntries) {
39
+ this.batch.batches.items.push(ent)
40
+ }
41
+ this.forceUpdate()
42
+ }
43
+
44
+ toggle = (event) => {
45
+ event.preventDefault();
46
+ this.setState({ collapsed: !this.state.collapsed })
47
+ }
48
+
49
+ render() {
50
+ const { data: bd } = this.props;
51
+
52
+ let sub_entries = [];
53
+ let sub_batches = bd.batches.items;
54
+ for (let b of sub_batches) {
55
+ sub_entries.push(html`<${TreeLevel} data=${b} />`)
56
+ }
57
+
58
+ let fully_loaded = !(sub_batches.length < bd.batches.total_count);
59
+
60
+ const load_more_elem = html`<div class="load-more">
61
+ ${sub_entries.length} / ${bd.batches.total_count} Items Loaded - <a href="#" onClick=${this.load_more}>Load More</a>
62
+ </div>`
63
+
64
+ return html`<div class="tree-entry tree-batch">
65
+ <div class="header">
66
+ <a class="row-toggle ${!bd.batches.total_count && 'not_applicable'}" onClick=${this.toggle} href="#">
67
+ ${this.state.collapsed ? '+' : '-'}
68
+ </a>
69
+
70
+ <div class="header-inner">
71
+ <div class="main">
72
+ <span class="bid">
73
+ ${bd.bid}
74
+ <a class="bid-goto" href="${root_url}batches/${bd.bid}">⇢</a>
75
+ </span>
76
+ ${bd.description || (bd.status == 'deleted' && html`<i class="text-inactive">Deleted</i>`) || html`<i class="text-inactive">No Description</i>`}
77
+ </div>
78
+
79
+ <span class="status-label ${bd.status}">${bd.status}</span>
80
+
81
+ ${bd.status != 'deleted' && html`
82
+ <${StatusBlock} class="job-status" title="Jobs" ...${bd.jobs} />
83
+ <${StatusBlock} class="batch-status" title="Batches" ...${bd.batches} />
84
+ `}
85
+ </div>
86
+ </div>
87
+
88
+ <div class="subitems ${this.state.collapsed ? 'hidden' : ''}">
89
+ ${sub_entries}
90
+ ${!fully_loaded && load_more_elem}
91
+ </div>
92
+ </div>`
93
+ }
94
+ }
95
+
96
+ class TreeRoot extends Component {
97
+ render() {
98
+ const tree_data = JSON.parse(document.querySelector('#batch-tree #initial-data').innerHTML);
99
+ return html`
100
+ <div class="tree-header">
101
+ <${StatusBlock} pending_count="pending" failed_count="failed" dead_count="dead" successful_count="successful" total_count="total" />
102
+ </div>
103
+ <${TreeLevel} data=${tree_data} />
104
+ `;
105
+ }
106
+ }
107
+
108
+ render(html`<${TreeRoot} />`, document.querySelector('#batch-tree'));
@@ -0,0 +1,2 @@
1
+
2
+ export const root_url = (document.querySelector('meta[name="sidekiq-baseurl"]')).content;
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CanvasSync::JobBatches::Sidekiq
4
+ module Web
5
+ module Helpers
6
+ VIEW_PATH = File.expand_path("../web/views", __dir__)
7
+
8
+ module_function
9
+
10
+ def get_template(name)
11
+ File.open(File.join(VIEW_PATH, "#{name}.erb")).read
12
+ end
13
+
14
+ def drain_zset(key)
15
+ items, _ = CanvasSync::JobBatches::Batch.redis do |r|
16
+ r.multi do |r|
17
+ r.zrange(key, 0, -1)
18
+ r.zremrangebyrank(key, 0, -1)
19
+ end
20
+ end
21
+ yield items
22
+ end
23
+
24
+ def safe_relative_time(time)
25
+ time = parse_time(time)
26
+ relative_time(time)
27
+ end
28
+
29
+ def parse_time(time)
30
+ case time
31
+ when Time
32
+ time
33
+ when Integer, Float
34
+ Time.at(time)
35
+ else
36
+ Time.parse(time.to_s)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,6 @@
1
+
2
+ <div id="batch-tree" class="batch-tree">
3
+ <script id="initial-data" type="application/json"><%= @tree_data.to_json %></script>
4
+ </div>
5
+
6
+ <script type="module" src="<%= root_path %>batches_assets/js/batch_tree.js"></script>
@@ -0,0 +1,44 @@
1
+ <table class="table table-striped table-bordered table-hover">
2
+ <thead>
3
+ <tr>
4
+ <th rowspan="2"><%= t('Started') %></th>
5
+ <th rowspan="2"><%= t('BID') %></th>
6
+ <th rowspan="2"><%= t('Description') %></th>
7
+
8
+ <th colspan="5"><%= t('Jobs') %></th>
9
+ <th colspan="4"><%= t('Sub-Batches') %></th>
10
+ </tr>
11
+ <tr>
12
+ <th><%= t('Pending') %></th>
13
+ <th><%= t('Failed') %></th>
14
+ <th><%= t('Dead') %></th>
15
+ <th><%= t('Complete') %></th>
16
+ <th><%= t('Total') %></th>
17
+
18
+ <th><%= t('Pending') %></th>
19
+ <th><%= t('Failed') %></th>
20
+ <th><%= t('Success') %></th>
21
+ <th><%= t('Total') %></th>
22
+ </tr>
23
+ </thead>
24
+
25
+ <% batches.each do |batch| %>
26
+ <% status = CanvasSync::JobBatches::Batch::Status.new(batch) %>
27
+ <tr>
28
+ <td><%= safe_relative_time(batch.created_at.to_f) %></th>
29
+ <td><a href="<%= root_path %>batches/<%= batch.bid %>"><%= batch.bid %></a></td>
30
+ <td><%= batch.description %></th>
31
+
32
+ <td><%= status.pending %></th>
33
+ <td><%= status.failures %></th>
34
+ <td><%= status.dead %></th>
35
+ <td><%= status.completed_count %></th>
36
+ <td><%= status.job_count %></th>
37
+
38
+ <td><%= status.child_count - status.successful_children_count %></th>
39
+ <td><%= status.failed_children_count %></th>
40
+ <td><%= status.successful_children_count %></th>
41
+ <td><%= status.child_count %></th>
42
+ </tr>
43
+ <% end %>
44
+ </table>
@@ -0,0 +1,13 @@
1
+
2
+ <% add_to_head do %>
3
+ <meta name="sidekiq-baseurl" content="<%= root_path %>" />
4
+ <% if dev_mode? %>
5
+ <link href="<%= root_path %>batches_assets/css/styles.less" media="screen" rel="stylesheet/less" type="text/css" />
6
+ <script async type="text/javascript" src="https://unpkg.com/less@4.1.1/dist/less.js" data-async="true"></script>
7
+ <% else %>
8
+ <link href="<%= root_path %>batches_assets/css/styles.less" media="screen" rel="stylesheet/less" type="text/css" />
9
+ <script async type="text/javascript" src="https://unpkg.com/less@4.1.1/dist/less.js" data-async="true"></script>
10
+ <%# TODO: Pre-compile LESS %>
11
+ <!-- <link href="<%= root_path %>batches_assets/css/styles.css" media="screen" rel="stylesheet" type="text/css" /> -->
12
+ <% end %>
13
+ <% end %>
@@ -0,0 +1,21 @@
1
+ <table class="table table-striped table-bordered table-hover">
2
+ <thead>
3
+ <tr>
4
+ <th><%= t('JID') %></th>
5
+ <th><%= t('Job Class') %></th>
6
+ <th><%= t('Parameters') %></th>
7
+ </tr>
8
+ </thead>
9
+
10
+ <% @jobs.each do |job_desc| %>
11
+ <tr>
12
+ <td><%= job_desc[:jid] %></td>
13
+ <td><%= job_desc['job'] %></td>
14
+ <td>
15
+ <code class="code-wrap">
16
+ <div class="args-extended"><%= job_desc['parameters'].to_json %></div>
17
+ </code>
18
+ </td>
19
+ </tr>
20
+ <% end %>
21
+ </table>
@@ -0,0 +1,26 @@
1
+
2
+ <% if @total_size > @count %>
3
+ <ul class="pagination pull-right flip">
4
+ <% _page_key = defined?(page_key) ? page_key.to_s : 'page' %>
5
+
6
+ <li class="<%= 'disabled' if @current_page == 1 %>">
7
+ <a href="<%= url %>?<%= _page_key %>=1">&laquo;</a>
8
+ </li>
9
+ <% if @current_page > 1 %>
10
+ <li>
11
+ <a href="<%= url %>?<%= (qparams(page: @current_page - 1) || '').gsub('page', _page_key) %>"><%= @current_page - 1 %></a>
12
+ </li>
13
+ <% end %>
14
+ <li class="disabled">
15
+ <a href="<%= url %>?<%= (qparams(page: @current_page) || '').gsub('page', _page_key) %>"><%= @current_page %></a>
16
+ </li>
17
+ <% if @total_size > @current_page * @count %>
18
+ <li>
19
+ <a href="<%= url %>?<%= (qparams(page: @current_page + 1) || '').gsub('page', _page_key) %>"><%= @current_page + 1 %></a>
20
+ </li>
21
+ <% end %>
22
+ <li class="<%= 'disabled' if @total_size <= @current_page * @count %>">
23
+ <a href="<%= url %>?<%= (qparams(page: (@total_size.to_f / @count).ceil) || '').gsub('page', _page_key) %>">&raquo;</a>
24
+ </li>
25
+ </ul>
26
+ <% end %>
@@ -0,0 +1,81 @@
1
+ <%= erb get_template(:_common) %>
2
+
3
+ <h3><%= t('Batch') %></h3>
4
+ <% status = CanvasSync::JobBatches::Batch::Status.new(@batch) %>
5
+
6
+ <div class="table_container">
7
+ <table class="table table-striped table-bordered table-white table-hover">
8
+ <tbody>
9
+ <tr>
10
+ <th colspan="2" scope=row><%= t('Batch') %></td>
11
+ <td><%= @batch.bid %></td>
12
+ </tr>
13
+ <tr>
14
+ <th colspan="2" scope=row><%= t('Parent') %></td>
15
+ <td>
16
+ <% if @batch.parent_bid.present? %>
17
+ <a href="<%= root_path %>batches/<%= @batch.parent_bid %>"><%= @batch.parent_bid %></a>
18
+ <% else %>
19
+ ROOT
20
+ <% end %>
21
+ </td>
22
+ </tr>
23
+ <tr>
24
+ <th colspan="2" scope=row><%= t('Started') %></td>
25
+ <td><%= safe_relative_time(@batch.created_at.to_f) %></td>
26
+ </tr>
27
+ <tr>
28
+ <th colspan="2" scope=row><%= t('Description') %></td>
29
+ <td><%= @batch.description %></td>
30
+ </tr>
31
+ <tr>
32
+ <th colspan="2" scope=row><%= t('Context') %></td>
33
+ <td>
34
+ <code class="code-wrap batch-context">
35
+ <div class="args-extended"><%= format_context(@batch) %></div>
36
+ </code>
37
+ </td>
38
+ </tr>
39
+ </tbody>
40
+ </table>
41
+ </div>
42
+
43
+ <header class="row">
44
+ <div class="col-sm-5">
45
+ <h3>
46
+ <%= t('Child Batches') %>
47
+ </h3>
48
+ </div>
49
+ </header>
50
+
51
+ <%= erb get_template(:_batch_tree), locals: { } %>
52
+
53
+ <header class="row">
54
+ <div class="col-sm-5">
55
+ <h3>
56
+ <%= t('Jobs') %>
57
+ <i>(Queued and Current)</i>
58
+ </h3>
59
+ </div>
60
+ <%
61
+ @current_page = @current_jobs_page
62
+ @total_size = @total_jobs_size
63
+ %>
64
+ <% if @jobs.any? && @total_size > @count.to_i %>
65
+ <div class="col-sm-4">
66
+ <%= erb get_template(:_pagination), locals: { url: "#{root_path}batches/#{@batch.bid}", page_key: :job_page } %>
67
+ </div>
68
+ <% end %>
69
+ </header>
70
+
71
+ <% if @jobs.any? %>
72
+ <div class="table_container">
73
+ <%= erb get_template(:_jobs_table), locals: { jobs: @jobs } %>
74
+ </div>
75
+ <% end %>
76
+
77
+ <form class="form-horizontal" action="<%= root_path %>batches/<%= @batch.bid %>" method="post">
78
+ <%= csrf_tag %>
79
+ <a class="btn btn-default" href="<%= root_path %>batches"><%= t('GoBack') %></a>
80
+ <input class="btn btn-danger" type="submit" name="delete" value="<%= t('Delete') %>" data-confirm="<%= t('AreYouSure') %>" />
81
+ </form>
@@ -0,0 +1,23 @@
1
+ <header class="row">
2
+ <div class="col-sm-5">
3
+ <h3>
4
+ <%= t('Batches') %>
5
+ </h3>
6
+ </div>
7
+ <% if @batches.any? && @total_size > @count.to_i %>
8
+ <div class="col-sm-4">
9
+ <%= erb get_template(:_pagination), locals: { url: "#{root_path}batches" } %>
10
+ </div>
11
+ <% end %>
12
+ </header>
13
+
14
+ <% if @batches.any? %>
15
+ <div class="table_container">
16
+ <%= erb get_template(:_batches_table), locals: { batches: @batches } %>
17
+ </div>
18
+ <% end %>
19
+
20
+ <form action="<%= root_path %>batches/all" method="post">
21
+ <%= csrf_tag %>
22
+ <input class="btn btn-danger btn-xs pull-right flip" type="submit" name="delete" value="<%= t('DeleteAll') %>" data-confirm="<%= t('AreYouSure') %>" />
23
+ </form>