canvas_sync 0.16.4 → 0.17.0.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.
- checksums.yaml +4 -4
- data/README.md +49 -137
- data/app/models/canvas_sync/sync_batch.rb +5 -0
- data/db/migrate/20170915210836_create_canvas_sync_job_log.rb +12 -31
- data/db/migrate/20180725155729_add_job_id_to_canvas_sync_job_logs.rb +4 -13
- data/db/migrate/20190916154829_add_fork_count_to_canvas_sync_job_logs.rb +3 -11
- data/db/migrate/20201018210836_create_canvas_sync_sync_batches.rb +11 -0
- data/lib/canvas_sync.rb +35 -118
- data/lib/canvas_sync/job.rb +5 -5
- data/lib/canvas_sync/job_batches/batch.rb +399 -0
- data/lib/canvas_sync/job_batches/batch_aware_job.rb +62 -0
- data/lib/canvas_sync/job_batches/callback.rb +153 -0
- data/lib/canvas_sync/job_batches/chain_builder.rb +203 -0
- data/lib/canvas_sync/job_batches/context_hash.rb +147 -0
- data/lib/canvas_sync/job_batches/jobs/base_job.rb +7 -0
- data/lib/canvas_sync/job_batches/jobs/concurrent_batch_job.rb +18 -0
- data/lib/canvas_sync/job_batches/jobs/serial_batch_job.rb +73 -0
- data/lib/canvas_sync/job_batches/sidekiq.rb +91 -0
- data/lib/canvas_sync/job_batches/status.rb +63 -0
- data/lib/canvas_sync/jobs/begin_sync_chain_job.rb +34 -0
- data/lib/canvas_sync/jobs/report_checker.rb +3 -6
- data/lib/canvas_sync/jobs/report_processor_job.rb +2 -5
- data/lib/canvas_sync/jobs/report_starter.rb +27 -19
- data/lib/canvas_sync/jobs/sync_accounts_job.rb +3 -5
- data/lib/canvas_sync/jobs/sync_admins_job.rb +2 -4
- data/lib/canvas_sync/jobs/sync_assignment_groups_job.rb +2 -4
- data/lib/canvas_sync/jobs/sync_assignments_job.rb +2 -4
- data/lib/canvas_sync/jobs/sync_context_module_items_job.rb +2 -4
- data/lib/canvas_sync/jobs/sync_context_modules_job.rb +2 -4
- data/lib/canvas_sync/jobs/sync_provisioning_report_job.rb +4 -31
- data/lib/canvas_sync/jobs/sync_roles_job.rb +2 -5
- data/lib/canvas_sync/jobs/sync_simple_table_job.rb +11 -32
- data/lib/canvas_sync/jobs/sync_submissions_job.rb +2 -4
- data/lib/canvas_sync/jobs/sync_terms_job.rb +22 -7
- data/lib/canvas_sync/misc_helper.rb +15 -0
- data/lib/canvas_sync/version.rb +1 -1
- data/spec/canvas_sync/canvas_sync_spec.rb +126 -153
- data/spec/canvas_sync/jobs/job_spec.rb +9 -17
- data/spec/canvas_sync/jobs/report_checker_spec.rb +1 -3
- data/spec/canvas_sync/jobs/report_processor_job_spec.rb +0 -3
- data/spec/canvas_sync/jobs/report_starter_spec.rb +19 -28
- data/spec/canvas_sync/jobs/sync_admins_job_spec.rb +1 -4
- data/spec/canvas_sync/jobs/sync_assignment_groups_job_spec.rb +2 -1
- data/spec/canvas_sync/jobs/sync_assignments_job_spec.rb +3 -2
- data/spec/canvas_sync/jobs/sync_context_module_items_job_spec.rb +3 -2
- data/spec/canvas_sync/jobs/sync_context_modules_job_spec.rb +3 -2
- data/spec/canvas_sync/jobs/sync_provisioning_report_job_spec.rb +3 -35
- data/spec/canvas_sync/jobs/sync_roles_job_spec.rb +1 -4
- data/spec/canvas_sync/jobs/sync_simple_table_job_spec.rb +5 -12
- data/spec/canvas_sync/jobs/sync_submissions_job_spec.rb +2 -1
- data/spec/canvas_sync/jobs/sync_terms_job_spec.rb +1 -4
- data/spec/dummy/app/models/account.rb +3 -0
- data/spec/dummy/app/models/pseudonym.rb +14 -0
- data/spec/dummy/app/models/submission.rb +1 -0
- data/spec/dummy/app/models/user.rb +1 -0
- data/spec/dummy/config/environments/test.rb +2 -0
- data/spec/dummy/db/migrate/20201016181346_create_pseudonyms.rb +24 -0
- data/spec/dummy/db/schema.rb +24 -4
- data/spec/job_batching/batch_aware_job_spec.rb +100 -0
- data/spec/job_batching/batch_spec.rb +363 -0
- data/spec/job_batching/callback_spec.rb +38 -0
- data/spec/job_batching/flow_spec.rb +91 -0
- data/spec/job_batching/integration/integration.rb +57 -0
- data/spec/job_batching/integration/nested.rb +88 -0
- data/spec/job_batching/integration/simple.rb +47 -0
- data/spec/job_batching/integration/workflow.rb +134 -0
- data/spec/job_batching/integration_helper.rb +48 -0
- data/spec/job_batching/sidekiq_spec.rb +124 -0
- data/spec/job_batching/status_spec.rb +92 -0
- data/spec/job_batching/support/base_job.rb +14 -0
- data/spec/job_batching/support/sample_callback.rb +2 -0
- data/spec/spec_helper.rb +10 -0
- metadata +90 -8
- data/lib/canvas_sync/job_chain.rb +0 -57
- data/lib/canvas_sync/jobs/fork_gather.rb +0 -59
- data/spec/canvas_sync/jobs/fork_gather_spec.rb +0 -73
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: b4ffe8d3a42b647f8c30f1d831c4a86998bc7c1b
         | 
