@backstage/plugin-search-backend-module-elasticsearch 1.5.7-next.1 → 1.6.0
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/CHANGELOG.md +29 -0
- package/alpha/package.json +1 -1
- package/dist/alpha.cjs.js +3 -49
- package/dist/alpha.cjs.js.map +1 -1
- package/dist/alpha.d.ts +2 -17
- package/dist/engines/ElasticSearchClientOptions.cjs.js +8 -0
- package/dist/engines/ElasticSearchClientOptions.cjs.js.map +1 -0
- package/dist/engines/ElasticSearchClientWrapper.cjs.js +137 -0
- package/dist/engines/ElasticSearchClientWrapper.cjs.js.map +1 -0
- package/dist/engines/ElasticSearchSearchEngine.cjs.js +371 -0
- package/dist/engines/ElasticSearchSearchEngine.cjs.js.map +1 -0
- package/dist/engines/ElasticSearchSearchEngineIndexer.cjs.js +169 -0
- package/dist/engines/ElasticSearchSearchEngineIndexer.cjs.js.map +1 -0
- package/dist/index.cjs.js +9 -660
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +21 -2
- package/dist/module.cjs.js +56 -0
- package/dist/module.cjs.js.map +1 -0
- package/package.json +9 -8
package/dist/index.cjs.js
CHANGED
|
@@ -1,667 +1,16 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
var aws = require('@opensearch-project/opensearch/aws');
|
|
5
|
-
var aws4 = require('aws4');
|
|
6
|
-
var elasticsearch = require('@elastic/elasticsearch');
|
|
7
|
-
var opensearch = require('@opensearch-project/opensearch');
|
|
8
|
-
var pluginSearchBackendNode = require('@backstage/plugin-search-backend-node');
|
|
9
|
-
var stream = require('stream');
|
|
10
|
-
var esb = require('elastic-builder');
|
|
11
|
-
var uuid = require('uuid');
|
|
12
|
-
var integrationAwsNode = require('@backstage/integration-aws-node');
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
13
4
|
|
|
14
|
-
|
|
5
|
+
var module$1 = require('./module.cjs.js');
|
|
6
|
+
var ElasticSearchSearchEngine = require('./engines/ElasticSearchSearchEngine.cjs.js');
|
|
7
|
+
var ElasticSearchClientOptions = require('./engines/ElasticSearchClientOptions.cjs.js');
|
|
15
8
|
|
|
16
|
-
var esb__default = /*#__PURE__*/_interopDefaultCompat(esb);
|
|
17
9
|
|
|
18
|
-
const isOpenSearchCompatible = (opts) => {
|
|
19
|
-
return ["aws", "opensearch"].includes(opts?.provider ?? "");
|
|
20
|
-
};
|
|
21
10
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
this.elasticSearchClient = options.elasticSearchClient;
|
|
28
|
-
}
|
|
29
|
-
static fromClientOptions(options) {
|
|
30
|
-
if (isOpenSearchCompatible(options)) {
|
|
31
|
-
return new ElasticSearchClientWrapper({
|
|
32
|
-
openSearchClient: new opensearch.Client(options)
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
return new ElasticSearchClientWrapper({
|
|
36
|
-
elasticSearchClient: new elasticsearch.Client(options)
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
search(options) {
|
|
40
|
-
const searchOptions = {
|
|
41
|
-
ignore_unavailable: true,
|
|
42
|
-
allow_no_indices: true
|
|
43
|
-
};
|
|
44
|
-
if (this.openSearchClient) {
|
|
45
|
-
return this.openSearchClient.search({
|
|
46
|
-
...options,
|
|
47
|
-
...searchOptions
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
if (this.elasticSearchClient) {
|
|
51
|
-
return this.elasticSearchClient.search({
|
|
52
|
-
...options,
|
|
53
|
-
...searchOptions
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
throw new Error("No client defined");
|
|
57
|
-
}
|
|
58
|
-
bulk(bulkOptions) {
|
|
59
|
-
if (this.openSearchClient) {
|
|
60
|
-
return this.openSearchClient.helpers.bulk(bulkOptions);
|
|
61
|
-
}
|
|
62
|
-
if (this.elasticSearchClient) {
|
|
63
|
-
return this.elasticSearchClient.helpers.bulk(bulkOptions);
|
|
64
|
-
}
|
|
65
|
-
throw new Error("No client defined");
|
|
66
|
-
}
|
|
67
|
-
putIndexTemplate(template) {
|
|
68
|
-
if (this.openSearchClient) {
|
|
69
|
-
return this.openSearchClient.indices.putIndexTemplate(template);
|
|
70
|
-
}
|
|
71
|
-
if (this.elasticSearchClient) {
|
|
72
|
-
return this.elasticSearchClient.indices.putIndexTemplate(template);
|
|
73
|
-
}
|
|
74
|
-
throw new Error("No client defined");
|
|
75
|
-
}
|
|
76
|
-
listIndices(options) {
|
|
77
|
-
if (this.openSearchClient) {
|
|
78
|
-
return this.openSearchClient.indices.get(options);
|
|
79
|
-
}
|
|
80
|
-
if (this.elasticSearchClient) {
|
|
81
|
-
return this.elasticSearchClient.indices.get(options);
|
|
82
|
-
}
|
|
83
|
-
throw new Error("No client defined");
|
|
84
|
-
}
|
|
85
|
-
indexExists(options) {
|
|
86
|
-
if (this.openSearchClient) {
|
|
87
|
-
return this.openSearchClient.indices.exists(options);
|
|
88
|
-
}
|
|
89
|
-
if (this.elasticSearchClient) {
|
|
90
|
-
return this.elasticSearchClient.indices.exists(options);
|
|
91
|
-
}
|
|
92
|
-
throw new Error("No client defined");
|
|
93
|
-
}
|
|
94
|
-
deleteIndex(options) {
|
|
95
|
-
if (this.openSearchClient) {
|
|
96
|
-
return this.openSearchClient.indices.delete(options);
|
|
97
|
-
}
|
|
98
|
-
if (this.elasticSearchClient) {
|
|
99
|
-
return this.elasticSearchClient.indices.delete(options);
|
|
100
|
-
}
|
|
101
|
-
throw new Error("No client defined");
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* @deprecated unused by the ElasticSearch Engine, will be removed in the future
|
|
105
|
-
*/
|
|
106
|
-
getAliases(options) {
|
|
107
|
-
const { aliases } = options;
|
|
108
|
-
if (this.openSearchClient) {
|
|
109
|
-
return this.openSearchClient.cat.aliases({
|
|
110
|
-
format: "json",
|
|
111
|
-
name: aliases
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
if (this.elasticSearchClient) {
|
|
115
|
-
return this.elasticSearchClient.cat.aliases({
|
|
116
|
-
format: "json",
|
|
117
|
-
name: aliases
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
throw new Error("No client defined");
|
|
121
|
-
}
|
|
122
|
-
createIndex(options) {
|
|
123
|
-
if (this.openSearchClient) {
|
|
124
|
-
return this.openSearchClient.indices.create(options);
|
|
125
|
-
}
|
|
126
|
-
if (this.elasticSearchClient) {
|
|
127
|
-
return this.elasticSearchClient.indices.create(options);
|
|
128
|
-
}
|
|
129
|
-
throw new Error("No client defined");
|
|
130
|
-
}
|
|
131
|
-
updateAliases(options) {
|
|
132
|
-
const filteredActions = options.actions.filter(Boolean);
|
|
133
|
-
if (this.openSearchClient) {
|
|
134
|
-
return this.openSearchClient.indices.updateAliases({
|
|
135
|
-
body: {
|
|
136
|
-
actions: filteredActions
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
if (this.elasticSearchClient) {
|
|
141
|
-
return this.elasticSearchClient.indices.updateAliases({
|
|
142
|
-
body: {
|
|
143
|
-
actions: filteredActions
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
throw new Error("No client defined");
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function duration(startTimestamp) {
|
|
152
|
-
const delta = process.hrtime(startTimestamp);
|
|
153
|
-
const seconds = delta[0] + delta[1] / 1e9;
|
|
154
|
-
return `${seconds.toFixed(1)}s`;
|
|
155
|
-
}
|
|
156
|
-
class ElasticSearchSearchEngineIndexer extends pluginSearchBackendNode.BatchSearchEngineIndexer {
|
|
157
|
-
processed = 0;
|
|
158
|
-
removableIndices = [];
|
|
159
|
-
startTimestamp;
|
|
160
|
-
type;
|
|
161
|
-
indexName;
|
|
162
|
-
indexPrefix;
|
|
163
|
-
indexSeparator;
|
|
164
|
-
alias;
|
|
165
|
-
logger;
|
|
166
|
-
sourceStream;
|
|
167
|
-
elasticSearchClientWrapper;
|
|
168
|
-
configuredBatchSize;
|
|
169
|
-
bulkResult;
|
|
170
|
-
bulkClientError;
|
|
171
|
-
constructor(options) {
|
|
172
|
-
super({ batchSize: options.batchSize });
|
|
173
|
-
this.configuredBatchSize = options.batchSize;
|
|
174
|
-
this.logger = options.logger.child({ documentType: options.type });
|
|
175
|
-
this.startTimestamp = process.hrtime();
|
|
176
|
-
this.type = options.type;
|
|
177
|
-
this.indexPrefix = options.indexPrefix;
|
|
178
|
-
this.indexSeparator = options.indexSeparator;
|
|
179
|
-
this.indexName = this.constructIndexName(`${Date.now()}`);
|
|
180
|
-
this.alias = options.alias;
|
|
181
|
-
this.elasticSearchClientWrapper = options.elasticSearchClientWrapper;
|
|
182
|
-
this.sourceStream = new stream.Readable({ objectMode: true });
|
|
183
|
-
this.sourceStream._read = () => {
|
|
184
|
-
};
|
|
185
|
-
const that = this;
|
|
186
|
-
this.bulkResult = this.elasticSearchClientWrapper.bulk({
|
|
187
|
-
datasource: this.sourceStream,
|
|
188
|
-
onDocument() {
|
|
189
|
-
that.processed++;
|
|
190
|
-
return {
|
|
191
|
-
index: { _index: that.indexName }
|
|
192
|
-
};
|
|
193
|
-
},
|
|
194
|
-
refreshOnCompletion: options.skipRefresh !== true
|
|
195
|
-
});
|
|
196
|
-
this.bulkResult.catch((e) => {
|
|
197
|
-
this.bulkClientError = e;
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
async initialize() {
|
|
201
|
-
this.logger.info(`Started indexing documents for index ${this.type}`);
|
|
202
|
-
const indices = await this.elasticSearchClientWrapper.listIndices({
|
|
203
|
-
index: this.constructIndexName("*")
|
|
204
|
-
});
|
|
205
|
-
for (const key of Object.keys(indices.body)) {
|
|
206
|
-
this.removableIndices.push(key);
|
|
207
|
-
}
|
|
208
|
-
await this.elasticSearchClientWrapper.createIndex({
|
|
209
|
-
index: this.indexName
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
async index(documents) {
|
|
213
|
-
await this.isReady();
|
|
214
|
-
documents.forEach((document) => {
|
|
215
|
-
this.sourceStream.push(document);
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
async finalize() {
|
|
219
|
-
await this.isReady();
|
|
220
|
-
this.sourceStream.push(null);
|
|
221
|
-
const result = await this.bulkResult;
|
|
222
|
-
if (this.processed === 0) {
|
|
223
|
-
this.logger.warn(
|
|
224
|
-
`Index for ${this.type} was not ${this.removableIndices.length ? "replaced" : "created"}: indexer received 0 documents`
|
|
225
|
-
);
|
|
226
|
-
try {
|
|
227
|
-
await this.elasticSearchClientWrapper.deleteIndex({
|
|
228
|
-
index: this.indexName
|
|
229
|
-
});
|
|
230
|
-
} catch (error) {
|
|
231
|
-
this.logger.error(`Unable to clean up elastic index: ${error}`);
|
|
232
|
-
}
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
this.logger.info(
|
|
236
|
-
`Indexing completed for index ${this.type} in ${duration(
|
|
237
|
-
this.startTimestamp
|
|
238
|
-
)}`,
|
|
239
|
-
result
|
|
240
|
-
);
|
|
241
|
-
await this.elasticSearchClientWrapper.updateAliases({
|
|
242
|
-
actions: [
|
|
243
|
-
{
|
|
244
|
-
remove: { index: this.constructIndexName("*"), alias: this.alias }
|
|
245
|
-
},
|
|
246
|
-
{
|
|
247
|
-
add: { index: this.indexName, alias: this.alias }
|
|
248
|
-
}
|
|
249
|
-
].filter(Boolean)
|
|
250
|
-
});
|
|
251
|
-
if (this.removableIndices.length) {
|
|
252
|
-
this.logger.info("Removing stale search indices", {
|
|
253
|
-
removableIndices: this.removableIndices
|
|
254
|
-
});
|
|
255
|
-
const chunks = this.removableIndices.reduce(
|
|
256
|
-
(resultArray, item, index) => {
|
|
257
|
-
const chunkIndex = Math.floor(index / 50);
|
|
258
|
-
if (!resultArray[chunkIndex]) {
|
|
259
|
-
resultArray[chunkIndex] = [];
|
|
260
|
-
}
|
|
261
|
-
resultArray[chunkIndex].push(item);
|
|
262
|
-
return resultArray;
|
|
263
|
-
},
|
|
264
|
-
[]
|
|
265
|
-
);
|
|
266
|
-
for (const chunk of chunks) {
|
|
267
|
-
try {
|
|
268
|
-
await this.elasticSearchClientWrapper.deleteIndex({
|
|
269
|
-
index: chunk
|
|
270
|
-
});
|
|
271
|
-
} catch (e) {
|
|
272
|
-
this.logger.warn(`Failed to remove stale search indices: ${e}`);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
/**
|
|
278
|
-
* Ensures that the number of documents sent over the wire to ES matches the
|
|
279
|
-
* number of documents this stream has received so far. This helps manage
|
|
280
|
-
* backpressure in other parts of the indexing pipeline.
|
|
281
|
-
*/
|
|
282
|
-
isReady() {
|
|
283
|
-
if (this.bulkClientError) {
|
|
284
|
-
return Promise.reject(this.bulkClientError);
|
|
285
|
-
}
|
|
286
|
-
if (this.sourceStream.readableLength < this.configuredBatchSize) {
|
|
287
|
-
return Promise.resolve();
|
|
288
|
-
}
|
|
289
|
-
return new Promise((isReady, abort) => {
|
|
290
|
-
let streamLengthChecks = 0;
|
|
291
|
-
const interval = setInterval(() => {
|
|
292
|
-
streamLengthChecks++;
|
|
293
|
-
if (this.sourceStream.readableLength < this.configuredBatchSize) {
|
|
294
|
-
clearInterval(interval);
|
|
295
|
-
isReady();
|
|
296
|
-
}
|
|
297
|
-
if (streamLengthChecks >= 6e3) {
|
|
298
|
-
clearInterval(interval);
|
|
299
|
-
abort(
|
|
300
|
-
new Error(
|
|
301
|
-
"Exceeded 5 minutes waiting for elastic to be ready to accept more documents. Check the elastic logs for possible problems."
|
|
302
|
-
)
|
|
303
|
-
);
|
|
304
|
-
}
|
|
305
|
-
}, 50);
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
constructIndexName(postFix) {
|
|
309
|
-
return `${this.indexPrefix}${this.type}${this.indexSeparator}${postFix}`;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
function isBlank(str) {
|
|
314
|
-
return lodash.isEmpty(str) && !lodash.isNumber(str) || lodash.isNaN(str);
|
|
315
|
-
}
|
|
316
|
-
const DEFAULT_INDEXER_BATCH_SIZE = 1e3;
|
|
317
|
-
class ElasticSearchSearchEngine {
|
|
318
|
-
constructor(elasticSearchClientOptions, aliasPostfix, indexPrefix, logger, batchSize, highlightOptions) {
|
|
319
|
-
this.elasticSearchClientOptions = elasticSearchClientOptions;
|
|
320
|
-
this.aliasPostfix = aliasPostfix;
|
|
321
|
-
this.indexPrefix = indexPrefix;
|
|
322
|
-
this.logger = logger;
|
|
323
|
-
this.batchSize = batchSize;
|
|
324
|
-
this.elasticSearchClientWrapper = ElasticSearchClientWrapper.fromClientOptions(elasticSearchClientOptions);
|
|
325
|
-
const uuidTag = uuid.v4();
|
|
326
|
-
this.highlightOptions = {
|
|
327
|
-
preTag: `<${uuidTag}>`,
|
|
328
|
-
postTag: `</${uuidTag}>`,
|
|
329
|
-
fragmentSize: 1e3,
|
|
330
|
-
numFragments: 1,
|
|
331
|
-
fragmentDelimiter: " ... ",
|
|
332
|
-
...highlightOptions
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
elasticSearchClientWrapper;
|
|
336
|
-
highlightOptions;
|
|
337
|
-
static async fromConfig(options) {
|
|
338
|
-
const {
|
|
339
|
-
logger,
|
|
340
|
-
config,
|
|
341
|
-
aliasPostfix = `search`,
|
|
342
|
-
indexPrefix = ``,
|
|
343
|
-
translator
|
|
344
|
-
} = options;
|
|
345
|
-
const credentialProvider = integrationAwsNode.DefaultAwsCredentialsManager.fromConfig(config);
|
|
346
|
-
const clientOptions = await this.createElasticSearchClientOptions(
|
|
347
|
-
await credentialProvider?.getCredentialProvider(),
|
|
348
|
-
config.getConfig("search.elasticsearch")
|
|
349
|
-
);
|
|
350
|
-
if (clientOptions.provider === "elastic") {
|
|
351
|
-
logger.info("Initializing Elastic.co ElasticSearch search engine.");
|
|
352
|
-
} else if (clientOptions.provider === "aws") {
|
|
353
|
-
logger.info("Initializing AWS OpenSearch search engine.");
|
|
354
|
-
} else if (clientOptions.provider === "opensearch") {
|
|
355
|
-
logger.info("Initializing OpenSearch search engine.");
|
|
356
|
-
} else {
|
|
357
|
-
logger.info("Initializing ElasticSearch search engine.");
|
|
358
|
-
}
|
|
359
|
-
const engine = new ElasticSearchSearchEngine(
|
|
360
|
-
clientOptions,
|
|
361
|
-
aliasPostfix,
|
|
362
|
-
indexPrefix,
|
|
363
|
-
logger,
|
|
364
|
-
config.getOptionalNumber("search.elasticsearch.batchSize") ?? DEFAULT_INDEXER_BATCH_SIZE,
|
|
365
|
-
config.getOptional(
|
|
366
|
-
"search.elasticsearch.highlightOptions"
|
|
367
|
-
)
|
|
368
|
-
);
|
|
369
|
-
for (const indexTemplate of this.readIndexTemplateConfig(
|
|
370
|
-
config.getConfig("search.elasticsearch")
|
|
371
|
-
)) {
|
|
372
|
-
await engine.setIndexTemplate(indexTemplate);
|
|
373
|
-
}
|
|
374
|
-
if (translator) {
|
|
375
|
-
await engine.setTranslator(translator);
|
|
376
|
-
}
|
|
377
|
-
return engine;
|
|
378
|
-
}
|
|
379
|
-
/**
|
|
380
|
-
* Create a custom search client from the derived search client configuration.
|
|
381
|
-
* This need not be the same client that the engine uses internally.
|
|
382
|
-
*
|
|
383
|
-
* @example Instantiate an instance of an Elasticsearch client.
|
|
384
|
-
*
|
|
385
|
-
* ```ts
|
|
386
|
-
* import { isOpenSearchCompatible } from '@backstage/plugin-search-backend-module-elasticsearch';
|
|
387
|
-
* import { Client } from '@elastic/elasticsearch';
|
|
388
|
-
*
|
|
389
|
-
* const client = searchEngine.newClient<Client>(options => {
|
|
390
|
-
* // This type guard ensures options are compatible with either OpenSearch
|
|
391
|
-
* // or Elasticsearch client constructors.
|
|
392
|
-
* if (!isOpenSearchCompatible(options)) {
|
|
393
|
-
* return new Client(options);
|
|
394
|
-
* }
|
|
395
|
-
* throw new Error('Incompatible options provided');
|
|
396
|
-
* });
|
|
397
|
-
* ```
|
|
398
|
-
*/
|
|
399
|
-
newClient(create) {
|
|
400
|
-
return create(this.elasticSearchClientOptions);
|
|
401
|
-
}
|
|
402
|
-
translator(query, options) {
|
|
403
|
-
const { term, filters = {}, types, pageCursor } = query;
|
|
404
|
-
const filter = Object.entries(filters).filter(([_, value]) => Boolean(value)).map(([key, value]) => {
|
|
405
|
-
if (["string", "number", "boolean"].includes(typeof value)) {
|
|
406
|
-
const keyword = typeof value === "string" ? `${key}.keyword` : key;
|
|
407
|
-
return esb__default.default.matchQuery(keyword, value.toString());
|
|
408
|
-
}
|
|
409
|
-
if (Array.isArray(value)) {
|
|
410
|
-
return esb__default.default.boolQuery().should(value.map((it) => esb__default.default.matchQuery(key, it.toString())));
|
|
411
|
-
}
|
|
412
|
-
this.logger.error("Failed to query, unrecognized filter type", {
|
|
413
|
-
key,
|
|
414
|
-
value
|
|
415
|
-
});
|
|
416
|
-
throw new Error(
|
|
417
|
-
"Failed to add filters to query. Unrecognized filter type"
|
|
418
|
-
);
|
|
419
|
-
});
|
|
420
|
-
const esbQuery = isBlank(term) ? esb__default.default.matchAllQuery() : esb__default.default.multiMatchQuery(["*"], term).fuzziness("auto").minimumShouldMatch(1);
|
|
421
|
-
const pageSize = query.pageLimit || 25;
|
|
422
|
-
const { page } = decodePageCursor(pageCursor);
|
|
423
|
-
let esbRequestBodySearch = esb__default.default.requestBodySearch().query(esb__default.default.boolQuery().filter(filter).must([esbQuery])).from(page * pageSize).size(pageSize);
|
|
424
|
-
if (options?.highlightOptions) {
|
|
425
|
-
esbRequestBodySearch = esbRequestBodySearch.highlight(
|
|
426
|
-
esb__default.default.highlight("*").numberOfFragments(options.highlightOptions.numFragments).fragmentSize(options.highlightOptions.fragmentSize).preTags(options.highlightOptions.preTag).postTags(options.highlightOptions.postTag)
|
|
427
|
-
);
|
|
428
|
-
}
|
|
429
|
-
return {
|
|
430
|
-
elasticSearchQuery: esbRequestBodySearch.toJSON(),
|
|
431
|
-
documentTypes: types,
|
|
432
|
-
pageSize
|
|
433
|
-
};
|
|
434
|
-
}
|
|
435
|
-
setTranslator(translator) {
|
|
436
|
-
this.translator = translator;
|
|
437
|
-
}
|
|
438
|
-
async setIndexTemplate(template) {
|
|
439
|
-
try {
|
|
440
|
-
await this.elasticSearchClientWrapper.putIndexTemplate(template);
|
|
441
|
-
this.logger.info("Custom index template set");
|
|
442
|
-
} catch (error) {
|
|
443
|
-
this.logger.error(`Unable to set custom index template: ${error}`);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
async getIndexer(type) {
|
|
447
|
-
const alias = this.constructSearchAlias(type);
|
|
448
|
-
const indexerLogger = this.logger.child({ documentType: type });
|
|
449
|
-
const indexer = new ElasticSearchSearchEngineIndexer({
|
|
450
|
-
type,
|
|
451
|
-
indexPrefix: this.indexPrefix,
|
|
452
|
-
indexSeparator: this.indexSeparator,
|
|
453
|
-
alias,
|
|
454
|
-
elasticSearchClientWrapper: this.elasticSearchClientWrapper,
|
|
455
|
-
logger: indexerLogger,
|
|
456
|
-
batchSize: this.batchSize,
|
|
457
|
-
skipRefresh: this.elasticSearchClientOptions?.service === "aoss"
|
|
458
|
-
});
|
|
459
|
-
indexer.on("error", async (e) => {
|
|
460
|
-
indexerLogger.error(`Failed to index documents for type ${type}`, e);
|
|
461
|
-
let cleanupError;
|
|
462
|
-
await new Promise(async (done) => {
|
|
463
|
-
const maxAttempts = 5;
|
|
464
|
-
let attempts = 0;
|
|
465
|
-
while (attempts < maxAttempts) {
|
|
466
|
-
try {
|
|
467
|
-
await this.elasticSearchClientWrapper.deleteIndex({
|
|
468
|
-
index: indexer.indexName
|
|
469
|
-
});
|
|
470
|
-
attempts = maxAttempts;
|
|
471
|
-
cleanupError = void 0;
|
|
472
|
-
done();
|
|
473
|
-
} catch (err) {
|
|
474
|
-
cleanupError = err;
|
|
475
|
-
}
|
|
476
|
-
await new Promise((okay) => setTimeout(okay, 1e3));
|
|
477
|
-
attempts++;
|
|
478
|
-
}
|
|
479
|
-
done();
|
|
480
|
-
});
|
|
481
|
-
if (cleanupError) {
|
|
482
|
-
indexerLogger.error(
|
|
483
|
-
`Unable to clean up elastic index ${indexer.indexName}: ${cleanupError}`
|
|
484
|
-
);
|
|
485
|
-
} else {
|
|
486
|
-
indexerLogger.info(
|
|
487
|
-
`Removed partial, failed index ${indexer.indexName}`
|
|
488
|
-
);
|
|
489
|
-
}
|
|
490
|
-
});
|
|
491
|
-
return indexer;
|
|
492
|
-
}
|
|
493
|
-
async query(query) {
|
|
494
|
-
const { elasticSearchQuery, documentTypes, pageSize } = this.translator(
|
|
495
|
-
query,
|
|
496
|
-
{ highlightOptions: this.highlightOptions }
|
|
497
|
-
);
|
|
498
|
-
const queryIndices = documentTypes ? documentTypes.map((it) => this.constructSearchAlias(it)) : this.constructSearchAlias("*");
|
|
499
|
-
try {
|
|
500
|
-
const result = await this.elasticSearchClientWrapper.search({
|
|
501
|
-
index: queryIndices,
|
|
502
|
-
body: elasticSearchQuery
|
|
503
|
-
});
|
|
504
|
-
const { page } = decodePageCursor(query.pageCursor);
|
|
505
|
-
const hasNextPage = result.body.hits.total.value > (page + 1) * pageSize;
|
|
506
|
-
const hasPreviousPage = page > 0;
|
|
507
|
-
const nextPageCursor = hasNextPage ? encodePageCursor({ page: page + 1 }) : void 0;
|
|
508
|
-
const previousPageCursor = hasPreviousPage ? encodePageCursor({ page: page - 1 }) : void 0;
|
|
509
|
-
return {
|
|
510
|
-
results: result.body.hits.hits.map(
|
|
511
|
-
(d, index) => {
|
|
512
|
-
const resultItem = {
|
|
513
|
-
type: this.getTypeFromIndex(d._index),
|
|
514
|
-
document: d._source,
|
|
515
|
-
rank: pageSize * page + index + 1
|
|
516
|
-
};
|
|
517
|
-
if (d.highlight) {
|
|
518
|
-
resultItem.highlight = {
|
|
519
|
-
preTag: this.highlightOptions.preTag,
|
|
520
|
-
postTag: this.highlightOptions.postTag,
|
|
521
|
-
fields: Object.fromEntries(
|
|
522
|
-
Object.entries(d.highlight).map(([field, fragments]) => [
|
|
523
|
-
field,
|
|
524
|
-
fragments.join(this.highlightOptions.fragmentDelimiter)
|
|
525
|
-
])
|
|
526
|
-
)
|
|
527
|
-
};
|
|
528
|
-
}
|
|
529
|
-
return resultItem;
|
|
530
|
-
}
|
|
531
|
-
),
|
|
532
|
-
nextPageCursor,
|
|
533
|
-
previousPageCursor,
|
|
534
|
-
numberOfResults: result.body.hits.total.value
|
|
535
|
-
};
|
|
536
|
-
} catch (error) {
|
|
537
|
-
if (error.meta?.body?.error?.type === "index_not_found_exception") {
|
|
538
|
-
throw new pluginSearchBackendNode.MissingIndexError(
|
|
539
|
-
`Missing index for ${queryIndices}. This means there are no documents to search through.`,
|
|
540
|
-
error
|
|
541
|
-
);
|
|
542
|
-
}
|
|
543
|
-
this.logger.error(
|
|
544
|
-
`Failed to query documents for indices ${queryIndices}`,
|
|
545
|
-
error
|
|
546
|
-
);
|
|
547
|
-
return Promise.reject({ results: [] });
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
indexSeparator = "-index__";
|
|
551
|
-
getTypeFromIndex(index) {
|
|
552
|
-
return index.substring(this.indexPrefix.length).split(this.indexSeparator)[0];
|
|
553
|
-
}
|
|
554
|
-
constructSearchAlias(type) {
|
|
555
|
-
const postFix = this.aliasPostfix ? `__${this.aliasPostfix}` : "";
|
|
556
|
-
return `${this.indexPrefix}${type}${postFix}`;
|
|
557
|
-
}
|
|
558
|
-
static async createElasticSearchClientOptions(credentialProvider, config) {
|
|
559
|
-
if (!config) {
|
|
560
|
-
throw new Error("No elastic search config found");
|
|
561
|
-
}
|
|
562
|
-
const clientOptionsConfig = config.getOptionalConfig("clientOptions");
|
|
563
|
-
const sslConfig = clientOptionsConfig?.getOptionalConfig("ssl");
|
|
564
|
-
if (config.getOptionalString("provider") === "elastic") {
|
|
565
|
-
const authConfig2 = config.getConfig("auth");
|
|
566
|
-
return {
|
|
567
|
-
provider: "elastic",
|
|
568
|
-
cloud: {
|
|
569
|
-
id: config.getString("cloudId")
|
|
570
|
-
},
|
|
571
|
-
auth: {
|
|
572
|
-
username: authConfig2.getString("username"),
|
|
573
|
-
password: authConfig2.getString("password")
|
|
574
|
-
},
|
|
575
|
-
...sslConfig ? {
|
|
576
|
-
ssl: {
|
|
577
|
-
rejectUnauthorized: sslConfig?.getOptionalBoolean("rejectUnauthorized")
|
|
578
|
-
}
|
|
579
|
-
} : {}
|
|
580
|
-
};
|
|
581
|
-
}
|
|
582
|
-
if (config.getOptionalString("provider") === "aws") {
|
|
583
|
-
const requestSigner = new aws4.RequestSigner(config.getString("node"));
|
|
584
|
-
const service = config.getOptionalString("service") ?? requestSigner.service;
|
|
585
|
-
if (service !== "es" && service !== "aoss")
|
|
586
|
-
throw new Error(`Unrecognized serivce type: ${service}`);
|
|
587
|
-
return {
|
|
588
|
-
provider: "aws",
|
|
589
|
-
node: config.getString("node"),
|
|
590
|
-
region: config.getOptionalString("region"),
|
|
591
|
-
service,
|
|
592
|
-
...sslConfig ? {
|
|
593
|
-
ssl: {
|
|
594
|
-
rejectUnauthorized: sslConfig?.getOptionalBoolean("rejectUnauthorized")
|
|
595
|
-
}
|
|
596
|
-
} : {},
|
|
597
|
-
...aws.AwsSigv4Signer({
|
|
598
|
-
region: config.getOptionalString("region") ?? requestSigner.region,
|
|
599
|
-
// for backwards compatibility
|
|
600
|
-
service,
|
|
601
|
-
getCredentials: async () => await credentialProvider.sdkCredentialProvider()
|
|
602
|
-
})
|
|
603
|
-
};
|
|
604
|
-
}
|
|
605
|
-
if (config.getOptionalString("provider") === "opensearch") {
|
|
606
|
-
const authConfig2 = config.getConfig("auth");
|
|
607
|
-
return {
|
|
608
|
-
provider: "opensearch",
|
|
609
|
-
node: config.getString("node"),
|
|
610
|
-
auth: {
|
|
611
|
-
username: authConfig2.getString("username"),
|
|
612
|
-
password: authConfig2.getString("password")
|
|
613
|
-
},
|
|
614
|
-
...sslConfig ? {
|
|
615
|
-
ssl: {
|
|
616
|
-
rejectUnauthorized: sslConfig?.getOptionalBoolean("rejectUnauthorized")
|
|
617
|
-
}
|
|
618
|
-
} : {}
|
|
619
|
-
};
|
|
620
|
-
}
|
|
621
|
-
const authConfig = config.getOptionalConfig("auth");
|
|
622
|
-
const auth = authConfig && (authConfig.has("apiKey") ? {
|
|
623
|
-
apiKey: authConfig.getString("apiKey")
|
|
624
|
-
} : {
|
|
625
|
-
username: authConfig.getString("username"),
|
|
626
|
-
password: authConfig.getString("password")
|
|
627
|
-
});
|
|
628
|
-
return {
|
|
629
|
-
node: config.getString("node"),
|
|
630
|
-
auth,
|
|
631
|
-
...sslConfig ? {
|
|
632
|
-
ssl: {
|
|
633
|
-
rejectUnauthorized: sslConfig?.getOptionalBoolean("rejectUnauthorized")
|
|
634
|
-
}
|
|
635
|
-
} : {}
|
|
636
|
-
};
|
|
637
|
-
}
|
|
638
|
-
static readIndexTemplateConfig(config) {
|
|
639
|
-
return config.getOptionalConfigArray("indexTemplates")?.map((templateConfig) => {
|
|
640
|
-
const bodyConfig = templateConfig.getConfig("body");
|
|
641
|
-
return {
|
|
642
|
-
name: templateConfig.getString("name"),
|
|
643
|
-
body: {
|
|
644
|
-
index_patterns: bodyConfig.getStringArray("index_patterns"),
|
|
645
|
-
composed_of: bodyConfig.getOptionalStringArray("composed_of"),
|
|
646
|
-
template: bodyConfig.getOptionalConfig("template")?.get()
|
|
647
|
-
}
|
|
648
|
-
};
|
|
649
|
-
}) ?? [];
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
function decodePageCursor(pageCursor) {
|
|
653
|
-
if (!pageCursor) {
|
|
654
|
-
return { page: 0 };
|
|
655
|
-
}
|
|
656
|
-
return {
|
|
657
|
-
page: Number(Buffer.from(pageCursor, "base64").toString("utf-8"))
|
|
658
|
-
};
|
|
659
|
-
}
|
|
660
|
-
function encodePageCursor({ page }) {
|
|
661
|
-
return Buffer.from(`${page}`, "utf-8").toString("base64");
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
exports.ElasticSearchSearchEngine = ElasticSearchSearchEngine;
|
|
665
|
-
exports.decodeElasticSearchPageCursor = decodePageCursor;
|
|
666
|
-
exports.isOpenSearchCompatible = isOpenSearchCompatible;
|
|
11
|
+
exports.default = module$1.default;
|
|
12
|
+
exports.elasticsearchTranslatorExtensionPoint = module$1.elasticsearchTranslatorExtensionPoint;
|
|
13
|
+
exports.ElasticSearchSearchEngine = ElasticSearchSearchEngine.ElasticSearchSearchEngine;
|
|
14
|
+
exports.decodeElasticSearchPageCursor = ElasticSearchSearchEngine.decodePageCursor;
|
|
15
|
+
exports.isOpenSearchCompatible = ElasticSearchClientOptions.isOpenSearchCompatible;
|
|
667
16
|
//# sourceMappingURL=index.cjs.js.map
|