Files
payload/packages/plugin-search/src/index.ts
Said Akhrarov defa13e4fe feat(plugin-search): added support for reindexing collections on demand (#9391)
### What?
This PR aims to add reindexing capabilities to `plugin-search` to allow
users to reindex entire searchable collections on demand.

### Why?
As it stands, end users must either perform document reindexing manually
one-by-one or via bulk operations. Both of these approaches are
undesirable because they result in new versions being published on
existing documents. Consider the case when `plugin-search` is only added
_after_ the project has started and documents have been added to
existing collections. It would be nice if users could simply click a
button, choose the searchable collections to reindex, and have the
custom endpoint handle the rest.

### How?
This PR adds on to the existing plugin configuration, creating a custom
endpoint and a custom `beforeListTable` component in the form of a popup
button. Upon clicking the button, a dropdown/popup is opened with
options to select which collection to reindex, as well as a useful `All
Collections` option to run reindexing on all configured search
collections. It also adds a `reindexBatchSize` option in the config to
allow users to specify in what quantity to batch documents to sync with
search.

Big shoutout to @paulpopus & @r1tsuu for the triple-A level support on
this one!

Fixes #8902 

See it in action:


https://github.com/user-attachments/assets/ee8dd68c-ea89-49cd-adc3-151973eea28b

Notes:
- Traditionally these kinds of long-running tasks would be better suited
for a job. However, given how many users enjoy deploying to serverless
environments, it would be problematic to offer this feature exclusive to
jobs queues. I thought a significant amount about this and decided it
would be best to ship the feature as-is with the intention of creating
an opt-in method to use job queues in the future if/when this gets
merged.
- In my testing, the collection description somehow started to appear in
the document views after the on-demand RSC merge. I haven't reproduced
this, but this PR has an example of that problem. Super strange.

---------

Co-authored-by: Sasha <64744993+r1tsuu@users.noreply.github.com>
Co-authored-by: Paul Popus <paul@nouance.io>
2024-11-26 23:14:31 +00:00

98 lines
3.3 KiB
TypeScript

import type { CollectionAfterChangeHook, CollectionAfterDeleteHook, Config } from 'payload'
import type { SearchPluginConfig, SearchPluginConfigWithLocales } from './types.js'
import { deleteFromSearch } from './Search/hooks/deleteFromSearch.js'
import { syncWithSearch } from './Search/hooks/syncWithSearch.js'
import { generateSearchCollection } from './Search/index.js'
type CollectionAfterChangeHookArgs = Parameters<CollectionAfterChangeHook>[0]
type CollectionAfterDeleteHookArgs = Parameters<CollectionAfterDeleteHook>[0]
export const searchPlugin =
(incomingPluginConfig: SearchPluginConfig) =>
(config: Config): Config => {
const { collections } = config
// If the user defines `localize` to either true or false, use that
// Otherwise, set it based on if their config has localization enabled or disabled
const shouldLocalize =
typeof incomingPluginConfig.localize === 'boolean'
? incomingPluginConfig.localize
: Boolean(config.localization)
incomingPluginConfig.localize = shouldLocalize
if (collections) {
const locales = config.localization
? config.localization.locales.map((localeConfig) =>
typeof localeConfig === 'string' ? localeConfig : localeConfig.code,
)
: []
const labels = Object.fromEntries(
collections
.filter(({ slug }) => incomingPluginConfig.collections?.includes(slug))
.map((collection) => [collection.slug, collection.labels]),
)
const pluginConfig: SearchPluginConfigWithLocales = {
// write any config defaults here
deleteDrafts: true,
labels,
locales,
reindexBatchSize: incomingPluginConfig?.reindexBatchSize || 50,
syncDrafts: false,
...incomingPluginConfig,
}
// add afterChange and afterDelete hooks to every search-enabled collection
const collectionsWithSearchHooks = config?.collections
?.map((collection) => {
const { hooks: existingHooks } = collection
const enabledCollections = pluginConfig.collections || []
const isEnabled = enabledCollections.indexOf(collection.slug) > -1
if (isEnabled) {
return {
...collection,
hooks: {
...collection.hooks,
afterChange: [
...(existingHooks?.afterChange || []),
async (args: CollectionAfterChangeHookArgs) => {
await syncWithSearch({
...args,
collection: collection.slug,
pluginConfig,
})
},
],
afterDelete: [
...(existingHooks?.afterDelete || []),
async (args: CollectionAfterDeleteHookArgs) => {
await deleteFromSearch({
...args,
pluginConfig,
})
},
],
},
}
}
return collection
})
.filter(Boolean)
return {
...config,
collections: [
...(collectionsWithSearchHooks || []),
generateSearchCollection(pluginConfig),
],
}
}
return config
}