canvas_sync 0.17.17.beta1 → 0.17.23.beta1

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +45 -1
  3. data/lib/canvas_sync/concerns/sync_mapping.rb +112 -0
  4. data/lib/canvas_sync/generators/install_generator.rb +1 -0
  5. data/lib/canvas_sync/generators/templates/migrations/create_grading_period_groups.rb +22 -0
  6. data/lib/canvas_sync/generators/templates/migrations/create_grading_periods.rb +22 -0
  7. data/lib/canvas_sync/generators/templates/migrations/create_user_observers.rb +17 -0
  8. data/lib/canvas_sync/generators/templates/models/grading_period.rb +8 -0
  9. data/lib/canvas_sync/generators/templates/models/grading_period_group.rb +9 -0
  10. data/lib/canvas_sync/generators/templates/models/user_observer.rb +11 -0
  11. data/lib/canvas_sync/importers/bulk_importer.rb +27 -16
  12. data/lib/canvas_sync/job_batches/batch.rb +9 -0
  13. data/lib/canvas_sync/job_batches/chain_builder.rb +9 -1
  14. data/lib/canvas_sync/job_batches/hier_batch_ids.lua +25 -0
  15. data/lib/canvas_sync/job_batches/sidekiq/web/batches_assets/css/styles.less +178 -0
  16. data/lib/canvas_sync/job_batches/sidekiq/web/batches_assets/js/batch_tree.js +106 -0
  17. data/lib/canvas_sync/job_batches/sidekiq/web/batches_assets/js/util.js +2 -0
  18. data/lib/canvas_sync/job_batches/sidekiq/web/views/_batch_tree.erb +6 -0
  19. data/lib/canvas_sync/job_batches/sidekiq/web/views/_common.erb +13 -0
  20. data/lib/canvas_sync/job_batches/sidekiq/web/views/batch.erb +15 -88
  21. data/lib/canvas_sync/job_batches/sidekiq/web.rb +93 -0
  22. data/lib/canvas_sync/jobs/begin_sync_chain_job.rb +1 -1
  23. data/lib/canvas_sync/jobs/report_checker.rb +37 -4
  24. data/lib/canvas_sync/jobs/report_starter.rb +2 -2
  25. data/lib/canvas_sync/processors/assignment_groups_processor.rb +1 -7
  26. data/lib/canvas_sync/processors/assignments_processor.rb +1 -7
  27. data/lib/canvas_sync/processors/context_module_items_processor.rb +1 -7
  28. data/lib/canvas_sync/processors/context_modules_processor.rb +1 -7
  29. data/lib/canvas_sync/processors/model_mappings.yml +68 -0
  30. data/lib/canvas_sync/processors/normal_processor.rb +3 -3
  31. data/lib/canvas_sync/processors/provisioning_report_processor.rb +21 -63
  32. data/lib/canvas_sync/processors/report_processor.rb +14 -9
  33. data/lib/canvas_sync/processors/submissions_processor.rb +1 -7
  34. data/lib/canvas_sync/record.rb +4 -0
  35. data/lib/canvas_sync/version.rb +1 -1
  36. data/lib/canvas_sync.rb +4 -1
  37. data/spec/canvas_sync/processors/provisioning_report_processor_spec.rb +40 -0
  38. data/spec/dummy/app/models/grading_period.rb +14 -0
  39. data/spec/dummy/app/models/grading_period_group.rb +15 -0
  40. data/spec/dummy/app/models/user_observer.rb +17 -0
  41. data/spec/dummy/db/migrate/20210907233329_create_user_observers.rb +23 -0
  42. data/spec/dummy/db/migrate/20210907233330_create_grading_periods.rb +28 -0
  43. data/spec/dummy/db/migrate/20210907233331_create_grading_period_groups.rb +28 -0
  44. data/spec/dummy/db/schema.rb +42 -1
  45. data/spec/dummy/log/development.log +1167 -0
  46. data/spec/dummy/log/test.log +2775 -0
  47. data/spec/support/fixtures/reports/grading_period_groups.csv +2 -0
  48. data/spec/support/fixtures/reports/grading_periods.csv +3 -0
  49. data/spec/support/fixtures/reports/user_observers.csv +3 -0
  50. metadata +38 -17
