canvas_sync 0.17.15 → 0.17.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ace82ecc5e7e541a763a459e209058e3c08e4f0e9fb340796d604364fed3f612
4
- data.tar.gz: 63fa9684557f40172f59a551912907618fc87ce8cfc63f461e2f0ef885994e8a
3
+ metadata.gz: d995ce469310b7b54158745305c968edd4c7a910bcad87891ffabfa29cd22a30
4
+ data.tar.gz: 83a0bb75ceb290402137f2531e1925d4872a0e272fcabf75a6c3451b3de5f721
5
5
  SHA512:
6
- metadata.gz: c87187f0881a49323df67c73d5ce46e15d279513e2dd2d02919c2eec1be36b955d587d2f7df3052eb34da3388d7e7286e74fe8031dd2b8ad79a4b462ec0f1b76
7
- data.tar.gz: 7256ab41fc3a60271a81413b8cbfad401ea731068fc962fd2a645c4f16e4ce690367bfe3d50d6575d4aae936bdbfa9ed4a6450c3401e94347941fd04383f4bcb
6
+ metadata.gz: 9287196092064977d34259de638cfd41cf189f6dc9b6a97c6fd507d2555f8f3b0527643d38991765d56bf428856407608ef0c1834b48b6a6dd3201ed232b9879
7
+ data.tar.gz: a751ad1e08d3feb2716e41d4c552f983f2d83590321dc4d1cc2a18d2f800859587fa1884272bbdbaff0cc4aefe867c0af8f0dfb24c2dc6aba25190e7a190449a
@@ -2,7 +2,7 @@ module CanvasSync
2
2
  # An array that "processes" after so many items are added.
3
3
  #
4
4
  # Example Usage:
5
- # batches = BatchProcessor.new(of: 1000) do |batch|
5
+ # batches = CanvasSync::BatchProcessor.new(of: 1000) do |batch|
6
6
  # # Process the batch somehow
7
7
  # end
8
8
  # enumerator_of_some_kind.each { |item| batches << item }
@@ -30,27 +30,27 @@ module CanvasSync::Concerns
30
30
  end
31
31
 
32
32
  def bulk_sync_from_api_result(api_array, conflict_target: :canvas_id, import_args: {}, all_pages: true, batch_size: 1000)
33
- columns = api_sync_options.keys
33
+ columns = api_sync_options[:field_map].keys
34
34
 
35
35
  update_conditions = {
36
- condition: Importers::BulkImporter.condition_sql(self, columns),
36
+ condition: CanvasSync::Importers::BulkImporter.condition_sql(self, columns),
37
37
  columns: columns,
38
38
  }
39
39
  update_conditions[:conflict_target] = conflict_target if conflict_target.present?
40
40
  options = { validate: false, on_duplicate_key_update: update_conditions }.merge(import_args)
41
41
 
42
42
  if all_pages
43
- batcher = BatchProcessor.new(of: batch_size) do |batch|
43
+ batcher = CanvasSync::BatchProcessor.new(of: batch_size) do |batch|
44
44
  import(columns, batch, options)
45
45
  end
46
46
  api_array.all_pages_each do |api_item|
47
- item = new.assign_from_api_params(api_items)
47
+ item = new.assign_from_api_params(api_item)
48
48
  batcher << item
49
49
  end
50
50
  batcher.flush
51
51
  else
52
52
  items = api_array.map do |api_item|
53
- new.assign_from_api_params(api_items)
53
+ new.assign_from_api_params(api_item)
54
54
  end
55
55
  import(columns, batch, options)
56
56
  end
@@ -26,18 +26,24 @@ module CanvasSync
26
26
  def self.perform_in_batches(report_file_path, mapping, klass, conflict_target, import_args: {})
27
27
  csv_column_names = mapping.keys
28
28
  database_column_names = mapping.values.map { |value| value[:database_column_name] }
29
- database_conflict_column_name = conflict_target ? mapping[conflict_target][:database_column_name] : nil
29
+
30
+ puts mapping.inspect
31
+
32
+ conflict_target = Array(conflict_target).map(&:to_sym)
33
+ database_conflict_column_name = conflict_target.map{|ct| mapping[ct][:database_column_name] }
30
34
 
