# apostrophe-jobs

# Inherits from: apostrophe-module

The apostrophe-jobs module runs long-running jobs in response to user actions. Batch operations on pieces are a good example.

The apostrophe-jobs module makes it simple to implement progress display, avoid server timeouts during long operations, and implement a "stop" button.

See the run method for the easiest way to use this module. The run method allows you to implement routes that are designed to be paired with the progress method of this module on the browser side. All you need to do is make sure the ids are posted to your route and then write a function that can carry out the operation on one id.

If you just want to add a new batch operation for pieces, see the examples already in apostrophe-pieces and those added by the apostrophe-workflow module. You don't need to go directly to this module for that case.

If your operation doesn't break down neatly into repeated operations on single documents, look into calling the start method and friends directly from your code.

The apostrophe-jobs module DOES NOT have anything to do with cron jobs or command line tasks.

# Methods

# addRoutes() [routes]

# run(req, change, options) [api]

Starts and supervises a long-running job such as a batch operation on pieces. Call it to implement an API route that runs a job involving carrying out the same action repetitively on many things. If your job doesn't look like that, check out runNonBatch instead.

The ids to be processed should be provided via req.body.ids.

Pass req and a change function that accepts (req, id, callback), performs the modification on that one id and invokes its callback with an error if any (if passed, this is recorded as a bad item in the job, it does not stop the job) and an optional result object.

If req.body.job is truthy, sends an immediate JSON response to req.res with { status: 'ok', jobId: 'cxxxx' }.

The jobId can then be passed to apos.modules['apostrophe-jobs'].progress(jobId) on the browser side to monitor progress. After the job status is completed, the results of the job can be obtained via the progress API as the results property, an object with a sub-property for each id.

Jobs run in this way support the stop operation. They do not currently support the cancel (undo/rollback) operation.

Note that this method does not actually care if id is a doc id or not. It is just a unique identifier for one item to be processed that your change function must understand.

The batchSimple method of apostrophe-pieces is an even simpler wrapper for this method if you are implementing a batch operation on a single type of piece.

Options

options.labels should be passed as an object with a title property, to title the progress modal. A default is provided but it is not very informative.

In addition, it may have failed, completed and running properties to label the progress modal when the job is in those states, and good and bad properties to label the count of items that were successful or had errors. All of these properties are optional and reasonable defaults are supplied.

Backwards compatibility

For bc a single id can be provided via req.body._id.

If req.body.job is not truthy, the entire job is processed and a single HTTP response is sent, like:

{ status: 'ok', data: firstResult } on success.

firstResult is an empty object if ids was passed rather than id.

This alternative approach is for bc only. Proxy timeouts are bad. Don't use it.

# runNonBatch(req, doTheWork, options) [api]

Similar to run, this method Starts and supervises a long-running job, however unlike run the doTheWork callback function provided is invoked just once, and when it completes the job is over. This is not the way to implement a batch operation on pieces; see the batchSimple method of that module.

The doTheWork function receives (req, reporting, callback) and may optionally invoke reporting.good() and reporting.bad() to update the progress and error counters, and reporting.setTotal() to indicate the total number of counts expected so a progress meter can be rendered. This is optional and an indication of progress is still displayed without it. reporting.setResults(object) may also be called to pass a results object, which is made available to the optional success callback of the job's modal on the browser side. doTheWork may optionally return a promise rather than invoking the callback.

This method will send { status: 'ok', jobId: 'cxxxx' } to the browser for you. There is no callback because there is nothing more for you to do in your route.

The jobId can then be passed to apos.modules['apostrophe-jobs'].progress(jobId) on the browser side to monitor progress. After the job status is completed, the results of the job can be obtained via the progress API as the results property, an object with a sub-property for each id.

Options

options.labels should be passed as an object with a title property, to title the progress modal. A default is provided but it is not very informative.

In addition, it may have failed, completed and running properties to label the progress modal when the job is in those states, and good and bad properties to label the count of items that were successful or had errors. All of these properties are optional and reasonable defaults are supplied.

You may set options.canStop or options.canCancel to true. If you do, a "stop" or "cancel" button is presented to the user. Your code may then invoke reporting.isCanceling() when convenient and, if it returns a function, must cease the operation and then invoke that function instead of its callback. If the operation has completed in the meantime your code must take care not to invoke it afterwards.

If canCancel was used, your code must also undo all effects of the entire job before invoking the function.

# start(options, callback) [api]

Start tracking a long-running job. Called by routes that require progress display and/or the ability to take longer than the server might permit a single HTTP request to last. You usually won't call this yourself. The easy way is usually to call the run method, above.

On success this method invokes its callback with (null, job). You can then invoke the setTotal, success, error, and end methods with the job object. For convenience, only start and end require a callback.

Once you successfully call start, you must eventually call end with a job object and a callback. In addition, you may call success(job) and error(job) any number of times to indicate the success or failure of one "row" or other item processed, and you may call setTotal(job, n) to indicate how many rows to expect for better progress display.

Canceling and stopping jobs

If options.cancel is passed, the user may cancel (undo) the job. If they do options.cancel will be invoked with (job, callback) and it must undo what was done. You must invoke the callback after the undo operation. If you pass an error to the callback, the job will be stopped with no further progress in the undo operation.

If options.stop is passed, the user may stop (halt) the operation. If they do options.stop will be invoked with (job, callback) and it must stop processing new items. You must invoke the callback after all operations have stopped.

The difference between stop and cancel is the lack of undo with "stop". Implement the one that is practical for you. Users like to be able to undo things fully, of course.

You should not offer both. If you do, only "Cancel" is presented to the user.

Labeling the progress modal: options.labels

options.labels should be passed as an object with a title property, to title the progress modal.

In addition, it may have failed, completed and running properties to label the progress modal when the job is in those states, and good and bad properties to label the count of items that were successful or had errors. All of these properties are optional and reasonable defaults are supplied.

# good(job, n) [api]

Call this to report that n items were good (successfully processed).

If the second argument is completely omitted, the default is 1.

For simplicity there is no callback, since the reporting is noncritical. Just call it and move on.

# bad(job, n) [api]

Call this to report that n items were bad (not successfully processed).

If the second argument is completely omitted, the default is 1.

For simplicity there is no callback, since the reporting is noncritical. Just call it and move on.

# setTotal(job, total) [api]

Call this to indicate the total number of items expected. Until and unless this is called a different type of progress display is used. If you do call this, the total of all calls to success() and error() should not exceed it.

It is OK not to call this, however the progress display will be less informative.

For simplicity there is no callback, since the reporting is noncritical. Just call it and move on.

# end(job, success, results, callback) [api]

Mark the given job as ended. If success is true the job is reported as an overall success, if failed is true the job is reported as an overall failure. Either way the user can still see the count of "good" and "bad" items.

If the results parameter is not omitted entirely, it is added to the job object in the database as the results property.

# ensureCollection(callback) [implementation]

# checkStop(context) [implementation]

Periodically invoked to check whether a request to cancel or stop the job has been made. If it has we invoke options.cancel or options.stop to actually cancel it, preferably the former. This method is invoked by an interval timer installed by self.start. This allows a possibly different apostrophe process to request a cancellation by setting the canceling property; the original process actually running the job cancels and then acknowledges this by setting status to canceled or stopped according to which operation is actually supported by the job.

# pushAssets() [browser]