@@ -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>
@@ -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"
@@ -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
@@ -4,8 +4,9 @@ module CanvasSync
4
4
  # Re-enqueues itself if the report is still processing on Canvas.
5
5
  # Enqueues the ReportProcessor when the report has completed.
6
6
  class ReportChecker < CanvasSync::Job
7
- REPORT_TIMEOUT = 12.hours
7
+ REPORT_TIMEOUT = 24.hours
8
8
  COMPILATION_TIMEOUT = 1.hour
9
+ MAX_TRIES = 3
9
10
 
10
11
  # @param report_name [Hash] e.g., 'provisioning_csv'
11
12
  # @param report_id [Integer]
@@ -13,6 +14,7 @@ module CanvasSync
13
14
  # @param options [Hash] hash of options that will be passed to the job processor
14
15
  # @return [nil]
15
16
  def perform(report_name, report_id, processor, options, checker_context = {}) # rubocop:disable Metrics/AbcSize
17
+ max_tries = options[:report_max_tries] || batch_context[:report_max_tries] || MAX_TRIES
16
18
  account_id = options[:account_id] || batch_context[:account_id] || "self"
17
19
  report_status = CanvasSync.get_canvas_sync_client(batch_context)
18
20
  .report_status(account_id, report_name, report_id)
@@ -27,9 +29,17 @@ module CanvasSync
27
29
  report_id,
28
30
  )
29
31
  when "error", "deleted"
30
- message = "Report failed to process; status was #{report_status} for report_name: #{report_name}, report_id: #{report_id}" # rubocop:disable Metrics/LineLength
32
+ checker_context[:failed_attempts] ||= 0
33
+ checker_context[:failed_attempts] += 1
34
+ failed_attempts = checker_context[:failed_attempts]
35
+ message = "Report failed to process; status was #{report_status} for report_name: #{report_name}, report_id: #{report_id}, #{current_organization.name}. This report has now failed #{checker_context[:failed_attempts]} time." # rubocop:disable Metrics/LineLength
31
36
  Rails.logger.error(message)
32
- raise message
37
+ if failed_attempts >= max_tries
38
+ Rails.logger.error("This report has failed #{failed_attempts} times. Giving up.")
39
+ raise message
40
+ else
41
+ restart_report(options, report_name, processor, checker_context)
42
+ end
33
43
  else
34
44
  report_timeout = parse_timeout(options[:report_timeout] || batch_context[:report_timeout] || REPORT_TIMEOUT)
35
45
  if timeout_met?(options[:sync_start_time], report_timeout)
@@ -51,7 +61,7 @@ module CanvasSync
51
61
  report_id,
52
62
  processor,
53
63
  options,
54
- checker_context,
64
+ checker_context
55
65
  )
56
66
  end
57
67
  end
@@ -66,6 +76,29 @@ module CanvasSync
66
76
  def parse_timeout(val)
67
77
  val
68
78
  end
79
+
80
+ def restart_report(options, report_name, processor, checker_context)
81
+ account_id = options[:account_id] || batch_context[:account_id] || "self"
82
+ options[:sync_start_time] = DateTime.now.utc.iso8601
83
+ new_context = {}
84
+ new_context[:failed_attempts] = checker_context[:failed_attempts]
85
+ report_id = start_report(account_id, report_name, options[:report_params])
86
+ CanvasSync::Jobs::ReportChecker
87
+ .set(wait: report_checker_wait_time)
88
+ .perform_later(
89
+ report_name,
90
+ report_id,
91
+ processor,
92
+ options,
93
+ new_context
94
+ )
95
+ end
96
+
97
+ def start_report(account_id, report_name, report_params)
98
+ report = CanvasSync.get_canvas_sync_client(batch_context)
99
+ .start_report(account_id, report_name, report_params)
100
+ report["id"]
101
+ end
69
102
  end
70
103
  end
71
104
  end