fix: delete scheduled publish jobs when deleting documents (#10584)

### What?

When a document gets deleted we are not cleaning up jobs that would fail
if the document doesn't exist. This change makes an extra call to the DB
to delete any incomplete jobs for the document.

### Why?

The jobs queue will error and retry needlessly unless these are purged.

### How?

Adds a call to delete jobs from the delete operation.
This commit is contained in:
Dan Ribbens
2025-01-15 11:22:21 -05:00
committed by GitHub
parent d4039f2f9e
commit 05b9d94cd2
5 changed files with 178 additions and 3 deletions

View File

@@ -23,6 +23,7 @@ import { commitTransaction } from '../../utilities/commitTransaction.js'
import { initTransaction } from '../../utilities/initTransaction.js' import { initTransaction } from '../../utilities/initTransaction.js'
import { killTransaction } from '../../utilities/killTransaction.js' import { killTransaction } from '../../utilities/killTransaction.js'
import { deleteCollectionVersions } from '../../versions/deleteCollectionVersions.js' import { deleteCollectionVersions } from '../../versions/deleteCollectionVersions.js'
import { deleteScheduledPublishJobs } from '../../versions/deleteScheduledPublishJobs.js'
import { buildAfterOperation } from './utils.js' import { buildAfterOperation } from './utils.js'
export type Arguments = { export type Arguments = {
@@ -177,6 +178,18 @@ export const deleteOperation = async <
}) })
} }
// /////////////////////////////////////
// Delete scheduled posts
// /////////////////////////////////////
if (collectionConfig.versions?.drafts && collectionConfig.versions.drafts.schedulePublish) {
await deleteScheduledPublishJobs({
id,
slug: collectionConfig.slug,
payload,
req,
})
}
// ///////////////////////////////////// // /////////////////////////////////////
// Delete document // Delete document
// ///////////////////////////////////// // /////////////////////////////////////

View File

@@ -19,6 +19,7 @@ import { commitTransaction } from '../../utilities/commitTransaction.js'
import { initTransaction } from '../../utilities/initTransaction.js' import { initTransaction } from '../../utilities/initTransaction.js'
import { killTransaction } from '../../utilities/killTransaction.js' import { killTransaction } from '../../utilities/killTransaction.js'
import { deleteCollectionVersions } from '../../versions/deleteCollectionVersions.js' import { deleteCollectionVersions } from '../../versions/deleteCollectionVersions.js'
import { deleteScheduledPublishJobs } from '../../versions/deleteScheduledPublishJobs.js'
import { buildAfterOperation } from './utils.js' import { buildAfterOperation } from './utils.js'
export type Arguments = { export type Arguments = {
@@ -155,6 +156,18 @@ export const deleteByIDOperation = async <TSlug extends CollectionSlug, TSelect
}) })
} }
// /////////////////////////////////////
// Delete scheduled posts
// /////////////////////////////////////
if (collectionConfig.versions?.drafts && collectionConfig.versions.drafts.schedulePublish) {
await deleteScheduledPublishJobs({
id,
slug: collectionConfig.slug,
payload,
req,
})
}
// ///////////////////////////////////// // /////////////////////////////////////
// Delete document // Delete document
// ///////////////////////////////////// // /////////////////////////////////////

View File

@@ -21,8 +21,9 @@ export const deleteCollectionVersions = async ({ id, slug, payload, req }: Args)
}, },
}) })
} catch (err) { } catch (err) {
payload.logger.error( payload.logger.error({
`There was an error removing versions for the deleted ${slug} document with ID ${id}.`, err,
) msg: `There was an error removing versions for the deleted ${slug} document with ID ${id}.`,
})
} }
} }

View File

@@ -0,0 +1,60 @@
import type { PayloadRequest } from '../types/index.js'
import { type Payload } from '../index.js'
type Args = {
id?: number | string
payload: Payload
req?: PayloadRequest
slug: string
}
export const deleteScheduledPublishJobs = async ({
id,
slug,
payload,
req,
}: Args): Promise<void> => {
try {
await payload.db.deleteMany({
collection: 'payload-jobs',
req,
where: {
and: [
// only want to delete jobs have not run yet
{
completedAt: {
exists: false,
},
},
{
processing: {
equals: false,
},
},
{
'input.doc.value': {
equals: id,
},
},
{
'input.doc.relationTo': {
equals: slug,
},
},
// data.type narrows scheduled publish jobs in case of another job having input.doc.value
{
taskSlug: {
equals: 'schedulePublish',
},
},
],
},
})
} catch (err) {
payload.logger.error({
err,
msg: `There was an error deleting scheduled publish jobs from the queue for ${slug} document with ID ${id}.`,
})
}
}

View File

@@ -1944,6 +1944,94 @@ describe('Versions', () => {
expect(retrieved._status).toStrictEqual('draft') expect(retrieved._status).toStrictEqual('draft')
}) })
it('should delete scheduled jobs after a document is deleted', async () => {
const draft = await payload.create({
collection: draftCollectionSlug,
data: {
title: 'my doc to publish in the future',
description: 'hello',
},
draft: true,
})
expect(draft._status).toStrictEqual('draft')
const currentDate = new Date()
await payload.jobs.queue({
task: 'schedulePublish',
waitUntil: new Date(currentDate.getTime() + 3000),
input: {
type: 'publish',
doc: {
relationTo: draftCollectionSlug,
value: draft.id,
},
},
})
await payload.delete({
collection: draftCollectionSlug,
where: {
id: { equals: draft.id },
},
})
const { docs } = await payload.find({
collection: 'payload-jobs',
where: {
'input.doc.value': {
equals: draft.id,
},
},
})
expect(docs[0]).toBeUndefined()
})
it('should delete scheduled jobs after a document is deleted by ID', async () => {
const draft = await payload.create({
collection: draftCollectionSlug,
data: {
title: 'my doc to publish in the future',
description: 'hello',
},
draft: true,
})
expect(draft._status).toStrictEqual('draft')
const currentDate = new Date()
await payload.jobs.queue({
task: 'schedulePublish',
waitUntil: new Date(currentDate.getTime() + 3000),
input: {
type: 'publish',
doc: {
relationTo: draftCollectionSlug,
value: draft.id,
},
},
})
await payload.delete({
collection: draftCollectionSlug,
id: draft.id,
})
const { docs } = await payload.find({
collection: 'payload-jobs',
where: {
'input.doc.value': {
equals: draft.id,
},
},
})
expect(docs[0]).toBeUndefined()
})
it('should allow global scheduled publish', async () => { it('should allow global scheduled publish', async () => {
const draft = await payload.updateGlobal({ const draft = await payload.updateGlobal({
slug: draftGlobalSlug, slug: draftGlobalSlug,