@vegan-friendly/strapi-plugin-elasticsearch 0.2.9 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vegan-friendly/strapi-plugin-elasticsearch",
3
- "version": "0.2.9",
3
+ "version": "0.3.1",
4
4
  "description": "A Strapi plugin to enable using Elasticsearch with Strapi CMS.",
5
5
  "homepage": "https://github.com/vegan-friendly/strapi-plugin-elasticsearch",
6
6
  "strapi": {
@@ -54,7 +54,7 @@
54
54
  }
55
55
  ],
56
56
  "engines": {
57
- "node": ">=16.0.0 <=20.x.x",
57
+ "node": ">=16.0.0",
58
58
  "npm": ">=6.0.0"
59
59
  },
60
60
  "license": "MIT",
@@ -156,6 +156,9 @@ exports.default = async ({ strapi }) => {
156
156
  },
157
157
  });
158
158
  });
159
+ // clean up old indexing tasks, as server is booting.
160
+ // allow 60 seconds, in case strapi is being run in cluster mode and this is the second instance
161
+ await scheduleIndexingService.getActiveFullIndexingTasks(60);
159
162
  configureIndexingService.markInitialized();
160
163
  }
161
164
  catch (err) {
@@ -34,6 +34,10 @@ declare const _default: {
34
34
  required: boolean;
35
35
  default: string;
36
36
  };
37
+ error_message: {
38
+ type: string;
39
+ required: boolean;
40
+ };
37
41
  full_site_indexing: {
38
42
  type: string;
39
43
  };
@@ -32,6 +32,10 @@ declare const _default: {
32
32
  required: boolean;
33
33
  default: string;
34
34
  };
35
+ error_message: {
36
+ type: string;
37
+ required: boolean;
38
+ };
35
39
  full_site_indexing: {
36
40
  type: string;
37
41
  };
@@ -30,10 +30,14 @@ exports.default = {
30
30
  },
31
31
  indexing_status: {
32
32
  type: 'enumeration',
33
- enum: ['to-be-done', 'in-progress', 'done'],
33
+ enum: ['to-be-done', 'in-progress', 'done', 'failed'],
34
34
  required: true,
35
35
  default: 'to-be-done',
36
36
  },
37
+ error_message: {
38
+ type: 'string',
39
+ required: false,
40
+ },
37
41
  full_site_indexing: {
38
42
  type: 'boolean',
39
43
  },
@@ -142,9 +142,9 @@ declare const _default: {
142
142
  recordId: any;
143
143
  }): Promise<void>;
144
144
  getItemsPendingToBeIndexed(): Promise<any>;
145
- markIndexingTaskComplete(recId: any): Promise<void>;
145
+ markIndexingTaskComplete(recId: any, error?: string | null): Promise<void>;
146
146
  markIndexingTaskInProgress(recId: any): Promise<void>;
147
- getFullIndexingInProgress(): Promise<any>;
147
+ getActiveFullIndexingTasks(staleTaskThresholdSeconds?: number): Promise<any[]>;
148
148
  };