| 4 | 
            +
              data.tar.gz: 2ba762a479d2df35e22a69b7930d051a2e43bd30
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 6702f6754b217edfe2c7e4ff51c586940e7073993b5e3a79464e49308edd274af9bc8165f38aa919ef3c288ec86f1623065a91f42121f68c4da1389f27a57cb2
         | 
| 7 | 
            +
              data.tar.gz: 71365002bcf14b762b00b73ae805862d8691a2b78ecd0254c0ef7fcf56eea6caeae838652e7ca4a685834eb380a08e782cf78e591b5af2c8950b4d4fd763e704
         | 
    
        data/README.md
    CHANGED
    
    | @@ -91,27 +91,45 @@ This gem also helps with syncing and processing other reports if needed. In orde | |
| 91 91 | 
             
            - Integrate your reports with the `ReportStarter`
         | 
| 92 92 | 
             
            - Tell the gem what jobs to run
         | 
| 93 93 |  | 
| 94 | 
            +
            ### `updated_after`
         | 
| 95 | 
            +
            An `updated_after` param may be passed when triggering a provision or making a chain:
         | 
| 96 | 
            +
            ```ruby
         | 
| 97 | 
            +
            CanvasSync.default_provisioning_report_chain(
         | 
| 98 | 
            +
              %i[list of models to sync], updated_after: false
         | 
| 99 | 
            +
            )
         | 
| 100 | 
            +
            ```
         | 
| 101 | 
            +
            It may be one of the following values:
         | 
| 102 | 
            +
            * `false` - Will not apply any `updated_after` filtering to the requested reports
         | 
| 103 | 
            +
            * An ISO-8601 Date - Will pass the supplied date ad the `updated_after` param for the requested reports
         | 
| 104 | 
            +
            * `true` (Default) - Will use the start date of the last successful sync
         | 
| 105 | 
            +
             | 
| 94 106 | 
             
            ### Extensible chain
         | 
| 95 107 | 
             
            It is sometimes desired to extend or customize the chain of jobs that are run with CanvasSync.
         | 
| 96 108 | 
             
            This can be achieved with the following pattern:
         | 
| 97 109 |  | 
| 98 110 | 
             
            ```ruby
         | 
| 99 | 
            -
             | 
| 111 | 
            +
            chain = CanvasSync.default_provisioning_report_chain(
         | 
| 100 112 | 
             
              %i[list of models to sync]
         | 
| 101 113 | 
             
            )
         | 
| 102 114 |  | 
| 103 | 
            -
            #  | 
| 104 | 
            -
             | 
| 105 | 
            -
             | 
| 106 | 
            -
             | 
| 107 | 
            -
             | 
| 108 | 
            -
            # Jobs placed before SyncProvisioningReportJob or after ForkGather will run once per Sync
         | 
| 109 | 
            -
            job_chain[:jobs] << { job: CanvasSync::Jobs::ForkGather, options: {} }
         | 
| 115 | 
            +
            # Add a custom job to the end of the chain.
         | 
| 116 | 
            +
            chain << { job: CanvasSyncCompleteWorker, parameters: [{ job_id: job.id }] }
         | 
| 117 | 
            +
            chain << { job: CanvasSyncCompleteWorker, options: { job_id: job.id } } # If an options key is provided, it will be automatically appended to the end of the :parameters array
         | 
| 118 | 
            +
             | 
| 119 | 
            +
            chain.process!
         | 
| 110 120 |  | 
| 111 | 
            -
            #  | 
| 112 | 
            -
             | 
| 121 | 
            +
            # The chain object provides a fairly extensive API:
         | 
| 122 | 
            +
            chain.insert({ job: SomeOtherJob }) # Adds the job to the end of the chain
         | 
| 123 | 
            +
            chain.insert_at(0, { job: SomeOtherJob }) # Adds the job to the beginning of the chain
         | 
| 124 | 
            +
            chain.insert({ job: SomeOtherJob }, after: 'CanvasSync::Jobs::SyncTermsJob') # Adds the job right after the SyncTermsJob
         | 
| 125 | 
            +
            chain.insert({ job: SomeOtherJob }, before: 'CanvasSync::Jobs::SyncTermsJob') # Adds the job right before the SyncTermsJob
         | 
| 126 | 
            +
            chain.insert({ job: SomeOtherJob }, with: 'CanvasSync::Jobs::SyncTermsJob') # Adds the job to be performed concurrently with the SyncTermsJob
         | 
| 113 127 |  | 
| 114 | 
            -
             | 
| 128 | 
            +
            # Some Jobs (such as the SyncTermsJob) have a sub-chain for, eg, Courses.
         | 
| 129 | 
            +
            # chain.insert is aware of these sub-chains and will recurse into them when looking for a before:/after:/with: reference
         | 
| 130 | 
            +
            chain.insert({ job: SomeOtherJob }, after: 'CanvasSync::Jobs::SyncCoursesJob') # Adds the job to be performed after SyncCoursesJob (which is a sub-job of the terms job and is duplicated for each term in the term_scope:)
         | 
| 131 | 
            +
            # You can also retrieve the sub-chain like so:
         | 
| 132 | 
            +
            chain.get_sub_chain('CanvasSync::Jobs::SyncTermsJob')
         | 
| 115 133 | 
             
            ```
         | 