31
35
  row_ids = {}
32
- batcher = BatchProcessor.new(of: batch_size) do |batch|
36
+ batcher = CanvasSync::BatchProcessor.new(of: batch_size) do |batch|
33
37
  row_ids = {}
34
38
  perform_import(klass, database_column_names, batch, database_conflict_column_name, import_args)
35
39
  end
36
40
 
37
41
  row_buffer_out = ->(row) {
38
- if conflict_target
39
- next if row_ids[row[conflict_target]]
40
- row_ids[row[conflict_target]] = true
42
+ if conflict_target.present?
43
+ key = conflict_target.map{|ct| row[ct] }
44
+ next if row_ids[key]
45
+
46
+ row_ids[key] = true
41
47
  end
42
48
 
43
49
  formatted_row = csv_column_names.map do |column|
@@ -79,7 +85,7 @@ module CanvasSync
79
85
  condition: condition_sql(klass, columns, import_args[:sync_start_time]),
80
86
  columns: columns
81
87
  }
82
- update_conditions[:conflict_target] = conflict_target if conflict_target
88
+ update_conditions[:conflict_target] = conflict_target if conflict_target.present?
83
89
 
84
90
  options = { validate: false, on_duplicate_key_update: update_conditions }.merge(import_args)
85
91
  options.delete(:on_duplicate_key_update) if options.key?(:on_duplicate_key_ignore)
@@ -96,7 +102,7 @@ module CanvasSync
96
102
  # started_at = Time.now
97
103
  # run_the_users_sync!
98
104
  # changed = User.where("updated_at >= ?", started_at)
99
- def self.condition_sql(klass, columns, report_start)
105
+ def self.condition_sql(klass, columns, report_start = nil)
100
106
  columns_str = columns.map { |c| "#{klass.quoted_table_name}.#{c}" }.join(", ")
101
107
  excluded_str = columns.map { |c| "EXCLUDED.#{c}" }.join(", ")
102
108
  condition_sql = "(#{columns_str}) IS DISTINCT FROM (#{excluded_str})"
@@ -28,6 +28,7 @@ module CanvasSync
28
28
 
29
29
  BID_EXPIRE_TTL = 2_592_000
30
30
  SCHEDULE_CALLBACK = RedisScript.new(Pathname.new(__FILE__) + "../schedule_callback.lua")
31
+ BID_HIERARCHY = RedisScript.new(Pathname.new(__FILE__) + "../hier_batch_ids.lua")
31
32
 
32
33
  attr_reader :bid
33
34
 
@@ -423,6 +424,14 @@ module CanvasSync
423
424
  def push_callbacks(args, queue)
424
425
  Batch::Callback::worker_class.enqueue_all(args, queue)
425
426
  end
427
+
428
+ def bid_hierarchy(bid, depth: 4, per_depth: 5, slice: nil)
429
+ args = [bid, depth, per_depth]
430
+ args << slice if slice
431
+ redis do |r|
432
+ BID_HIERARCHY.call(r, [], args)
433
+ end
434
+ end
426
435
  end
427
436
  end
428
437
 
@@ -172,6 +172,14 @@ module CanvasSync
172
172
  mapper[key] ||= []
173
173
  end
174
174
 
175
+ # TODO: Add a Chain progress web View
176
+ # Augment Batch tree-view with Chain data
177
+ # > [DONE] Tree view w/o Chain will only show Parent/Current batches and Job Counts
178
+ # > If augmented with Chain data, the above will be annotated with Chain-related info and will be able to show Jobs defined in the Chain
179
+ # > Chain-jobs will be supplied chain_id and chain_step_id metadata
180
+ # > Using server-middleware, if a Chain-job (has chain_id and chain_step_id) creates a Batch, tag the Batch w/ the chain_id and chain_step_id
181
+ # > UI will map Batches to Chain-steps using the chain_step_id. UI will add entries for any Chain-steps that were not tied to a Batch
182
+ # > [DONE] Use a Lua script to find child batch IDs. Support max_depth, items_per_depth, top_depth_slice parameters
175
183
  def enqueue_job(job_def)
