Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Implementation of createMany lifecycle hook #972

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"meilisearch": "^0.42.0"
},
"peerDependencies": {
"@strapi/strapi": "^4.0.0"
"@strapi/strapi": "^4.9.0"
},
"author": {
"name": "Charlotte Vermandel <[email protected]>"
Expand Down
3 changes: 3 additions & 0 deletions server/__mocks__/meilisearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ const updateSettingsMock = jest.fn(() => 10)
const deleteDocuments = jest.fn(() => {
return [{ taskUid: 1 }, { taskUid: 2 }]
})
const deleteDocument = jest.fn(() => 3)

const getStats = jest.fn(() => {
return {
databaseSize: 447819776,
Expand Down Expand Up @@ -41,6 +43,7 @@ const mockIndex = jest.fn(() => ({
addDocuments: addDocumentsMock,
updateDocuments: updateDocumentsMock,
updateSettings: updateSettingsMock,
deleteDocument,
deleteDocuments,
getStats: getIndexStats,
}))
Expand Down
100 changes: 100 additions & 0 deletions server/__tests__/meilisearch.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,106 @@ describe('Tests content types', () => {
])
})

test('Test to update entries linked to multiple indexes in Meilisearch', async () => {
const pluginMock = jest.fn(() => ({
// This rewrites only the needed methods to reach the system under test (removeSensitiveFields)
service: jest.fn().mockImplementation(() => {
return {
async actionInBatches({ contentType = 'restaurant', callback }) {
await callback({
entries: [
{
id: 1,
title: 'title',
internal_notes: 'note123',
secret: '123',
},
{
id: 2,
title: 'abc',
internal_notes: 'note234',
secret: '234',
},
],
contentType,
})
},
getCollectionName: ({ contentType }) => contentType,
addIndexedContentType: jest.fn(),
subscribeContentType: jest.fn(),
getCredentials: () => ({}),
}
}),
}))

// Spy
const client = new Meilisearch({ host: 'abc' })

const meilisearchService = createMeilisearchService({
strapi: {
plugin: pluginMock,
contentTypes: {
restaurant: {
attributes: {
id: { private: false },
title: { private: false },
internal_notes: { private: true },
secret: { private: true },
},
},
},
config: {
get: jest.fn(() => ({
restaurant: {
noSanitizePrivateFields: ['internal_notes'],
indexName: ['customIndex', 'anotherIndex'],
},
})),
},
log: mockLogger,
},
contentTypes: {
restaurant: {
attributes: {
id: { private: false },
title: { private: false },
internal_notes: { private: true },
secret: { private: true },
},
},
},
})

const mockEntryUpdate = { attributes: { id: 1 } }

const mockEntryCreate = {
_meilisearch_id: 'restaurant-1',
id: 3,
title: 'title',
internal_notes: 'note123',
publishedAt: null,
}

const tasks = await meilisearchService.updateEntriesInMeilisearch({
contentType: 'restaurant',
entries: [mockEntryUpdate, mockEntryCreate],
})

expect(strapi.log.info).toHaveBeenCalledTimes(4)
expect(strapi.log.info).toHaveBeenCalledWith(
'A task to update 1 documents to the Meilisearch index "customIndex" has been enqueued.',
)
expect(strapi.log.info).toHaveBeenCalledWith(
'A task to update 1 documents to the Meilisearch index "anotherIndex" has been enqueued.',
)
expect(client.index('').updateDocuments).toHaveBeenCalledTimes(2)
expect(client.index('').deleteDocument).toHaveBeenCalledTimes(2)

expect(client.index).toHaveBeenCalledWith('customIndex')
expect(client.index).toHaveBeenCalledWith('anotherIndex')
expect(tasks).toEqual([3, 3, 10, 10])
})

test('Test to get stats', async () => {
const customStrapi = createStrapiMock({})

Expand Down
39 changes: 35 additions & 4 deletions server/services/lifecycle/lifecycle.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,41 @@ module.exports = ({ strapi }) => {
)
})
},
async afterCreateMany() {
strapi.log.error(
`Meilisearch does not work with \`afterCreateMany\` hook as the entries are provided without their id`,
)
async afterCreateMany(event) {
const { result } = event
const meilisearch = strapi
.plugin('meilisearch')
.service('meilisearch')

const nbrEntries = result.count
const ids = result.ids

const entries = []
const BATCH_SIZE = 500
for (let pos = 0; pos < nbrEntries; pos += BATCH_SIZE) {
const batch = await contentTypeService.getEntries({
contentType: contentTypeUid,
start: pos,
limit: BATCH_SIZE,
filters: {
id: {
$in: ids,
},
},
})
entries.push(...batch)
}

meilisearch
.updateEntriesInMeilisearch({
contentType: contentTypeUid,
entries: entries,
})
.catch(e => {
strapi.log.error(
`Meilisearch could not update the entries: ${e.message}`,
)
})
},
async afterUpdate(event) {
const { result } = event
Expand Down
74 changes: 46 additions & 28 deletions server/services/meilisearch/connector.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,40 +132,58 @@ module.exports = ({ strapi, adapter, config }) => {
if (!Array.isArray(entries)) entries = [entries]

const indexUids = config.getIndexNamesOfContentType({ contentType })
await Promise.all(

const addDocuments = await sanitizeEntries({
contentType,
entries,
config,
adapter,
})

// Check which documents are not in sanitized documents and need to be deleted
const deleteDocuments = entries.filter(
entry => !addDocuments.map(document => document.id).includes(entry.id),
)
// Collect delete tasks
const deleteTasks = await Promise.all(
indexUids.map(async indexUid => {
const tasks = await Promise.all(
entries.map(async entry => {
const sanitized = await sanitizeEntries({
entries: [entry],
contentType,
config,
adapter,
})

if (sanitized.length === 0) {
const task = await client.index(indexUid).deleteDocument(
adapter.addCollectionNamePrefixToId({
contentType,
entryId: entry.id,
}),
)

strapi.log.info(
`A task to delete one document from the Meilisearch index "${indexUid}" has been enqueued (Task uid: ${task.taskUid}).`,
)

return task
} else {
return client
.index(indexUid)
.updateDocuments(sanitized, { primaryKey: '_meilisearch_id' })
}
deleteDocuments.map(async document => {
const task = await client.index(indexUid).deleteDocument(
adapter.addCollectionNamePrefixToId({
contentType,
entryId: document.id,
}),
)

strapi.log.info(
`A task to delete one document from the Meilisearch index "${indexUid}" has been enqueued (Task uid: ${task.taskUid}).`,
)

return task
}),
)
return tasks.flat()
return tasks
}),
)

// Collect update tasks
const updateTasks = await Promise.all(
indexUids.map(async indexUid => {
const task = client.index(indexUid).updateDocuments(addDocuments, {
primaryKey: '_meilisearch_id',
})

strapi.log.info(
`A task to update ${addDocuments.length} documents to the Meilisearch index "${indexUid}" has been enqueued.`,
)

return task
}),
)

const tasks = [...deleteTasks.flat(), ...updateTasks]
return tasks
},

/**
Expand Down
Loading