| 116 134 |  | 
| 117 135 | 
             
            ### Processor
         | 
| @@ -134,9 +152,8 @@ Let's say we have a custom Canvas report called "my_really_cool_report_csv". Fir | |
| 134 152 |  | 
| 135 153 | 
             
            ```ruby
         | 
| 136 154 | 
             
            class MyReallyCoolReportJob < CanvasSync::Jobs::ReportStarter
         | 
| 137 | 
            -
              def perform( | 
| 155 | 
            +
              def perform(options)
         | 
| 138 156 | 
             
                super(
         | 
| 139 | 
            -
                  job_chain,
         | 
| 140 157 | 
             
                  'my_really_cool_report_csv', # Report name
         | 
| 141 158 | 
             
                  { "parameters[param1]" => true }, # Report parameters
         | 
| 142 159 | 
             
                  MyCoolProcessor.to_s, # Your processor class as a string
         | 
| @@ -148,57 +165,6 @@ end | |
| 148 165 |  | 
| 149 166 | 
             
            You can also see examples in `lib/canvas_sync/jobs/sync_users_job.rb` and `lib/canvas_sync/jobs/sync_provisioning_report.rb`.
         | 
| 150 167 |  | 
| 151 | 
            -
            ### Start the jobs
         | 
| 152 | 
            -
             | 
| 153 | 
            -
            The `CanvasSync.process_jobs` method allows you to pass in a chain of jobs to run. The job chain must be formatted like:
         | 
| 154 | 
            -
             | 
| 155 | 
            -
            ```ruby
         | 
| 156 | 
            -
            {
         | 
| 157 | 
            -
              jobs: [
         | 
| 158 | 
            -
                { job: JobClass, options: {} },
         | 
| 159 | 
            -
                { job: JobClass2, options: {} }
         | 
| 160 | 
            -
              ],
         | 
| 161 | 
            -
              global_options: {}
         | 
| 162 | 
            -
            }
         | 
| 163 | 
            -
            ```
         | 
| 164 | 
            -
             | 
| 165 | 
            -
            Here is an example that runs our new report job first followed by the builtin provisioning job:
         | 
| 166 | 
            -
             | 
| 167 | 
            -
            ```ruby
         | 
| 168 | 
            -
            job_chain = {
         | 
| 169 | 
            -
              jobs: [
         | 
| 170 | 
            -
                { job: MyReallyCoolReportJob, options: {} },
         | 
| 171 | 
            -
                { job: CanvasSync::Jobs::SyncProvisioningReportJob, options: { models: ['users', 'courses'] } }
         | 
| 172 | 
            -
              ],
         | 
| 173 | 
            -
              global_options: {}
         | 
| 174 | 
            -
            }
         | 
| 175 | 
            -
             | 
| 176 | 
            -
            CanvasSync.process_jobs(job_chain)
         | 
| 177 | 
            -
            ```
         | 
| 178 | 
            -
             | 
| 179 | 
            -
            What if you've got some other job that you want run that doesn't deal with a report? No problem! Just make sure you call `CanvasSync.invoke_next` at the end of your job. Example:
         | 
| 180 | 
            -
             | 
| 181 | 
            -
             | 
| 182 | 
            -
            ```ruby
         | 
| 183 | 
            -
            class SomeRandomJob < CanvasSync::Job
         | 
| 184 | 
            -
              def perform(job_chain, options)
         | 
| 185 | 
            -
                i_dunno_do_something!
         | 
| 186 | 
            -
             | 
| 187 | 
            -
                CanvasSync.invoke_next(job_chain)
         | 
| 188 | 
            -
              end
         | 
| 189 | 
            -
            end
         | 
| 190 | 
            -
             | 
| 191 | 
            -
            job_chain = {
         | 
| 192 | 
            -
              jobs: [
         | 
| 193 | 
            -
                { job: SomeRandomJob, options: {} },
         | 
| 194 | 
            -
                { job: CanvasSync::Jobs::SyncProvisioningReportJob, options: { models: ['users', 'courses'] } }
         | 
| 195 | 
            -
              ],
         | 
| 196 | 
            -
              global_options: {}
         | 
| 197 | 
            -
            }
         | 
| 198 | 
            -
             | 
| 199 | 
            -
            CanvasSync.process_jobs(job_chain)
         | 
| 200 | 
            -
            ```
         | 
| 201 | 
            -
             | 
| 202 168 | 
             
            ### Batching
         | 
| 203 169 |  | 
| 204 170 | 
             
            The provisioning report uses the `CanvasSync::Importers::BulkImporter` class to bulk import rows with the activerecord-import gem. It inserts rows in batches of 10,000 by default. This can be customized by setting the `BULK_IMPORTER_BATCH_SIZE` environment variable.
         | 
| @@ -257,6 +223,14 @@ class CanvasSyncModel < ApplicationRecord | |
| 257 223 | 
             
            end
         | 
| 258 224 | 
             
            ```
         | 
| 259 225 |  | 
| 226 | 
            +
            ### Job Batching
         | 
| 227 | 
            +
            CanvasSync adds a `CanvasSync::JobBatches` module. It adds Sidekiq/sidekiq-batch like support for Job Batches.
         | 
| 228 | 
            +
            It integrates automatically with both Sidekiq and ActiveJob. The API is highly similar to the Sidekiq-batch implementation,
         | 
| 229 | 
            +
            documentation for which can be found at https://github.com/mperham/sidekiq/wiki/Batches
         | 
| 230 | 
            +
             | 
| 231 | 
            +
            A batch can be created using `Sidekiq::Batch` or `CanvasSync::JobBatching::Batch`.
         | 