176
184
  job_class = job_def[:job].constantize
177
185
  job_options = job_def[:parameters] || []
@@ -0,0 +1,25 @@
1
+
2
+ local function add_bids(root, depth)
3
+ local result_data = {}
4
+
5
+ if depth > 0 then
6
+ local sbids
7
+ if depth == tonumber(ARGV[2]) and ARGV[4] then
8
+ local min, max = ARGV[4]:match('(%d+):(%d+)')
9
+ sbids = redis.call('ZRANGE', 'BID-' .. root .. '-bids', min, max)
10
+ else
11
+ sbids = redis.call('ZRANGE', 'BID-' .. root .. '-bids', 0, tonumber(ARGV[3]) - 1)
12
+ end
13
+
14
+ local sub_data = {}
15
+ for _,v in ipairs(sbids) do
16
+ table.insert(sub_data, add_bids(v, depth - 1))
17
+ end
18
+
19
+ return { root, sub_data }
20
+ end
21
+
22
+ return { root, result_data}
23
+ end
24
+
25
+ return add_bids(ARGV[1], tonumber(ARGV[2]))
@@ -9,11 +9,19 @@ require_relative "web/helpers"
9
9
 
10
10
  module CanvasSync::JobBatches::Sidekiq
11
11
  module Web
12
+ DEV_MODE = (defined?(Rails) && !Rails.env.production?) || !!ENV["SIDEKIQ_WEB_TESTING"]
13
+
12
14
  def self.registered(app) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
13
15
  app.helpers do
14
16
  include Web::Helpers
17
+
18
+ def dev_mode?
19
+ DEV_MODE
20
+ end
15
21
  end
16
22
 
23
+ # =============== BATCHES =============== #
24
+
17
25
  app.get "/batches" do
18
26
  @count = (params['count'] || 25).to_i
19
27
  @current_page, @total_size, @batches = page('batches', params['page'], @count)
@@ -26,6 +34,8 @@ module CanvasSync::JobBatches::Sidekiq
26
34
  @bid = params[:bid]
27
35
  @batch = CanvasSync::JobBatches::Batch.new(@bid)
28
36
 
37
+ @tree_data = tree_data(@bid)
38
+
29
39
  @count = (params['count'] || 25).to_i
30
40
  @current_batches_page, @total_batches_size, @sub_batches = page("BID-#{@batch.bid}-bids", params['batch_page'], @count)
31
41
  @sub_batches = @sub_batches.map {|b, score| CanvasSync::JobBatches::Batch.new(b) }
@@ -36,6 +46,81 @@ module CanvasSync::JobBatches::Sidekiq
36
46
  erb(get_template(:batch))
37
47
  end
38
48
 
