Skip to content

Commit

Permalink
Add top level disable and enable (#1109)
Browse files Browse the repository at this point in the history
* Add top-level `agenda.disable` and `agenda.enable` methods

* Similar to the mapping between `job.remove` and `agenda.cancel`, these methods alter the `disabled` property for all jobs matching the supplied query
* Tests covering the new logic have been added to `/test/agenda.js`

* Revert local changes

* Cleanup test logging

* Add documentation for the new `agenda.disable()` and `agenda.enable()` methods

* Change sample job title

* Fix linting errors

* Port the global enable and disable functions over to typescript
  • Loading branch information
pdfowler committed Aug 4, 2021
1 parent bd19693 commit 8daa43b
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 0 deletions.
20 changes: 20 additions & 0 deletions README.md
Expand Up @@ -546,6 +546,26 @@ const numRemoved = await agenda.cancel({ name: "printAnalyticsReport" });

This functionality can also be achieved by first retrieving all the jobs from the database using `agenda.jobs()`, looping through the resulting array and calling `job.remove()` on each. It is however preferable to use `agenda.cancel()` for this use case, as this ensures the operation is atomic.

### disable(mongodb-native query)

Disables any jobs matching the passed mongodb-native query, preventing any matching jobs from being run by the Job Processor.

```js
const numDisabled = await agenda.disable({name: 'pollExternalService'});
```

Similar to `agenda.cancel()`, this functionality can be acheived with a combination of `agenda.jobs()` and `job.disable()`

### enable(mongodb-native query)

Enables any jobs matching the passed mongodb-native query, allowing any matching jobs to be run by the Job Processor.

```js
const numEnabled = await agenda.enable({name: 'pollExternalService'});
```

Similar to `agenda.cancel()`, this functionality can be acheived with a combination of `agenda.jobs()` and `job.enable()`

### purge()

Removes all jobs in the database without defined behaviors. Useful if you change a definition name and want to remove old jobs. Returns a Promise resolving to the number of removed jobs, or rejecting on error.
Expand Down
28 changes: 28 additions & 0 deletions lib/agenda/disable.ts
@@ -0,0 +1,28 @@
import createDebugger from "debug";
import { FilterQuery } from "mongodb";
import { Agenda } from ".";
const debug = createDebugger("agenda:disable");

/**
* Disables any jobs matching the passed MongoDB query by setting the `disabled` flag to `true`
* @name Agenda#disable
* @function
* @param query MongoDB query to use when enabling
* @returns {Promise<number>} Resolved with the number of disabled job instances.
*/
export const disable = async function (
this: Agenda,
query: FilterQuery<unknown> = {}
): Promise<number> {
debug("attempting to disable all jobs matching query", query);
try {
const { result } = await this._collection.updateMany(query, {
$set: { disabled: true },
});
debug("%s jobs disabled", result.n);
return result.n;
} catch (error) {
debug("error trying to mark jobs as `disabled`");
throw error;
}
};
29 changes: 29 additions & 0 deletions lib/agenda/enable.ts
@@ -0,0 +1,29 @@
import createDebugger from "debug";
import { FilterQuery } from "mongodb";
import { Agenda } from ".";
const debug = createDebugger("agenda:enable");

/**
* Enables any jobs matching the passed MongoDB query by setting the `disabled` flag to `false`
* @name Agenda#enable
* @function
* @param query MongoDB query to use when enabling
* @caller client code, Agenda.purge(), Job.remove()
* @returns {Promise<Number>} A promise that contains the number of removed documents when fulfilled.
*/
export const enable = async function (
this: Agenda,
query: FilterQuery<unknown> = {}
): Promise<number> {
debug("attempting to enable all jobs matching query", query);
try {
const { result } = await this._collection.updateMany(query, {
$set: { disabled: false },
});
debug("%s jobs enabled", result.n);
return result.n;
} catch (error) {
debug("error trying to mark jobs as `enabled`");
throw error;
}
};
6 changes: 6 additions & 0 deletions lib/agenda/index.ts
Expand Up @@ -16,6 +16,8 @@ import { defaultConcurrency } from "./default-concurrency";
import { defaultLockLifetime } from "./default-lock-lifetime";
import { defaultLockLimit } from "./default-lock-limit";
import { define } from "./define";
import { disable } from "./disable";
import { enable } from "./enable";
import { every } from "./every";
import { jobs } from "./jobs";
import { lockLimit } from "./lock-limit";
Expand Down Expand Up @@ -106,6 +108,8 @@ class Agenda extends EventEmitter {
defaultLockLifetime!: typeof defaultLockLifetime;
defaultLockLimit!: typeof defaultLockLimit;
define!: typeof define;
disable!: typeof disable;
enable!: typeof enable;
every!: typeof every;
jobs!: typeof jobs;
lockLimit!: typeof lockLimit;
Expand Down Expand Up @@ -194,6 +198,8 @@ Agenda.prototype.defaultConcurrency = defaultConcurrency;
Agenda.prototype.defaultLockLifetime = defaultLockLifetime;
Agenda.prototype.defaultLockLimit = defaultLockLimit;
Agenda.prototype.define = define;
Agenda.prototype.disable = disable;
Agenda.prototype.enable = enable;
Agenda.prototype.every = every;
Agenda.prototype.jobs = jobs;
Agenda.prototype.lockLimit = lockLimit;
Expand Down
98 changes: 98 additions & 0 deletions test/agenda.js
Expand Up @@ -679,6 +679,104 @@ describe("Agenda", () => {
});
});