| 232 | 
            +
             | 
| 233 | 
            +
            Also see `canvas_sync/jobs/begin_sync_chain_job`, `canvas_sync/Job_batches/jobs/serial_batch_job`, or `canvas_sync/Job_batches/jobs/concurrent_batch_job` for example usage.
         | 
| 260 234 |  | 
| 261 235 | 
             
            ## Legacy Support
         | 
| 262 236 |  | 
| @@ -308,80 +282,19 @@ end | |
| 308 282 | 
             
            ## Syncronize different reports
         | 
| 309 283 | 
             
            CanvasSync provides the functionality to import data from other reports into an specific table.
         | 
| 310 284 |  | 
| 311 | 
            -
            This can be  | 
| 312 | 
            -
             | 
| 313 | 
            -
            ```ruby
         | 
| 314 | 
            -
            CanvasSync.provisioning_sync(<array of models to sync>, term_scope: <optional term scope>)
         | 
| 315 | 
            -
            CanvasSync
         | 
| 316 | 
            -
              .simple_report_sync(
         | 
| 317 | 
            -
                [
         | 
| 318 | 
            -
                  {
         | 
| 319 | 
            -
                    report_name: <report name>,
         | 
| 320 | 
            -
                    model: <model to sync>,
         | 
| 321 | 
            -
                    params: <hash with the require parameters the report needs to sync>
         | 
| 322 | 
            -
                  },
         | 
| 323 | 
            -
                  {
         | 
| 324 | 
            -
                    report_name: <report name>,
         | 
| 325 | 
            -
                    model: <model to sync>,
         | 
| 326 | 
            -
                    params: <hash with the require parameters the report needs to sync>
         | 
| 327 | 
            -
                  },
         | 
| 328 | 
            -
                  ...
         | 
| 329 | 
            -
                ],
         | 
| 330 | 
            -
                term_scope: <optional term scope>
         | 
| 331 | 
            -
              )
         | 
| 332 | 
            -
            ```
         | 
| 333 | 
            -
             | 
| 334 | 
            -
            Example:
         | 
| 285 | 
            +
            This can be achieved by using the following method
         | 
| 335 286 |  | 
| 336 287 | 
             
            ```ruby
         | 
| 337 | 
            -
            CanvasSync
         | 
| 338 | 
            -
             | 
| 339 | 
            -
             | 
| 340 | 
            -
             | 
| 341 | 
            -
             | 
| 342 | 
            -
             | 
| 343 | 
            -
             | 
| 344 | 
            -
             | 
| 345 | 
            -
             | 
| 346 | 
            -
             | 
| 347 | 
            -
                  },
         | 
| 348 | 
            -
                  {
         | 
| 349 | 
            -
                    report_name: 'proservices_provisioning_csv',
         | 
| 350 | 
            -
                    model: 'accounts',
         | 
| 351 | 
            -
                    params: {
         | 
| 352 | 
            -
                      "parameters[include_deleted]" => true,
         | 
| 353 | 
            -
                      "parameters[accounts]" => true
         | 
| 354 | 
            -
                    }
         | 
| 355 | 
            -
                  }
         | 
| 356 | 
            -
                ]
         | 
| 357 | 
            -
              )
         | 
| 358 | 
            -
            ```
         | 
| 359 | 
            -
             | 
| 360 | 
            -
            Example with the term_scope active:
         | 
| 361 | 
            -
             | 
| 362 | 
            -
            ```ruby
         | 
| 363 | 
            -
            CanvasSync
         | 
| 364 | 
            -
              .simple_report_sync(
         | 
| 365 | 
            -
                [
         | 
| 366 | 
            -
                  {
         | 
| 367 | 
            -
                    report_name: 'proservices_provisioning_csv',
         | 
| 368 | 
            -
                    model: 'sections',
         | 
| 369 | 
            -
                    params: {
         | 
| 370 | 
            -
                      "parameters[include_deleted]" => true,
         | 
| 371 | 
            -
                      "parameters[sections]" => true
         | 
| 372 | 
            -
                    }
         | 
| 373 | 
            -
                  },
         | 
| 374 | 
            -
                  {
         | 
| 375 | 
            -
                    report_name: 'proservices_provisioning_csv',
         | 
| 376 | 
            -
                    model: 'courses',
         | 
| 377 | 
            -
                    params: {
         | 
| 378 | 
            -
                      "parameters[include_deleted]" => true,
         | 
| 379 | 
            -
                      "parameters[courses]" => true
         | 
| 380 | 
            -
                    }
         | 
| 381 | 
            -
                  }
         | 
| 382 | 
            -
                ],
         | 
| 383 | 
            -
                term_scope: 'active'
         | 
| 384 | 
            -
              )
         | 
| 288 | 
            +
            chain = CanvasSync.default_provisioning_report_chain
         | 
| 289 | 
            +
            chain << {
         | 
| 290 | 
            +
              job: CanvasSync::Jobs::SyncSimpleTableJob,
         | 
| 291 | 
            +
              options: {
         | 
| 292 | 
            +
                report_name: <report name>,
         | 
| 293 | 
            +
                model: <model to sync>,
         | 
| 294 | 
            +
                params: <hash with the require parameters the report needs to sync>
         | 
| 295 | 
            +
              },
         | 
| 296 | 
            +
            }
         | 
| 297 | 
            +
            chain.process!
         | 
| 385 298 | 
             
            ```
         | 
| 386 299 |  | 
| 387 300 | 
             
            ## Configuration
         | 
| @@ -421,7 +334,6 @@ class CanvasSyncStarterWorker | |
| 421 334 | 
             
                    }
         | 