49
+ app.get "/batches/:bid/tree" do
50
+ @bid = params[:bid]
51
+
52
+ json(tree_data(@bid, slice: params[:slice]))
53
+ end
54
+
55
+ app.helpers do
56
+ def tree_data(root_bid, slice: nil)
57
+ tree_bids = CanvasSync::JobBatches::Batch.bid_hierarchy(root_bid, slice: slice)
58
+
59
+ CanvasSync::JobBatches::Batch.redis do |r|
60
+ layer_data = ->(layer, parent = nil) {
61
+ bid = layer[0]
62
+ batch = CanvasSync::JobBatches::Batch.new(bid)
63
+
64
+ jobs_total = r.hget("BID-#{bid}", "job_count").to_i
65
+ jobs_pending = r.hget("BID-#{bid}", 'pending').to_i
66
+ jobs_failed = r.scard("BID-#{bid}-failed").to_i
67
+ jobs_success = jobs_total - jobs_pending
68
+
69
+ batches_total = r.hget("BID-#{bid}", 'children').to_i
70
+ batches_success = r.scard("BID-#{bid}-batches-success").to_i
71
+ batches_pending = batches_total - batches_success
72
+ batches_failed = r.scard("BID-#{bid}-batches-failed").to_i
73
+
74
+ status = 'in_progress'
75
+ status = 'complete' if batches_pending == batches_failed && jobs_pending == jobs_failed
76
+ status = 'success' if batches_pending == 0 && jobs_pending == 0
77
+ status = 'deleted' if bid != root_bid && !batch.parent_bid
78
+
79
+ {
80
+ bid: bid,
81
+ created_at: r.hget("BID-#{bid}", 'created_at'),
82
+ status: status,
83
+ parent_bid: parent ? parent.bid : batch.parent_bid,
84
+ description: batch.description,
85
+ jobs: {
86
+ pending_count: jobs_pending,
87
+ successful_count: jobs_success,
88
+ failed_count: jobs_failed,
89
+ total_count: jobs_total,
90
+ # items: batches.map{|b| layer_data[b] },
91
+ },
92
+ batches: {
93
+ pending_count: batches_pending,
94
+ successful_count: batches_success,
95
+ failed_count: batches_failed,
96
+ total_count: batches_total,
97
+ items: layer[1].map{|b| layer_data[b, batch] },
98
+ },
99
+ }
100
+ }
101
+
102
+ data = layer_data[tree_bids]
103
+ data[:batches][:slice] = slice if slice
104
+ data
105
+ end
106
+ end
107
+
108
+ def format_context(batch)
109
+ bits = []
110
+ own_keys = batch.context.own.keys
111
+ batch.context.flatten.each do |k,v|
112
+ added = own_keys.include? k
113
+ bits << " <span class=\"key #{added ? 'own' : 'inherited'}\">\"#{k}\": #{v.to_json},</span>"
114
+ end
115
+ bits = [
116
+ "{ // <span class=\"own\">Added</span> / <span class=\"inherited\">Inherited</span>",
117
+ *bits,
118
+ '}'
119
+ ]
120
+ bits.join("\n")
121
+ end
122
+ end
123
+
39
124
  app.post "/batches/all" do
40
125
  if params['delete']
41
126
  drain_zset('batches') do |batches|
@@ -107,6 +192,14 @@ module CanvasSync::JobBatches::Sidekiq
107
192
  end
108
193
 
109
194
  if defined?(::Sidekiq::Web)
195
+ rules = []
196
+ rules = [[:all, {"Cache-Control" => "public, max-age=86400"}]] unless CanvasSync::JobBatches::Sidekiq::Web::DEV_MODE
197
+
198
+ ::Sidekiq::Web.use Rack::Static, urls: ["/batches_assets"],
199
+ root: File.expand_path("#{File.dirname(__FILE__)}/web"),
200
+ cascade: true,
201
+ header_rules: rules
202
+
110
203
  ::Sidekiq::Web.register CanvasSync::JobBatches::Sidekiq::Web
111
204
  ::Sidekiq::Web.tabs["Batches"] = "batches"
112
205
  ::Sidekiq::Web.tabs["Pools"] = "pools"
