### 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>
98 lines
3.3 KiB
TypeScript
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
|
|
}
|