| 422 335 | 
             
                  }
         | 
| 423 336 | 
             
                )
         | 
| 424 | 
            -
                CanvasSync.invoke_next(job_chain)
         | 
| 425 337 | 
             
              end
         | 
| 426 338 |  | 
| 427 339 | 
             
              def self.handle_canvas_sync_error(error, **options)
         | 
| @@ -1,35 +1,16 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
               | 
| 3 | 
            -
                 | 
| 4 | 
            -
                   | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
                    t.text :job_arguments
         | 
| 1 | 
            +
            class CreateCanvasSyncJobLog < CanvasSync::MiscHelper::MigrationClass
         | 
| 2 | 
            +
              def change
         | 
| 3 | 
            +
                create_table :canvas_sync_job_logs do |t|
         | 
| 4 | 
            +
                  t.datetime :started_at
         | 
| 5 | 
            +
                  t.datetime :completed_at
         | 
| 6 | 
            +
                  t.string :exception
         | 
| 7 | 
            +
                  t.text :backtrace
         | 
| 8 | 
            +
                  t.string :job_class
         | 
| 9 | 
            +
                  t.string :status
         | 
| 10 | 
            +
                  t.text :metadata
         | 
| 11 | 
            +
                  t.text :job_arguments
         | 
| 13 12 |  | 
| 14 | 
            -
             | 
| 15 | 
            -
                  end
         | 
| 16 | 
            -
                end
         | 
| 17 | 
            -
              end
         | 
| 18 | 
            -
            else
         | 
| 19 | 
            -
              class CreateCanvasSyncJobLog < ActiveRecord::Migration
         | 
| 20 | 
            -
                def change
         | 
| 21 | 
            -
                  create_table :canvas_sync_job_logs do |t|
         | 
| 22 | 
            -
                    t.datetime :started_at
         | 
| 23 | 
            -
                    t.datetime :completed_at
         | 
| 24 | 
            -
                    t.string :exception
         | 
| 25 | 
            -
                    t.text :backtrace
         | 
| 26 | 
            -
                    t.string :job_class
         | 
| 27 | 
            -
                    t.string :status
         | 
| 28 | 
            -
                    t.text :metadata
         | 
| 29 | 
            -
                    t.text :job_arguments
         | 
| 30 | 
            -
             | 
| 31 | 
            -
                    t.timestamps
         | 
| 32 | 
            -
                  end
         | 
| 13 | 
            +
                  t.timestamps
         | 
| 33 14 | 
             
                end
         | 
| 34 15 | 
             
              end
         | 
| 35 16 | 
             
            end
         | 
| @@ -1,15 +1,6 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
               | 
| 3 | 
            -
                 | 
| 4 | 
            -
             | 
| 5 | 
            -
                  add_index :canvas_sync_job_logs, :job_id
         | 
| 6 | 
            -
                end
         | 
| 7 | 
            -
              end
         | 
| 8 | 
            -
            else
         | 
| 9 | 
            -
              class AddJobIdToCanvasSyncJobLogs < ActiveRecord::Migration
         | 
| 10 | 
            -
                def change
         | 
| 11 | 
            -
                  add_column :canvas_sync_job_logs, :job_id, :string
         | 
| 12 | 
            -
                  add_index :canvas_sync_job_logs, :job_id
         | 
| 13 | 
            -
                end
         | 
| 1 | 
            +
            class AddJobIdToCanvasSyncJobLogs < CanvasSync::MiscHelper::MigrationClass
         | 
| 2 | 
            +
              def change
         | 
| 3 | 
            +
                add_column :canvas_sync_job_logs, :job_id, :string
         | 
| 4 | 
            +
                add_index :canvas_sync_job_logs, :job_id
         | 
| 14 5 | 
             
              end
         | 
| 15 6 | 
             
            end
         | 
| @@ -1,13 +1,5 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
               | 
| 3 | 
            -
                 | 
| 4 | 
            -
                  add_column :canvas_sync_job_logs, :fork_count, :integer
         | 
| 5 | 
            -
                end
         | 
| 6 | 
            -
              end
         | 
| 7 | 
            -
            else
         | 
| 8 | 
            -
              class AddForkCountToCanvasSyncJobLogs < ActiveRecord::Migration
         | 
| 9 | 
            -
                def change
         | 
| 10 | 
            -
                  add_column :canvas_sync_job_logs, :fork_count, :integer
         | 
| 11 | 
            -
                end
         | 
| 1 | 
            +
            class AddForkCountToCanvasSyncJobLogs < CanvasSync::MiscHelper::MigrationClass
         | 
| 2 | 
            +
              def change
         | 
| 3 | 
            +
                add_column :canvas_sync_job_logs, :fork_count, :integer
         | 
| 12 4 | 
             
              end
         | 
| 13 5 | 
             
            end
         | 
    
        data/lib/canvas_sync.rb
    CHANGED
    
    | @@ -2,9 +2,9 @@ require "bearcat" | |
| 2 2 |  | 
| 3 3 | 
             
            require "canvas_sync/version"
         | 
| 4 4 | 
             
            require "canvas_sync/engine"
         | 
| 5 | 
            +
            require "canvas_sync/misc_helper"
         | 
| 5 6 | 
             
            require "canvas_sync/class_callback_executor"
         | 
| 6 7 | 
             
            require "canvas_sync/job"
         | 
| 7 | 
            -
            require "canvas_sync/job_chain"
         | 
| 8 8 | 
             
            require "canvas_sync/sidekiq_job"
         | 
| 9 9 | 
             
            require "canvas_sync/api_syncable"
         | 