@@ -0,0 +1,178 @@
1
+
2
+ @color-green: #25c766;
3
+ @color-red: #c7254e;
4
+ @color-yellow: #c4c725;
5
+
6
+ .code-wrap.batch-context .args-extended {
7
+ white-space: pre;
8
+
9
+ .key {
10
+ white-space: pre-wrap;
11
+ margin-left: 2em;
12
+ text-indent: -2em;
13
+ display: inline-block;
14
+ }
15
+
16
+ .own {
17
+ color: @color-green;
18
+ }
19
+ }
20
+
21
+
22
+ .batch-tree {
23
+ .status-block {
24
+ .tree-stat {
25
+ margin: 0 4px;
26
+
27
+ &.pending {
28
+ color: @color-yellow;
29
+ }
30
+ &.failed {
31
+ color: @color-red;
32
+ }
33
+ &.success {
34
+ color: @color-green;
35
+ }
36
+ &.total {
37
+
38
+ }
39
+ }
40
+ }
41
+
42
+ .text-inactive {
43
+ color: darken(#fff, 35%);
44
+ font-size: 80%;
45
+ }
46
+
47
+ .tree-header {
48
+ position: relative;
49
+
50
+ .status-block {
51
+ position: absolute;
52
+ bottom: 0;
53
+ width: 100%;
54
+
55
+ margin-right: 8px;
56
+ font-size: 90%;
57
+ text-align: right;
58
+
59
+ .tree-stat {
60
+ font-style: italic;
61
+ }
62
+ }
63
+ }
64
+
65
+ .tree-entry {
66
+ > .header {
67
+ display: flex;
68
+ align-items: center;
69
+
70
+ .header-inner {
71
+ padding: 4px 0;
72
+ border-bottom: 1px dashed white;
73
+ display: flex;
74
+ align-items: center;
75
+ flex: 1;
76
+ }
77
+
78
+ &:hover {
79
+ background-color: rgba(0,0,0,0.20);
80
+ border-radius: 3px;
81
+ }
82
+
83
+ .row-toggle {
84
+ width: 16px;
85
+ height: 16px;
86
+ text-align: center;
87
+ align-self: center;
88
+ border-radius: 50%;
89
+ border: 1px solid #999;
90
+ text-decoration: none;
91
+ margin: 0 4px;
92
+ font-size: 16px;
93
+ line-height: 15px;
94
+
95
+ &.not_applicable {
96
+ opacity: 0;
97
+ pointer-events: none;
98
+ }
99
+ }
100
+
101
+ .main {
102
+ flex: 1;
103
+ display: flex;
104
+ align-items: baseline;
105
+
106
+ .bid {
107
+ font-family: monospace;
108
+ padding: 3px 6px;
109
+ background: rgba(0,0,0,0.2);
110
+ border-radius: 3px;
111
+ font-size: 12px;
112
+ margin: 0 12px 0 0;
113
+
114
+ &:hover {
115
+ .bid-goto {
116
+ display: inline-block;
117
+ padding: 0 0 0 4px;
118
+ font-size: 200%;
119
+ line-height: 10px;
120
+ vertical-align: sub;
121
+ text-decoration: dotted;
122
+ }
123
+ }
124
+
125
+ .bid-goto {
126
+ display: none;
127
+ }
128
+ }
129
+ }
130
+
131
+ .goto-link {
132
+ margin: 0 8px;
133
+ display: inline-block;
134
+ height: 16px;
135
+ font-size: 90%;
136
+ border-bottom: 1px dotted white;
137
+ }
138
+
139
+ .status-label {
140
+ font-family: monospace;
141
+ padding: 3px 6px;
142
+ background: rgba(0,0,0,0.2);
143
+ border-radius: 3px;
144
+ font-size: 12px;
145
+ margin: 0 12px 0 0;
146
+
147
+ &.deleted {
148
+ background: #99999933;
149
+ }
150
+ &.failed, &.complete {
151
+ background: #99000033;
152
+ }
153
+ &.success {
154
+ background: #00990033;
155
+ }
156
+ }
157
+
158
+ .status-block {
159
+ width: 10em;
160
+ text-align: center;
161
+ }
162
+ }
163
+
164
+ > .subitems {
165
+ padding-left: 16px;
166
+
167
+ >.load-more {
168
+ padding: 4px 0;
169
+ text-align: center;
170
+ border-bottom: 1px dashed white;
171
+ a {
172
+ border-bottom: 1px dotted white;
173
+ text-decoration: none;
174
+ }
175
+ }
176
+ }
177
+ }
178
+ }
@@ -0,0 +1,106 @@
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 success">${props.successful_count}</span>
16
+ /
17
+ <span class="tree-stat total">${props.total_count}</span>
18
+ </div>
19
+ `
20
+
21
+ class TreeLevel extends Component {
22
+ get bid() {
23
+ return this.props.data.bid;
24
+ }
25
+
26
+ get batch() {
27
+ return this.props.data;
28
+ }
29
+
30
+ load_more = async (event) => {
31
+ event.preventDefault();
32
+ const l = this.batch.batches.items.length;
33
+ const resp = await fetch(`${root_url}batches/${this.bid}/tree?slice=${l}:${l + 5 - 1}`)
34
+ const result = await resp.json()
35
+ const newEntries = result.batches.items;
36
+ for (let ent of newEntries) {
37
+ this.batch.batches.items.push(ent)
38
+ }
39
+ this.forceUpdate()
40
+ }
41
+
42
+ toggle = (event) => {
43
+ event.preventDefault();
44
+ this.setState({ collapsed: !this.state.collapsed })
45
+ }
46
+
47
+ render() {
48
+ const { data: bd } = this.props;
49
+
50
+ let sub_entries = [];
51
+ let sub_batches = bd.batches.items;
52
+ for (let b of sub_batches) {
53
+ sub_entries.push(html`<${TreeLevel} data=${b} />`)
54
+ }
55
+
56
+ let fully_loaded = !(sub_batches.length < bd.batches.total_count);
57
+
58
+ const load_more_elem = html`<div class="load-more">
59
+ ${sub_entries.length} / ${bd.batches.total_count} Items Loaded - <a href="#" onClick=${this.load_more}>Load More</a>
60
+ </div>`
61
+
62
+ return html`<div class="tree-entry tree-batch">
63
+ <div class="header">
64
+ <a class="row-toggle ${!bd.batches.total_count && 'not_applicable'}" onClick=${this.toggle} href="#">
65
+ ${this.state.collapsed ? '+' : '-'}
66
+ </a>
67
+
68
+ <div class="header-inner">
69
+ <div class="main">
70
+ <span class="bid">
71
+ ${bd.bid}
72
+ <a class="bid-goto" href="${root_url}batches/${bd.bid}">⇢</a>
73
+ </span>
74
+ ${bd.description || (bd.status == 'deleted' && html`<i class="text-inactive">Deleted</i>`) || html`<i class="text-inactive">No Description</i>`}
75
+ </div>
76
+
77
+ <span class="status-label ${bd.status}">${bd.status}</span>
78
+
79
+ ${bd.status != 'deleted' && html`
80
+ <${StatusBlock} class="job-status" title="Jobs" ...${bd.jobs} />
81
+ <${StatusBlock} class="batch-status" title="Batches" ...${bd.batches} />
82
+ `}
83
+ </div>
84
+ </div>
85
+
86
+ <div class="subitems ${this.state.collapsed ? 'hidden' : ''}">
87
+ ${sub_entries}
88
+ ${!fully_loaded && load_more_elem}
89
+ </div>
90
+ </div>`
91
+ }
92
+ }
93
+
94
+ class TreeRoot extends Component {
95
+ render() {
96
+ const tree_data = JSON.parse(document.querySelector('#batch-tree #initial-data').innerHTML);
97
+ return html`
98
+ <div class="tree-header">
99
+ <${StatusBlock} pending_count="pending" failed_count="failed" successful_count="successful" total_count="total" />
100
+ </div>
101
+ <${TreeLevel} data=${tree_data} />
102
+ `;
103
+ }
104
+ }
105
+
106
+ 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,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,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 %>
@@ -1,3 +1,5 @@
1
+ <%= erb get_template(:_common) %>
2
+
1
3
  <h3><%= t('Batch') %></h3>
2
4
  <% status = CanvasSync::JobBatches::Batch::Status.new(@batch) %>
3
5
 
@@ -17,70 +19,13 @@
17
19
  <td><%= @batch.description %></td>
18
20
  </tr>
19
21
  <tr>
20
- <th colspan="2" scope=row><%= t('Added Context') %></td>
22
+ <th colspan="2" scope=row><%= t('Context') %></td>
21
23
  <td>
22
- <code class="code-wrap">
23
- <div class="args-extended"><%= @batch.context.own.to_json %></div>
24
+ <code class="code-wrap batch-context">
25
+ <div class="args-extended"><%= format_context(@batch) %></div>
24
26
  </code>
25
27
  </td>
26
28
  </tr>
27
- <tr>
28
- <th colspan="2" scope=row><%= t('Full Context') %></td>
29
- <td>
30
- <code class="code-wrap">
31
- <div class="args-extended"><%= @batch.context.flatten.to_json %></div>
32
- </code>
33
- </td>
34
- </tr>
35
-
36
- <tr>
37
- <th colspan="3">Jobs</th>
38
- </tr>
39
- <tr>
40
- <th></th>
41
- <th><%= t('Pending') %></th>
42
- <td><%= status.pending %></th>
43
- </tr>
44
- <tr>
45
- <th></th>
46
- <th><%= t('Failed') %></th>
47
- <td><%= status.failures %></th>
48
- </tr>
49
- <tr>
50
- <th></th>
51
- <th><%= t('Complete') %></th>
52
- <td><%= status.completed_count %></th>
53
- </tr>
54
- <tr>
55
- <th></th>
56
- <th><%= t('Total') %></th>
57
- <td><%= status.job_count %></th>
58
- </tr>
59
-
60
- <tr>
61
- <th colspan="3">Batches</th>
62
- </tr>
63
- <tr>
64
- <th></th>
65
- <th><%= t('Pending') %></th>
66
- <td><%= status.child_count - status.successful_children_count %></th>
67
- </tr>
68
- <tr>
69
- <th></th>
70
- <th><%= t('Failed') %></th>
71
- <td><%= status.failed_children_count %></th>
72
- </tr>
73
- <tr>
74
- <th></th>
75
- <th><%= t('Success') %></th>
76
- <td><%= status.successful_children_count %></th>
77
- </tr>
78
- <tr>
79
- <th></th>
80
- <th><%= t('Total') %></th>
81
- <td><%= status.child_count %></th>
82
- </tr>
83
-
84
29
  </tbody>
85
30
  </table>
86
31
  </div>
@@ -88,51 +33,33 @@
88
33
  <header class="row">
89
34
  <div class="col-sm-5">
90
35
  <h3>
91
- <%= t('Jobs') %>
36
+ <%= t('Child Batches') %>
92
37
  </h3>
93
38
  </div>
94
- <%
95
- @current_page = @current_jobs_page
96
- @total_size = @total_jobs_size
97
- %>
98
- <% if @jobs.any? && @total_size > @count.to_i %>
99
- <div class="col-sm-4">
100
- <%= erb get_template(:_pagination), locals: { url: "#{root_path}batches/#{@batch.bid}", page_key: :job_page } %>
101
- </div>
102
- <% end %>
103
39
  </header>
104
40
 
105
- <% if @jobs.any? %>
106
- <div class="table_container">
107
- <%= erb get_template(:_jobs_table), locals: { jobs: @jobs } %>
108
- </div>
109
- <% end %>
41
+ <%= erb get_template(:_batch_tree), locals: { } %>
110
42
 
111
43
  <header class="row">
112
44
  <div class="col-sm-5">
113
45
  <h3>
114
- <%= t('Child Batches') %>
46
+ <%= t('Jobs') %>
47
+ <i>(Queued and Current)</i>
115
48
  </h3>
116
49
  </div>
117
50
  <%
118
- @current_page = @current_batches_page
119
- @total_size = @total_batches_size
51
+ @current_page = @current_jobs_page
52
+ @total_size = @total_jobs_size
120
53
  %>
121
- <% if @sub_batches.any? && @total_size > @count.to_i %>
54
+ <% if @jobs.any? && @total_size > @count.to_i %>
122
55
  <div class="col-sm-4">
123
- <%= erb get_template(:_pagination), locals: { url: "#{root_path}batches/#{@batch.bid}", page_key: :batch_page } %>
56
+ <%= erb get_template(:_pagination), locals: { url: "#{root_path}batches/#{@batch.bid}", page_key: :job_page } %>
124
57
  </div>
125
58
  <% end %>
126
59
  </header>
127
60
 
128
- <% if @sub_batches.any? %>
61
+ <% if @jobs.any? %>
129
62
  <div class="table_container">
130
- <%= erb get_template(:_batches_table), locals: { batches: @sub_batches } %>
63
+ <%= erb get_template(:_jobs_table), locals: { jobs: @jobs } %>
131
64
  </div>
132
65
  <% end %>
133
-
134
- <form class="form-horizontal" action="<%= root_path %>batches/<%= @batch.bid %>" method="post">
135
- <%= csrf_tag %>
136
- <a class="btn btn-default" href="<%= root_path %>batches"><%= t('GoBack') %></a>
137
- <input class="btn btn-danger" type="submit" name="delete" value="<%= t('Delete') %>" data-confirm="<%= t('AreYouSure') %>" />
138
- </form>
@@ -46,7 +46,7 @@ module CanvasSync
46
46
  m = Regexp.last_match
47
47
  day = m[1]
48
48
  skip = m[2] || "1"
49
- Date.new.send(:"#{day}?") && last_full_sync.end_of_day <= (skip.to_i.weeks.ago.end_of_day)
49
+ DateTime.now.send(:"#{day}?") && last_full_sync.end_of_day <= (skip.to_i.weeks.ago.end_of_day)
50
50
  when opt.match?(%r{^(\d+)\%$})
51
51
  m = Regexp.last_match
52
52
  rand(100) < m[1].to_i
@@ -12,7 +12,7 @@ module CanvasSync
12
12
  # @param options [Hash] hash of options that will be passed to the job processor
13
13
  # @return [nil]
14
14
  def perform(report_name, report_url, processor, options, report_id)
15
- @job_log.update_attributes(job_class: processor)
15
+ @job_log.update(job_class: processor)
16
16
  download(report_name, report_url) do |file_path|
17
17
  options = batch_context.merge(options).merge({
18
18
  report_processor_job_id: @job_log.job_id
@@ -1,3 +1,3 @@
1
1
  module CanvasSync
2
- VERSION = "0.17.15".freeze
2
+ VERSION = "0.17.19".freeze
3
3
  end
@@ -35,7 +35,7 @@ RSpec.describe Assignment, type: :model do
35
35
  let!(:matching_course) { FactoryGirl.create(:course) }
36
36
 
37
37
  before do
38
- subject.update_attributes(canvas_context_type: "Course", canvas_context_id: matching_course.canvas_id)
38
+ subject.update(canvas_context_type: "Course", canvas_context_id: matching_course.canvas_id)
39
39
  end
40
40
 
41
41
  it "should belong to courses where the canvas_context_type is Course and canvas_context_id is the canvas_course_id" do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: canvas_sync
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.15
4
+ version: 0.17.19
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nate Collings
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-06-29 00:00:00.000000000 Z
11
+ date: 2021-08-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -442,6 +442,7 @@ files:
442
442
  - lib/canvas_sync/job_batches/callback.rb
443
443
  - lib/canvas_sync/job_batches/chain_builder.rb
444
444
  - lib/canvas_sync/job_batches/context_hash.rb
445
+ - lib/canvas_sync/job_batches/hier_batch_ids.lua
445
446
  - lib/canvas_sync/job_batches/hincr_max.lua
446
447
  - lib/canvas_sync/job_batches/jobs/base_job.rb
447
448
  - lib/canvas_sync/job_batches/jobs/concurrent_batch_job.rb
@@ -453,8 +454,13 @@ files:
453
454
  - lib/canvas_sync/job_batches/schedule_callback.lua
454
455
  - lib/canvas_sync/job_batches/sidekiq.rb
455
456
  - lib/canvas_sync/job_batches/sidekiq/web.rb
457
+ - lib/canvas_sync/job_batches/sidekiq/web/batches_assets/css/styles.less
458
+ - lib/canvas_sync/job_batches/sidekiq/web/batches_assets/js/batch_tree.js
459
+ - lib/canvas_sync/job_batches/sidekiq/web/batches_assets/js/util.js
456
460
  - lib/canvas_sync/job_batches/sidekiq/web/helpers.rb
461
+ - lib/canvas_sync/job_batches/sidekiq/web/views/_batch_tree.erb
457
462
  - lib/canvas_sync/job_batches/sidekiq/web/views/_batches_table.erb
463
+ - lib/canvas_sync/job_batches/sidekiq/web/views/_common.erb
458
464
  - lib/canvas_sync/job_batches/sidekiq/web/views/_jobs_table.erb
459
465
  - lib/canvas_sync/job_batches/sidekiq/web/views/_pagination.erb
460
466
  - lib/canvas_sync/job_batches/sidekiq/web/views/batch.erb
@@ -677,7 +683,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
677
683
  - !ruby/object:Gem::Version
678
684
  version: '0'
679
685
  requirements: []
680
- rubygems_version: 3.0.3.1
686
+ rubygems_version: 3.0.3
681
687
  signing_key:
682
688
  specification_version: 4
683
689
  summary: Gem for generating Canvas models and migrations and syncing data from Canvas