describe('disable', () => {
beforeEach(async() => {
await Promise.all([
jobs.create('sendEmail', {to: 'some guy'}).schedule('1 minute').save(),
jobs.create('sendEmail', {from: 'some guy'}).schedule('1 minute').save(),
jobs.create('some job').schedule('30 seconds').save()
]);
});

it('disables all jobs', async() => {
const ct = await jobs.disable({});

expect(ct).to.be(3);
const disabledJobs = await jobs.jobs({});

expect(disabledJobs).to.have.length(3);
disabledJobs.map(x => expect(x.attrs.disabled).to.be(true));
});

it('disables jobs when queried by name', async() => {
const ct = await jobs.disable({name: 'sendEmail'});

expect(ct).to.be(2);
const disabledJobs = await jobs.jobs({name: 'sendEmail'});

expect(disabledJobs).to.have.length(2);
disabledJobs.map(x => expect(x.attrs.disabled).to.be(true));
});

it('disables jobs when queried by data', async() => {
const ct = await jobs.disable({'data.from': 'some guy'});

expect(ct).to.be(1);
const disabledJobs = await jobs.jobs({'data.from': 'some guy', disabled: true});

expect(disabledJobs).to.have.length(1);
});

it('does not modify `nextRunAt`', async() => {
const js = await jobs.jobs({name: 'some job'});
const ct = await jobs.disable({name: 'some job'});

expect(ct).to.be(1);
const disabledJobs = await jobs.jobs({name: 'some job', disabled: true});

expect(disabledJobs[0].attrs.nextRunAt.toString()).to.be(js[0].attrs.nextRunAt.toString());
});
});

describe('enable', () => {
beforeEach(async() => {
await Promise.all([
jobs.create('sendEmail', {to: 'some guy'}).schedule('1 minute').save(),
jobs.create('sendEmail', {from: 'some guy'}).schedule('1 minute').save(),
jobs.create('some job').schedule('30 seconds').save()
]);
});

it('enables all jobs', async() => {
const ct = await jobs.enable({});

expect(ct).to.be(3);
const enabledJobs = await jobs.jobs({});

expect(enabledJobs).to.have.length(3);
enabledJobs.map(x => expect(x.attrs.disabled).to.be(false));
});

it('enables jobs when queried by name', async() => {
const ct = await jobs.enable({name: 'sendEmail'});

expect(ct).to.be(2);
const enabledJobs = await jobs.jobs({name: 'sendEmail'});

expect(enabledJobs).to.have.length(2);
enabledJobs.map(x => expect(x.attrs.disabled).to.be(false));
});

it('enables jobs when queried by data', async() => {
const ct = await jobs.enable({'data.from': 'some guy'});

expect(ct).to.be(1);
const enabledJobs = await jobs.jobs({'data.from': 'some guy', disabled: false});

expect(enabledJobs).to.have.length(1);
});

it('does not modify `nextRunAt`', async() => {
const js = await jobs.jobs({name: 'some job'});
const ct = await jobs.enable({name: 'some job'});

expect(ct).to.be(1);
const enabledJobs = await jobs.jobs({name: 'some job', disabled: false});

expect(enabledJobs[0].attrs.nextRunAt.toString()).to.be(js[0].attrs.nextRunAt.toString());
});
});

describe("search", () => {
beforeEach(async () => {
await jobs.create("jobA", 1).save();
Expand Down

0 comments on commit 8daa43b

Please sign in to comment.