| 10 10 | 
             
            require "canvas_sync/record"
         | 
| @@ -13,6 +13,8 @@ require "canvas_sync/jobs/report_checker" | |
| 13 13 | 
             
            require "canvas_sync/jobs/report_processor_job"
         | 
| 14 14 | 
             
            require "canvas_sync/config"
         | 
| 15 15 |  | 
| 16 | 
            +
            require "canvas_sync/job_batches/batch"
         | 
| 17 | 
            +
             | 
| 16 18 | 
             
            Dir[File.dirname(__FILE__) +  "/canvas_sync/jobs/*.rb"].each { |file| require file }
         | 
| 17 19 | 
             
            Dir[File.dirname(__FILE__) +  "/canvas_sync/processors/*.rb"].each { |file| require file }
         | 
| 18 20 | 
             
            Dir[File.dirname(__FILE__) +  "/canvas_sync/importers/*.rb"].each { |file| require file }
         | 
| @@ -57,6 +59,9 @@ module CanvasSync | |
| 57 59 | 
             
                graded_submissions
         | 
| 58 60 | 
             
              ].freeze
         | 
| 59 61 |  | 
| 62 | 
            +
              JobBatches::ChainBuilder.register_chain_job(CanvasSync::Jobs::SyncTermsJob, :sub_jobs)
         | 
| 63 | 
            +
              JobBatches::ChainBuilder.register_chain_job(CanvasSync::Jobs::BeginSyncChainJob, 0)
         | 
| 64 | 
            +
             | 
| 60 65 | 
             
              class << self
         | 
| 61 66 | 
             
                # Runs a standard provisioning sync job with no extra report types.
         | 
| 62 67 | 
             
                # Terms will be synced first using the API. If you are syncing users/roles/admins
         | 
| @@ -72,72 +77,9 @@ module CanvasSync | |
| 72 77 | 
             
                #   and inserts it into the database. If an array of model names is provided then only those models will use legacy support.
         | 
| 73 78 | 
             
                # @param account_id [Integer, nil] This optional parameter can be used if your Term creation and
         | 
| 74 79 | 
             
                #   canvas_sync_client methods require an account ID.
         | 