149
149
  esInterface: ({ strapi }: {
150
150
  strapi: any;
@@ -152,7 +152,7 @@ declare const _default: {
152
152
  indexer: ({ strapi }: {
153
153
  strapi: any;
154
154
  }) => {
155
- rebuildIndex(item?: any): Promise<boolean>;
155
+ rebuildIndex(task?: any): Promise<boolean>;
156
156
  indexCollection(collectionName: any, indexName?: string | null): Promise<number>;
157
157
  indexPendingData(): Promise<boolean>;
158
158
  };
@@ -215,6 +215,10 @@ declare const _default: {
215
215
  required: boolean;
216
216
  default: string;
217
217
  };
218
+ error_message: {
219
+ type: string;
220
+ required: boolean;
221
+ };
218
222
  full_site_indexing: {
219
223
  type: string;
220
224
  };
@@ -38,9 +38,9 @@ declare const _default: {
38
38
  recordId: any;
39
39
  }): Promise<void>;
40
40
  getItemsPendingToBeIndexed(): Promise<any>;
41
- markIndexingTaskComplete(recId: any): Promise<void>;
41
+ markIndexingTaskComplete(recId: any, error?: string | null): Promise<void>;
42
42
  markIndexingTaskInProgress(recId: any): Promise<void>;
43
- getFullIndexingInProgress(): Promise<any>;
43
+ getActiveFullIndexingTasks(staleTaskThresholdSeconds?: number): Promise<any[]>;
44
44
  };
45
45
  esInterface: ({ strapi }: {
46
46
  strapi: any;
@@ -48,7 +48,7 @@ declare const _default: {
48
48
  indexer: ({ strapi }: {
49
49
  strapi: any;
50
50
  }) => {
51
- rebuildIndex(item?: any): Promise<boolean>;
51
+ rebuildIndex(task?: any): Promise<boolean>;
52
52
  indexCollection(collectionName: any, indexName?: string | null): Promise<number>;
53
53
  indexPendingData(): Promise<boolean>;
54
54
  };
@@ -1,7 +1,7 @@
1
1
  declare const _default: ({ strapi }: {
2
2
  strapi: any;
3
3
  }) => {
4
- rebuildIndex(item?: any): Promise<boolean>;
4
+ rebuildIndex(task?: any): Promise<boolean>;
5
5
  indexCollection(collectionName: any, indexName?: string | null): Promise<number>;
6
6
  indexPendingData(): Promise<boolean>;
7
7
  };
@@ -1,17 +1,19 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.default = ({ strapi }) => ({
4
- async rebuildIndex(item = null) {
5
- const helper = strapi.plugins['elasticsearch'].services.helper;
6
- const esInterface = strapi.plugins['elasticsearch'].services.esInterface;
7
- const scheduleIndexingService = strapi.plugins['elasticsearch'].services.scheduleIndexing;
8
- const configureIndexingService = strapi.plugins['elasticsearch'].services.configureIndexing;
9
- const logIndexingService = strapi.plugins['elasticsearch'].services.logIndexing;
10
- const virtualCollectionsIndexer = strapi.plugins['elasticsearch'].services['virtualCollectionsIndexer'];
11
- const virtualCollectionsRegistry = strapi.plugins['elasticsearch'].services['virtualCollectionsRegistry'];
4
+ async rebuildIndex(task = null) {
5
+ const pluginServices = strapi.plugins['elasticsearch'].services;
6
+ const helper = pluginServices.helper;
7
+ const esInterface = pluginServices.esInterface;
8
+ const scheduleIndexingService = pluginServices.scheduleIndexing;
9
+ const configureIndexingService = pluginServices.configureIndexing;
10
+ const logIndexingService = pluginServices.logIndexing;
11
+ const virtualCollectionsIndexer = pluginServices.virtualCollectionsIndexer;
12
+ const virtualCollectionsRegistry = pluginServices.virtualCollectionsRegistry;
13
+ let taskError = null;
12
14
  try {
13
15
  console.log('strapi-plugin-elasticsearch : Request to rebuild the index received.');
14
- const fullIndexingInProgress = await scheduleIndexingService.getFullIndexingInProgress();
16
+ const fullIndexingInProgress = await scheduleIndexingService.getActiveFullIndexingTasks();
15
17
  if (fullIndexingInProgress.length > 0) {
16
18
  const msg = `Indexing is already in progress - see tasks ${fullIndexingInProgress.map((t) => t.id)}. This request is ignored and marked as failed.`;
17
19
  console.log('strapi-plugin-elasticsearch : ' + msg);
@@ -35,19 +37,19 @@ exports.default = ({ strapi }) => ({
35
37
  }
36
38
  //Step 2 : Index all the stuff on this new index
37
39
  console.log('strapi-plugin-elasticsearch : Starting to index all data into the new index.');
38
- if (item == null) {
39
- item = await scheduleIndexingService.addFullSiteIndexingTask();
40
+ if (task == null) {
41
+ task = await scheduleIndexingService.addFullSiteIndexingTask();
40
42
  }
41
- if (item?.id) {
42
- await scheduleIndexingService.markIndexingTaskInProgress(item.id);
43
+ if (task?.id) {
44
+ await scheduleIndexingService.markIndexingTaskInProgress(task.id);
43
45
  let entitiesIndexed = 0;
44
46
  for (let r = 0; r < cols.length; r++) {
45
47
  entitiesIndexed += await this.indexCollection(cols[r], newIndexName);
46
48
  }
47
49
  // Indexing the virtual collections
48
- console.log('strapi-plugin-elasticsearch : Starting to index virtual collections. task id : ', item.id);
50
+ console.log('strapi-plugin-elasticsearch : Starting to index virtual collections. task id : ', task.id);
49
51
  const virtualEntriesIndexed = await virtualCollectionsIndexer.reindexAll(newIndexName);
50
- await scheduleIndexingService.markIndexingTaskComplete(item.id);
52
+ await scheduleIndexingService.markIndexingTaskComplete(task.id);
51
53
  console.log('strapi-plugin-elasticsearch : Indexing of data into the new index complete.');
52
54
  //Step 4 : Move the alias to this new index
53
55
  await esInterface.attachAliasToIndex(newIndexName);
@@ -64,14 +66,15 @@ exports.default = ({ strapi }) => ({
64
66
  }
65
67
  }
66
68
  catch (err) {
69
+ taskError = err.message || String(err);
67
70
  console.log('strapi-plugin-elasticsearch : searchController : An error was encountered while re-indexing.');
68
71
  console.log(err);
69
72
  await logIndexingService.recordIndexingFail(err);
70
73
  throw err;
71
74
  }
72
75
  finally {
73
- if (item?.id) {
74
- await scheduleIndexingService.markIndexingTaskComplete(item.id);
76
+ if (task?.id) {
77
+ await scheduleIndexingService.markIndexingTaskComplete(task.id, taskError);
75
78
  }
76
79
  }
77
80
  },
@@ -14,8 +14,8 @@ declare const _default: ({ strapi }: {
14
14
  recordId: any;
15
15
  }): Promise<void>;
16
16
  getItemsPendingToBeIndexed(): Promise<any>;
17
- markIndexingTaskComplete(recId: any): Promise<void>;
17
+ markIndexingTaskComplete(recId: any, error?: string | null): Promise<void>;
18
18
  markIndexingTaskInProgress(recId: any): Promise<void>;
19
- getFullIndexingInProgress(): Promise<any>;
19
+ getActiveFullIndexingTasks(staleTaskThresholdSeconds?: number): Promise<any[]>;
20
20
  };
21
21
  export default _default;
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ const STUCK_THRESHOLD_SECONDS = 60 * 60; // 1 hour
3
4
  exports.default = ({ strapi }) => ({
4
5
  async addFullSiteIndexingTask() {
5
6
  const data = await strapi.entityService.create('plugin::elasticsearch.task', {
@@ -52,10 +53,12 @@ exports.default = ({ strapi }) => ({
52
53
  });
53
54
  return entries;
54
55
  },
55
- async markIndexingTaskComplete(recId) {
56
+ async markIndexingTaskComplete(recId, error = null) {
57
+ const status = error ? 'failed' : 'done';
56
58
  const entries = await strapi.entityService.update('plugin::elasticsearch.task', recId, {
57
59
  data: {
58
- indexing_status: 'done',
60
+ indexing_status: status,
61
+ error_message: error,
59
62
  },
60
63
  });
61
64
  },
@@ -66,13 +69,29 @@ exports.default = ({ strapi }) => ({
66
69
  },
67
70
  });
68
71
  },
69
- async getFullIndexingInProgress() {
72
+ async getActiveFullIndexingTasks(staleTaskThresholdSeconds) {
73
+ if (!staleTaskThresholdSeconds) {
74
+ staleTaskThresholdSeconds = Number(strapi.config.get('plugin.elasticsearch').staleTaskThresholdSeconds) || STUCK_THRESHOLD_SECONDS;
75
+ }
76
+ staleTaskThresholdSeconds *= 1000;
70
77
  const entries = await strapi.entityService.findMany('plugin::elasticsearch.task', {
71
78
  filters: {
72
79
  indexing_status: 'in-progress',
73
80
  full_site_indexing: true,
74
81
  },
75
82
  });
76
- return entries;
83
+ const now = Date.now();
84
+ const activeTasks = [];
85
+ for (const t of entries) {
86
+ const updatedAt = new Date(t.updatedAt || t.createdAt).getTime();
87
+ if (now - updatedAt > staleTaskThresholdSeconds) {
88
+ await strapi.plugins['elasticsearch'].services.logIndexing.recordIndexingFail(`Task ${t.id} was stuck in-progress for too long. Marking as failed.`);
89
+ await this.markIndexingTaskComplete(t.id, 'Stuck in-progress');
90
+ }
91
+ else {
92
+ activeTasks.push(t);
93
+ }
94
+ }
95
+ return activeTasks;
77
96
  },
78
97
  });
@@ -24,7 +24,9 @@ exports.default = ({ strapi }) => {
24
24
  try {
25
25
  const results = await collection.extractByIds([itemId]);
26
26
  if (!results || !Array.isArray(results) || results.length === 0) {
27
- strapi.log.warn(`No data extracted for ${collectionName} with ID ${itemId}`);
27
+ // item does not exit - delete it from index
28
+ await this.deleteItem(collectionName, itemId);
29
+ strapi.log.debug(`Deleted virtual item: ${collectionName}:${itemId}`);
28
30
  return null;
29
31
  }
30
32
  const itemData = results[0];
@@ -58,9 +60,11 @@ exports.default = ({ strapi }) => {
58
60
  async reindex(collection) {
59
61
  const collectionName = collection.collectionName;
60
62
  const privateIndexAlias = collection.indexAlias;
63
+ const pageSize = 100;
61
64
  const helper = getHelperService();
62
65
  let timestamp = Date.now();
63
66
  let indexName = '';
67
+ let errors = 0;
64
68
  try {
65
69
  const esInterface = getElasticsearchService();
66
70
  if (privateIndexAlias) {
@@ -75,7 +79,16 @@ exports.default = ({ strapi }) => {
75
79
  let totalIndexed = 0;
76
80
  const pageLimit = 10000;
77
81
  while (page <= pageLimit) {
78
- const pageData = await collection.extractData(page);
82
+ let pageData;
83
+ try {
84
+ pageData = await collection.extractData(page, pageSize);
85
+ }
86
+ catch (error) {
87
+ strapi.log.error(`Error extracting data for page ${page} of ${collectionName}: ${error.message}`);
88
+ errors += pageSize;
89
+ page++;
90
+ continue;
91
+ }
79
92
  strapi.log.debug(`Extracted ${pageData.length} items from ${collectionName} for page ${page}`);
80
93
  if (!Array.isArray(pageData) || pageData.length === 0) {
81
94
  break;
@@ -97,12 +110,18 @@ exports.default = ({ strapi }) => {
97
110
  operations.push({ itemId, itemData });
98
111
  }
99
112
  if (operations.length > 0) {
100
- await Promise.all(operations.map((op) => esInterface.indexDataToSpecificIndex(op, indexName)));
113
+ await Promise.all(operations.map((op) => esInterface.indexDataToSpecificIndex(op, indexName).catch((err) => {
114
+ strapi.log.error(`Failed to index item ${op.itemId} in ${collectionName}: ${err}`);
115
+ errors++;
116
+ })));
101
117
  }
102
118
  totalIndexed += pageData.length;
103
119
  prevPageData = pageData;
104
120
  page++;
105
121
  }
122
+ if (errors > 0) {
123
+ throw new Error(`Failed to index ${errors} of ${totalIndexed} items for virtual collection ${collectionName}. Errors were logged. Alias was not updated. took ${(0, humanize_duration_1.default)(Date.now() - timestamp)}`);
124
+ }
106
125
  strapi.log.info(`Reindexed ${totalIndexed} items for virtual collection: ${collectionName}. took ${(0, humanize_duration_1.default)(Date.now() - timestamp)}. now updating alias.`);
107
126
  if (privateIndexAlias) {
108
127
  timestamp = Date.now();
@@ -138,14 +157,7 @@ exports.default = ({ strapi }) => {
138
157
  const idsToReindex = await trigger.getIdsToReindex(result);
139
158
  // Reindex each item
140
159
  for (const id of idsToReindex) {
141
- const isDelete = event.action?.toLowerCase()?.includes('delete') && trigger.alsoTriggerDelete && id === result.id;
142
- if (isDelete) {
143
- //delete the item from the index, if the item being delete is the one being reindexed
144
- await this.deleteItem(collection.collectionName, id);
145
- }
146
- else {
147
- await this.indexItem(collection.collectionName, id);
148
- }
160
+ await this.indexItem(collection.collectionName, id);
149
161
  }
150
162
  }
151
163
  },
@@ -47,7 +47,6 @@ const configSchema = yup.object({
47
47
  .of(yup.object({
48
48
  collection: yup.string().required(),
49
49
  getIdsToReindex: isFunction().required(),
50
- alsoTriggerDelete: yup.boolean().default(false),
51
50
  }))
52
51
  .default([]),
53
52
  mappings: yup.object().optional(),
@@ -45,11 +45,6 @@ export type VirtualCollectionConfig = {
45
45
  * @returns ids of the items to be reindexed.
46
46
  */
47
47
  getIdsToReindex: GetIdsToIndexFunction;
48
- /**
49
- * if true, and the trigger is a delete event, the item of the virtual collection will be deleted as well if the id returned from getIdsToReindex match.
50
- * defaults to false.
51
- */
52
- alsoTriggerDelete?: boolean;
53
48
  }>;
54
49
  /**
55
50
  * Optional schema to be sent to Elasticsearch when creating the index.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vegan-friendly/strapi-plugin-elasticsearch",
3
- "version": "0.2.9",
3
+ "version": "0.3.1",
4
4
  "description": "A Strapi plugin to enable using Elasticsearch with Strapi CMS.",
5
5
  "homepage": "https://github.com/vegan-friendly/strapi-plugin-elasticsearch",
6
6
  "strapi": {
@@ -54,7 +54,7 @@
54
54
  }
55
55
  ],
56
56
  "engines": {
57
- "node": ">=16.0.0 <=20.x.x",
57
+ "node": ">=16.0.0",
58
58
  "npm": ">=6.0.0"
59
59
  },
60
60
  "license": "MIT",
@@ -64,4 +64,4 @@
64
64
  "ts-node": "^10.9.2",
65
65
  "typescript": "^5.8.3"
66
66
  }
67
- }
67
+ }