# 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.