| 75 | 
            -
                def provisioning_sync(models,  | 
| 80 | 
            +
                def provisioning_sync(models, **kwargs)
         | 
| 76 81 | 
             
                  validate_models!(models)
         | 
| 77 | 
            -
                   | 
| 78 | 
            -
                end
         | 
| 79 | 
            -
             | 
| 80 | 
            -
                # Runs a report different from provisioning sync job with no extra report types.
         | 
| 81 | 
            -
                #
         | 
| 82 | 
            -
                # @param reports_mapping [Array<Hash>] An Array of hash that list the model and params with their report you
         | 
| 83 | 
            -
                #   want to import:
         | 
| 84 | 
            -
                #   [{model: 'submissions', report_name: 'my_report_name_csv', params: { "parameters[include_deleted]" => true } }, ...]
         | 
| 85 | 
            -
                # @param term_scope [Symbol, nil] An optional symbol representing a scope that exists on the Term model.
         | 
| 86 | 
            -
                #   The provisioning report will be run for each of the terms contained in that scope.
         | 
| 87 | 
            -
                # @param account_id [Integer, nil] This optional parameter can be used if your Term creation and
         | 
| 88 | 
            -
                #   canvas_sync_client methods require an account ID.
         | 
| 89 | 
            -
                def simple_report_sync(reports_mapping, term_scope: nil, account_id: nil)
         | 
| 90 | 
            -
                  invoke_next(simple_report_chain(reports_mapping, term_scope, account_id))
         | 
| 91 | 
            -
                end
         | 
| 92 | 
            -
             | 
| 93 | 
            -
                # Runs a chain of ordered jobs
         | 
| 94 | 
            -
                #
         | 
| 95 | 
            -
                # See the README for usage and examples
         | 
| 96 | 
            -
                #
         | 
| 97 | 
            -
                # @param job_chain [Hash]
         | 
| 98 | 
            -
                def process_jobs(job_chain)
         | 
| 99 | 
            -
                  invoke_next(job_chain)
         | 
| 100 | 
            -
                end
         | 
| 101 | 
            -
             | 
| 102 | 
            -
                def duplicate_chain(job_chain)
         | 
| 103 | 
            -
                  Marshal.load(Marshal.dump(job_chain))
         | 
| 104 | 
            -
                end
         | 
| 105 | 
            -
             | 
| 106 | 
            -
                # Invokes the next job in a chain of jobs.
         | 
| 107 | 
            -
                #
         | 
| 108 | 
            -
                # This should typically be called automatically by the gem where necessary.
         | 
| 109 | 
            -
                #
         | 
| 110 | 
            -
                # @param job_chain [Hash] A chain of jobs to execute
         | 
| 111 | 
            -
                def invoke_next(job_chain, extra_options: {})
         | 
| 112 | 
            -
                  job_chain = job_chain.chain_data if job_chain.is_a?(JobChain)
         | 
| 113 | 
            -
             | 
| 114 | 
            -
                  return if job_chain[:jobs].empty?
         | 
| 115 | 
            -
             | 
| 116 | 
            -
                  # Make sure all job classes are serialized as strings
         | 
| 117 | 
            -
                  job_chain[:jobs].each { |job| job[:job] = job[:job].to_s }
         | 
| 118 | 
            -
             | 
| 119 | 
            -
                  duped_job_chain = Marshal.load(Marshal.dump(job_chain))
         | 
| 120 | 
            -
                  jobs = duped_job_chain[:jobs]
         | 
| 121 | 
            -
                  next_job = jobs.shift
         | 
| 122 | 
            -
                  next_job_class = next_job[:job].constantize
         | 
| 123 | 
            -
                  next_options = next_job[:options] || {}
         | 
| 124 | 
            -
                  next_options.merge!(extra_options)
         | 
| 125 | 
            -
                  next_job_class.perform_later(duped_job_chain, next_options)
         | 
| 126 | 
            -
                end
         | 
| 127 | 
            -
             | 
| 128 | 
            -
                def fork(job_log, job_chain, keys: [])
         | 
| 129 | 
            -
                  job_chain = job_chain.chain_data if job_chain.is_a?(JobChain)
         | 
| 130 | 
            -
             | 
| 131 | 
            -
                  duped_job_chain = Marshal.load(Marshal.dump(job_chain))
         | 
| 132 | 
            -
                  duped_job_chain[:global_options][:fork_path] ||= []
         | 
| 133 | 
            -
                  duped_job_chain[:global_options][:fork_keys] ||= []
         | 
| 134 | 
            -
                  duped_job_chain[:global_options][:fork_path] << job_log.job_id
         | 
| 135 | 
            -
                  duped_job_chain[:global_options][:fork_keys] << keys.map(&:to_s)
         | 
| 136 | 
            -
                  duped_job_chain[:global_options][:on_failure] ||= 'CanvasSync::Jobs::ForkGather.handle_branch_error'
         | 
| 137 | 
            -
                  sub_items = yield duped_job_chain
         | 
| 138 | 
            -
                  sub_count = sub_items.respond_to?(:count) ? sub_items.count : sub_items
         | 
| 139 | 
            -
                  job_log.fork_count = sub_count
         | 
| 140 | 
            -
                  sub_items
         | 
| 82 | 
            +
                  default_provisioning_report_chain(models, **kwargs).process!
         | 
| 141 83 | 
             
                end
         | 
| 142 84 |  | 
| 143 85 | 
             
                # Given a Model or Relation, scope it down to items that should be synced
         | 
| @@ -154,33 +96,6 @@ module CanvasSync | |
| 154 96 | 
             
                  scope
         | 
| 155 97 | 
             
                end
         | 
| 156 98 |  | 
| 157 | 
            -
                # Syn any report to an specific set of models
         | 
| 158 | 
            -
                #
         | 
| 159 | 
            -
                # @param reports_mapping [Array<Hash>] List of reports with their specific model and params
         | 
| 160 | 
            -
                # @param term_scope [String]
         | 
| 161 | 
            -
                # @param account_id [Integer, nil] This optional parameter can be used if your Term creation and
         | 
| 162 | 
            -
                #   canvas_sync_client methods require an account ID.
         | 
| 163 | 
            -
                def simple_report_chain(reports_mapping, term_scope=nil, account_id=nil)
         | 
| 164 | 
            -
                  jobs = reports_mapping.map do |report|
         | 
| 165 | 
            -
                    {
         | 
| 166 | 
            -
                      job: CanvasSync::Jobs::SyncSimpleTableJob.to_s,
         | 
| 167 | 
            -
                      options: {
         | 
| 168 | 
            -
                        report_name: report[:report_name],
         | 
| 169 | 
            -
                        model: report[:model],
         | 
| 170 | 
            -
                        mapping: report[:model],
         | 
| 171 | 
            -
                        klass: report[:model].singularize.capitalize.to_s,
         | 
| 172 | 
            -
                        term_scope: term_scope,
         | 
| 173 | 
            -
                        params: report[:params]
         | 
| 174 | 
            -
                      }
         | 
| 175 | 
            -
                    }
         | 
| 176 | 
            -
                  end
         | 
| 177 | 
            -
             | 
| 178 | 
            -
                  global_options = {}
         | 
| 179 | 
            -
                  global_options[:account_id] = account_id if account_id.present?
         | 
| 180 | 
            -
             | 
| 181 | 
            -
                  JobChain.new(jobs: jobs, global_options: global_options)
         | 
| 182 | 
            -
                end
         | 
| 183 | 
            -
             | 
| 184 99 | 
             
                # Syncs terms, users/roles/admins if necessary, then the rest of the specified models.
         | 
| 185 100 | 
             
                #
         | 
| 186 101 | 
             
                # @param models [Array<String>]
         | 
| @@ -191,7 +106,7 @@ module CanvasSync | |
| 191 106 | 
             
                # @param account_id [Integer, nil] This optional parameter can be used if your Term creation and
         | 
| 192 107 | 
             
                #   canvas_sync_client methods require an account ID.
         | 
| 193 108 | 
             
                # @return [Hash]
         | 
| 194 | 
            -
                def default_provisioning_report_chain(models, term_scope | 
| 109 | 
            +
                def default_provisioning_report_chain(models, term_scope: nil, legacy_support: false, account_id: nil, updated_after: nil, options: {}) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/LineLength
         | 
| 195 110 | 
             
                  return unless models.present?
         | 
| 196 111 | 
             
                  models.map! &:to_s
         | 
| 197 112 | 
             
                  term_scope = term_scope.to_s if term_scope
         | 
| @@ -210,64 +125,66 @@ module CanvasSync | |
| 210 125 | 
             
                    context_module_items: CanvasSync::Jobs::SyncContextModuleItemsJob,
         | 
| 211 126 | 
             
                  }.with_indifferent_access
         | 
| 212 127 |  | 
| 213 | 
            -
                   | 
| 128 | 
            +
                  root_chain = JobBatches::ChainBuilder.new(CanvasSync::Jobs::BeginSyncChainJob)
         | 
| 129 | 
            +
                  concurrent_root_chain = JobBatches::ChainBuilder.new(JobBatches::ConcurrentBatchJob)
         | 
| 130 | 
            +
                  root_chain << concurrent_root_chain
         | 
| 131 | 
            +
                  current_chain = concurrent_root_chain
         | 
| 132 | 
            +
             | 
| 214 133 | 
             
                  try_add_model_job = ->(model) {
         | 
| 215 134 | 
             
                    return unless models.include?(model)
         | 
| 216 | 
            -
                     | 
| 135 | 
            +
                    current_chain << { job: model_job_map[model].to_s, options: options[model.to_sym] || {} }
         | 
| 217 136 | 
             
                    models -= [model]
         | 
| 218 137 | 
             
                  }
         | 
| 219 138 |  | 
| 220 139 | 
             
                  ##############################
         | 
| 221 | 
            -
                  #  | 
| 140 | 
            +
                  # General provisioning jobs (not term-scoped)
         | 
| 222 141 | 
             
                  ##############################
         | 
| 223 142 |  | 
| 224 | 
            -
                  #  | 
| 225 | 
            -
                  models | 
| 226 | 
            -
                  try_add_model_job.call('terms')
         | 
| 143 | 
            +
                  # Terms are always synced regardless of the models option
         | 
| 144 | 
            +
                  models -= ['terms']
         | 
| 227 145 |  | 
| 228 | 
            -
                  # Accounts, users, roles, and admins  | 
| 146 | 
            +
                  # Accounts, users, roles, and admins cannot be scoped to term
         | 
| 229 147 | 
             
                  try_add_model_job.call('accounts')
         | 
| 230 148 |  | 
| 231 149 | 
             
                  # These Models use the provisioning report, but are not term-scoped,
         | 
| 232 | 
            -
                  # so we sync them  | 
| 150 | 
            +
                  # so we sync them outside of the term scoping to ensure work is not duplicated
         | 
| 233 151 | 
             
                  if term_scope.present?
         | 
| 234 152 | 
             
                    models -= (first_provisioning_models = models & ['users', 'pseudonyms'])
         | 
| 235 | 
            -
                     | 
| 236 | 
            -
                      generate_provisioning_jobs(first_provisioning_models, options)
         | 
| 237 | 
            -
                    )
         | 
| 153 | 
            +
                    current_chain.insert(generate_provisioning_jobs(first_provisioning_models, options))
         | 
| 238 154 | 
             
                  end
         | 
| 239 155 |  | 
| 240 156 | 
             
                  try_add_model_job.call('roles')
         | 
| 241 157 | 
             
                  try_add_model_job.call('admins')
         | 
| 242 | 
            -
                  pre_provisioning_jobs = jobs
         | 
| 243 158 |  | 
| 244 159 | 
             
                  ###############################
         | 
| 245 | 
            -
                  #  | 
| 160 | 
            +
                  # Per-term provisioning jobs
         | 
| 246 161 | 
             
                  ###############################
         | 
| 247 162 |  | 
| 248 | 
            -
                   | 
| 163 | 
            +
                  per_term_chain = JobBatches::ChainBuilder.new(model_job_map[:terms])
         | 
| 164 | 
            +
                  current_chain << per_term_chain
         | 
| 165 | 
            +
                  current_chain = per_term_chain
         | 
| 166 | 
            +
             | 
| 249 167 | 
             
                  try_add_model_job.call('assignments')
         | 
| 250 168 | 
             
                  try_add_model_job.call('submissions')
         | 
| 251 169 | 
             
                  try_add_model_job.call('assignment_groups')
         | 
| 252 170 | 
             
                  try_add_model_job.call('context_modules')
         | 
| 253 171 | 
             
                  try_add_model_job.call('context_module_items')
         | 
| 254 | 
            -
             | 
| 172 | 
            +
             | 
| 173 | 
            +
                  current_chain.insert(
         | 
| 174 | 
            +
                    generate_provisioning_jobs(models, options, job_options: { term_scope: term_scope }, only_split: ['users'])
         | 
| 175 | 
            +
                  )
         | 
| 255 176 |  | 
| 256 177 | 
             
                  ###############################
         | 
| 257 | 
            -
                  #  | 
| 178 | 
            +
                  # Wrap it all up
         | 
| 258 179 | 
             
                  ###############################
         | 
| 259 180 |  | 
| 260 | 
            -
                   | 
| 261 | 
            -
                    *pre_provisioning_jobs,
         | 
| 262 | 
            -
                    *generate_provisioning_jobs(models, options, job_options: { term_scope: term_scope }, only_split: ['users']),
         | 
| 263 | 
            -
                    *post_provisioning_jobs,
         | 
| 264 | 
            -
                  ]
         | 
| 265 | 
            -
             | 
| 266 | 
            -
                  global_options = { legacy_support: legacy_support }
         | 
| 181 | 
            +
                  global_options = { legacy_support: legacy_support, updated_after: updated_after }
         | 
| 267 182 | 
             
                  global_options[:account_id] = account_id if account_id.present?
         | 
| 268 183 | 
             
                  global_options.merge!(options[:global]) if options[:global].present?
         | 
| 269 184 |  | 
| 270 | 
            -
                   | 
| 185 | 
            +
                  root_chain.params[1] = global_options
         | 
| 186 | 
            +
             | 
| 187 | 
            +
                  root_chain
         | 
| 271 188 | 
             
                end
         | 
| 272 189 |  | 
| 273 190 | 
             
                def group_by_job_options(model_list, options_hash, only_split: nil, default_key: :provisioning)
         |