@backstage/plugin-search-backend-module-elasticsearch 0.0.2 → 0.0.6
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 +30 -0
- package/config.d.ts +86 -73
- package/dist/index.cjs.js +63 -27
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +8 -4
- package/package.json +8 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @backstage/plugin-search-backend-module-elasticsearch
|
|
2
2
|
|
|
3
|
+
## 0.0.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- dcd1a0c3f4: Minor improvement to the API reports, by not unpacking arguments directly
|
|
8
|
+
|
|
9
|
+
## 0.0.5
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 36350bf8b3: Pinning version of elastic search client to 7.13.0 to prevent breaking change towards third party ElasticSearch clusters on 7.14.0.
|
|
14
|
+
|
|
15
|
+
## 0.0.4
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- f0c2c81676: Added rejectUnauthorized config option
|
|
20
|
+
|
|
21
|
+
## 0.0.3
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- a13f21cdc: Implement optional `pageCursor` based paging in search.
|
|
26
|
+
|
|
27
|
+
To use paging in your app, add a `<SearchResultPager />` to your
|
|
28
|
+
`SearchPage.tsx`.
|
|
29
|
+
|
|
30
|
+
- Updated dependencies
|
|
31
|
+
- @backstage/search-common@0.2.0
|
|
32
|
+
|
|
3
33
|
## 0.0.2
|
|
4
34
|
|
|
5
35
|
### Patch Changes
|
package/config.d.ts
CHANGED
|
@@ -20,90 +20,103 @@ export interface Config {
|
|
|
20
20
|
/**
|
|
21
21
|
* Options for ElasticSearch
|
|
22
22
|
*/
|
|
23
|
-
elasticsearch?:
|
|
24
|
-
|
|
25
|
-
{
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
elasticsearch?: {
|
|
24
|
+
/** Miscellaneous options for the client */
|
|
25
|
+
clientOptions?: {
|
|
26
|
+
ssl?: {
|
|
28
27
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
28
|
+
* If true the server will reject any connection which is not
|
|
29
|
+
* authorized with the list of supplied CAs.
|
|
30
|
+
* @default true
|
|
31
31
|
*/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
rejectUnauthorized?: boolean;
|
|
33
|
+
};
|
|
34
|
+
} & (
|
|
35
|
+
| {
|
|
36
|
+
// elastic = Elastic.co ElasticSearch provider
|
|
37
|
+
provider: 'elastic';
|
|
36
38
|
|
|
37
39
|
/**
|
|
38
|
-
*
|
|
40
|
+
* Elastic.co CloudID
|
|
41
|
+
* See: https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/client-connecting.html#authentication
|
|
39
42
|
*/
|
|
40
|
-
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
+
cloudId: string;
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
*
|
|
47
|
-
* Authentication is handled using the default AWS credentials provider chain
|
|
48
|
-
*/
|
|
49
|
-
| {
|
|
50
|
-
provider: 'aws';
|
|
45
|
+
auth: {
|
|
46
|
+
username: string;
|
|
51
47
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
48
|
+
/**
|
|
49
|
+
* @visibility secret
|
|
50
|
+
*/
|
|
51
|
+
password: string;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
59
54
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Node configuration.
|
|
69
|
-
* URL/URLS to ElasticSearch node to connect to.
|
|
70
|
-
* Either direct URL like 'https://localhost:9200' or with credentials like 'https://username:password@localhost:9200'
|
|
71
|
-
*/
|
|
72
|
-
node: string | string[];
|
|
55
|
+
/**
|
|
56
|
+
* AWS = Amazon Elasticsearch Service provider
|
|
57
|
+
*
|
|
58
|
+
* Authentication is handled using the default AWS credentials provider chain
|
|
59
|
+
*/
|
|
60
|
+
| {
|
|
61
|
+
provider: 'aws';
|
|
73
62
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Node configuration.
|
|
65
|
+
* URL AWS ES endpoint to connect to.
|
|
66
|
+
* Eg. https://my-es-cluster.eu-west-1.es.amazonaws.com
|
|
67
|
+
*/
|
|
68
|
+
node: string;
|
|
69
|
+
}
|
|
81
70
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Base64 Encoded API key to be used to connect to the cluster.
|
|
90
|
-
* See: https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html
|
|
91
|
-
*
|
|
92
|
-
* @visibility secret
|
|
93
|
-
*/
|
|
94
|
-
apiKey: string;
|
|
95
|
-
};
|
|
96
|
-
/* TODO(kuangp): unsupported until @elastic/elasticsearch@7.14 is released
|
|
71
|
+
/**
|
|
72
|
+
* Standard ElasticSearch
|
|
73
|
+
*
|
|
74
|
+
* Includes self-hosted clusters and others that provide direct connection via an endpoint
|
|
75
|
+
* and authentication method (see possible authentication options below)
|
|
76
|
+
*/
|
|
97
77
|
| {
|
|
78
|
+
/**
|
|
79
|
+
* Node configuration.
|
|
80
|
+
* URL/URLS to ElasticSearch node to connect to.
|
|
81
|
+
* Either direct URL like 'https://localhost:9200' or with credentials like 'https://username:password@localhost:9200'
|
|
82
|
+
*/
|
|
83
|
+
node: string | string[];
|
|
98
84
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Authentication credentials for ElasticSearch
|
|
87
|
+
* If both ApiKey/Bearer token and username+password is provided, tokens take precedence
|
|
88
|
+
*/
|
|
89
|
+
auth?:
|
|
90
|
+
| {
|
|
91
|
+
username: string;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @visibility secret
|
|
95
|
+
*/
|
|
96
|
+
password: string;
|
|
97
|
+
}
|
|
98
|
+
| {
|
|
99
|
+
/**
|
|
100
|
+
* Base64 Encoded API key to be used to connect to the cluster.
|
|
101
|
+
* See: https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html
|
|
102
|
+
*
|
|
103
|
+
* @visibility secret
|
|
104
|
+
*/
|
|
105
|
+
apiKey: string;
|
|
106
|
+
};
|
|
107
|
+
/* TODO(kuangp): unsupported until @elastic/elasticsearch@7.14 is released
|
|
108
|
+
| {
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Bearer authentication token to connect to the cluster.
|
|
112
|
+
* See: https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-service-token.html
|
|
113
|
+
*
|
|
114
|
+
* @visibility secret
|
|
115
|
+
*
|
|
116
|
+
bearer: string;
|
|
117
|
+
};*/
|
|
118
|
+
}
|
|
119
|
+
);
|
|
120
|
+
};
|
|
108
121
|
};
|
|
109
122
|
}
|
package/dist/index.cjs.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
-
var esb = require('elastic-builder');
|
|
6
|
-
var elasticsearch = require('@elastic/elasticsearch');
|
|
7
5
|
var awsEsConnection = require('@acuris/aws-es-connection');
|
|
6
|
+
var elasticsearch = require('@elastic/elasticsearch');
|
|
7
|
+
var esb = require('elastic-builder');
|
|
8
8
|
var lodash = require('lodash');
|
|
9
9
|
|
|
10
10
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
@@ -27,18 +27,21 @@ class ElasticSearchSearchEngine {
|
|
|
27
27
|
this.logger = logger;
|
|
28
28
|
this.indexSeparator = "-index__";
|
|
29
29
|
}
|
|
30
|
-
static async fromConfig({
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
static async fromConfig(options) {
|
|
31
|
+
const {
|
|
32
|
+
logger,
|
|
33
|
+
config,
|
|
34
|
+
aliasPostfix = `search`,
|
|
35
|
+
indexPrefix = ``
|
|
36
|
+
} = options;
|
|
36
37
|
return new ElasticSearchSearchEngine(await ElasticSearchSearchEngine.constructElasticSearchClient(logger, config.getConfig("search.elasticsearch")), aliasPostfix, indexPrefix, logger);
|
|
37
38
|
}
|
|
38
39
|
static async constructElasticSearchClient(logger, config) {
|
|
39
40
|
if (!config) {
|
|
40
41
|
throw new Error("No elastic search config found");
|
|
41
42
|
}
|
|
43
|
+
const clientOptionsConfig = config.getOptionalConfig("clientOptions");
|
|
44
|
+
const sslConfig = clientOptionsConfig == null ? void 0 : clientOptionsConfig.getOptionalConfig("ssl");
|
|
42
45
|
if (config.getOptionalString("provider") === "elastic") {
|
|
43
46
|
logger.info("Initializing Elastic.co ElasticSearch search engine.");
|
|
44
47
|
const authConfig2 = config.getConfig("auth");
|
|
@@ -49,7 +52,12 @@ class ElasticSearchSearchEngine {
|
|
|
49
52
|
auth: {
|
|
50
53
|
username: authConfig2.getString("username"),
|
|
51
54
|
password: authConfig2.getString("password")
|
|
52
|
-
}
|
|
55
|
+
},
|
|
56
|
+
...sslConfig ? {
|
|
57
|
+
ssl: {
|
|
58
|
+
rejectUnauthorized: sslConfig == null ? void 0 : sslConfig.getOptionalBoolean("rejectUnauthorized")
|
|
59
|
+
}
|
|
60
|
+
} : {}
|
|
53
61
|
});
|
|
54
62
|
}
|
|
55
63
|
if (config.getOptionalString("provider") === "aws") {
|
|
@@ -58,7 +66,12 @@ class ElasticSearchSearchEngine {
|
|
|
58
66
|
const AWSConnection = awsEsConnection.createAWSConnection(awsCredentials);
|
|
59
67
|
return new elasticsearch.Client({
|
|
60
68
|
node: config.getString("node"),
|
|
61
|
-
...AWSConnection
|
|
69
|
+
...AWSConnection,
|
|
70
|
+
...sslConfig ? {
|
|
71
|
+
ssl: {
|
|
72
|
+
rejectUnauthorized: sslConfig == null ? void 0 : sslConfig.getOptionalBoolean("rejectUnauthorized")
|
|
73
|
+
}
|
|
74
|
+
} : {}
|
|
62
75
|
});
|
|
63
76
|
}
|
|
64
77
|
logger.info("Initializing ElasticSearch search engine.");
|
|
@@ -71,28 +84,33 @@ class ElasticSearchSearchEngine {
|
|
|
71
84
|
});
|
|
72
85
|
return new elasticsearch.Client({
|
|
73
86
|
node: config.getString("node"),
|
|
74
|
-
auth
|
|
87
|
+
auth,
|
|
88
|
+
...sslConfig ? {
|
|
89
|
+
ssl: {
|
|
90
|
+
rejectUnauthorized: sslConfig == null ? void 0 : sslConfig.getOptionalBoolean("rejectUnauthorized")
|
|
91
|
+
}
|
|
92
|
+
} : {}
|
|
75
93
|
});
|
|
76
94
|
}
|
|
77
|
-
translator({
|
|
78
|
-
term,
|
|
79
|
-
filters = {},
|
|
80
|
-
types
|
|
81
|
-
}) {
|
|
95
|
+
translator(query) {
|
|
96
|
+
const { term, filters = {}, types, pageCursor } = query;
|
|
82
97
|
const filter = Object.entries(filters).filter(([_, value]) => Boolean(value)).map(([key, value]) => {
|
|
83
98
|
if (["string", "number", "boolean"].includes(typeof value)) {
|
|
84
|
-
return esb__default[
|
|
99
|
+
return esb__default["default"].matchQuery(key, value.toString());
|
|
85
100
|
}
|
|
86
101
|
if (Array.isArray(value)) {
|
|
87
|
-
return esb__default[
|
|
102
|
+
return esb__default["default"].boolQuery().should(value.map((it) => esb__default["default"].matchQuery(key, it.toString())));
|
|
88
103
|
}
|
|
89
104
|
this.logger.error("Failed to query, unrecognized filter type", key, value);
|
|
90
105
|
throw new Error("Failed to add filters to query. Unrecognized filter type");
|
|
91
106
|
});
|
|
92
|
-
const
|
|
107
|
+
const esbQuery = isBlank(term) ? esb__default["default"].matchAllQuery() : esb__default["default"].multiMatchQuery(["*"], term).fuzziness("auto").minimumShouldMatch(1);
|
|
108
|
+
const pageSize = 25;
|
|
109
|
+
const { page } = decodePageCursor(pageCursor);
|
|
93
110
|
return {
|
|
94
|
-
elasticSearchQuery: esb__default[
|
|
95
|
-
documentTypes: types
|
|
111
|
+
elasticSearchQuery: esb__default["default"].requestBodySearch().query(esb__default["default"].boolQuery().filter(filter).must([esbQuery])).from(page * pageSize).size(pageSize).toJSON(),
|
|
112
|
+
documentTypes: types,
|
|
113
|
+
pageSize
|
|
96
114
|
};
|
|
97
115
|
}
|
|
98
116
|
setTranslator(translator) {
|
|
@@ -116,7 +134,7 @@ class ElasticSearchSearchEngine {
|
|
|
116
134
|
datasource: documents,
|
|
117
135
|
onDocument() {
|
|
118
136
|
return {
|
|
119
|
-
index: {_index: index}
|
|
137
|
+
index: { _index: index }
|
|
120
138
|
};
|
|
121
139
|
},
|
|
122
140
|
refreshOnCompletion: index
|
|
@@ -125,8 +143,8 @@ class ElasticSearchSearchEngine {
|
|
|
125
143
|
await this.elasticSearchClient.indices.updateAliases({
|
|
126
144
|
body: {
|
|
127
145
|
actions: [
|
|
128
|
-
{remove: {index: this.constructIndexName(type, "*"), alias}},
|
|
129
|
-
{add: {index, alias}}
|
|
146
|
+
{ remove: { index: this.constructIndexName(type, "*"), alias } },
|
|
147
|
+
{ add: { index, alias } }
|
|
130
148
|
]
|
|
131
149
|
}
|
|
132
150
|
});
|
|
@@ -151,22 +169,29 @@ class ElasticSearchSearchEngine {
|
|
|
151
169
|
}
|
|
152
170
|
}
|
|
153
171
|
async query(query) {
|
|
154
|
-
const {elasticSearchQuery, documentTypes} = this.translator(query);
|
|
172
|
+
const { elasticSearchQuery, documentTypes, pageSize } = this.translator(query);
|
|
155
173
|
const queryIndices = documentTypes ? documentTypes.map((it) => this.constructSearchAlias(it)) : this.constructSearchAlias("*");
|
|
156
174
|
try {
|
|
157
175
|
const result = await this.elasticSearchClient.search({
|
|
158
176
|
index: queryIndices,
|
|
159
177
|
body: elasticSearchQuery
|
|
160
178
|
});
|
|
179
|
+
const { page } = decodePageCursor(query.pageCursor);
|
|
180
|
+
const hasNextPage = result.body.hits.total.value > page * pageSize;
|
|
181
|
+
const hasPreviousPage = page > 0;
|
|
182
|
+
const nextPageCursor = hasNextPage ? encodePageCursor({ page: page + 1 }) : void 0;
|
|
183
|
+
const previousPageCursor = hasPreviousPage ? encodePageCursor({ page: page - 1 }) : void 0;
|
|
161
184
|
return {
|
|
162
185
|
results: result.body.hits.hits.map((d) => ({
|
|
163
186
|
type: this.getTypeFromIndex(d._index),
|
|
164
187
|
document: d._source
|
|
165
|
-
}))
|
|
188
|
+
})),
|
|
189
|
+
nextPageCursor,
|
|
190
|
+
previousPageCursor
|
|
166
191
|
};
|
|
167
192
|
} catch (e) {
|
|
168
193
|
this.logger.error(`Failed to query documents for indices ${queryIndices}`, e);
|
|
169
|
-
return Promise.reject({results: []});
|
|
194
|
+
return Promise.reject({ results: [] });
|
|
170
195
|
}
|
|
171
196
|
}
|
|
172
197
|
constructIndexName(type, postFix) {
|
|
@@ -180,6 +205,17 @@ class ElasticSearchSearchEngine {
|
|
|
180
205
|
return `${this.indexPrefix}${type}${postFix}`;
|
|
181
206
|
}
|
|
182
207
|
}
|
|
208
|
+
function decodePageCursor(pageCursor) {
|
|
209
|
+
if (!pageCursor) {
|
|
210
|
+
return { page: 0 };
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
page: Number(Buffer.from(pageCursor, "base64").toString("utf-8"))
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function encodePageCursor({ page }) {
|
|
217
|
+
return Buffer.from(`${page}`, "utf-8").toString("base64");
|
|
218
|
+
}
|
|
183
219
|
|
|
184
220
|
exports.ElasticSearchSearchEngine = ElasticSearchSearchEngine;
|
|
185
221
|
//# sourceMappingURL=index.cjs.js.map
|
package/dist/index.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs.js","sources":["../src/engines/ElasticSearchSearchEngine.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n IndexableDocument,\n SearchQuery,\n SearchResultSet,\n SearchEngine,\n} from '@backstage/search-common';\nimport { Logger } from 'winston';\nimport esb from 'elastic-builder';\nimport { Client } from '@elastic/elasticsearch';\nimport { Config } from '@backstage/config';\nimport {\n createAWSConnection,\n awsGetCredentials,\n} from '@acuris/aws-es-connection';\nimport { isEmpty, isNaN as nan, isNumber } from 'lodash';\n\nexport type ConcreteElasticSearchQuery = {\n documentTypes?: string[];\n elasticSearchQuery: Object;\n};\n\ntype ElasticSearchQueryTranslator = (\n query: SearchQuery,\n) => ConcreteElasticSearchQuery;\n\ntype ElasticSearchOptions = {\n logger: Logger;\n config: Config;\n aliasPostfix?: string;\n indexPrefix?: string;\n};\n\ntype ElasticSearchResult = {\n _index: string;\n _type: string;\n _score: number;\n _source: IndexableDocument;\n};\n\nfunction duration(startTimestamp: [number, number]): string {\n const delta = process.hrtime(startTimestamp);\n const seconds = delta[0] + delta[1] / 1e9;\n return `${seconds.toFixed(1)}s`;\n}\n\nfunction isBlank(str: string) {\n return (isEmpty(str) && !isNumber(str)) || nan(str);\n}\n\nexport class ElasticSearchSearchEngine implements SearchEngine {\n constructor(\n private readonly elasticSearchClient: Client,\n private readonly aliasPostfix: string,\n private readonly indexPrefix: string,\n private readonly logger: Logger,\n ) {}\n\n static async fromConfig({\n logger,\n config,\n aliasPostfix = `search`,\n indexPrefix = ``,\n }: ElasticSearchOptions) {\n return new ElasticSearchSearchEngine(\n await ElasticSearchSearchEngine.constructElasticSearchClient(\n logger,\n config.getConfig('search.elasticsearch'),\n ),\n aliasPostfix,\n indexPrefix,\n logger,\n );\n }\n\n private static async constructElasticSearchClient(\n logger: Logger,\n config?: Config,\n ) {\n if (!config) {\n throw new Error('No elastic search config found');\n }\n\n if (config.getOptionalString('provider') === 'elastic') {\n logger.info('Initializing Elastic.co ElasticSearch search engine.');\n const authConfig = config.getConfig('auth');\n return new Client({\n cloud: {\n id: config.getString('cloudId'),\n },\n auth: {\n username: authConfig.getString('username'),\n password: authConfig.getString('password'),\n },\n });\n }\n if (config.getOptionalString('provider') === 'aws') {\n logger.info('Initializing AWS ElasticSearch search engine.');\n const awsCredentials = await awsGetCredentials();\n const AWSConnection = createAWSConnection(awsCredentials);\n return new Client({\n node: config.getString('node'),\n ...AWSConnection,\n });\n }\n logger.info('Initializing ElasticSearch search engine.');\n const authConfig = config.getOptionalConfig('auth');\n const auth =\n authConfig &&\n (authConfig.has('apiKey')\n ? {\n apiKey: authConfig.getString('apiKey'),\n }\n : {\n username: authConfig.getString('username'),\n password: authConfig.getString('password'),\n });\n return new Client({\n node: config.getString('node'),\n auth,\n });\n }\n\n protected translator({\n term,\n filters = {},\n types,\n }: SearchQuery): ConcreteElasticSearchQuery {\n const filter = Object.entries(filters)\n .filter(([_, value]) => Boolean(value))\n .map(([key, value]: [key: string, value: any]) => {\n if (['string', 'number', 'boolean'].includes(typeof value)) {\n return esb.matchQuery(key, value.toString());\n }\n if (Array.isArray(value)) {\n return esb\n .boolQuery()\n .should(value.map(it => esb.matchQuery(key, it.toString())));\n }\n this.logger.error(\n 'Failed to query, unrecognized filter type',\n key,\n value,\n );\n throw new Error(\n 'Failed to add filters to query. Unrecognized filter type',\n );\n });\n const query = isBlank(term)\n ? esb.matchAllQuery()\n : esb\n .multiMatchQuery(['*'], term)\n .fuzziness('auto')\n .minimumShouldMatch(1);\n\n return {\n elasticSearchQuery: esb\n .requestBodySearch()\n .query(esb.boolQuery().filter(filter).must([query]))\n // TODO: Replace size limit with page cursor after pagination approach decided\n // See: https://github.com/backstage/backstage/issues/6062\n .size(100)\n .toJSON(),\n documentTypes: types,\n };\n }\n\n setTranslator(translator: ElasticSearchQueryTranslator) {\n this.translator = translator;\n }\n\n async index(type: string, documents: IndexableDocument[]): Promise<void> {\n this.logger.info(\n `Started indexing ${documents.length} documents for index ${type}`,\n );\n const startTimestamp = process.hrtime();\n const alias = this.constructSearchAlias(type);\n const index = this.constructIndexName(type, `${Date.now()}`);\n try {\n const aliases = await this.elasticSearchClient.cat.aliases({\n format: 'json',\n name: alias,\n });\n const removableIndices = aliases.body.map(\n (r: Record<string, any>) => r.index,\n );\n\n await this.elasticSearchClient.indices.create({\n index,\n });\n const result = await this.elasticSearchClient.helpers.bulk({\n datasource: documents,\n onDocument() {\n return {\n index: { _index: index },\n };\n },\n refreshOnCompletion: index,\n });\n\n this.logger.info(\n `Indexing completed for index ${type} in ${duration(startTimestamp)}`,\n result,\n );\n await this.elasticSearchClient.indices.updateAliases({\n body: {\n actions: [\n { remove: { index: this.constructIndexName(type, '*'), alias } },\n { add: { index, alias } },\n ],\n },\n });\n\n this.logger.info('Removing stale search indices', removableIndices);\n if (removableIndices.length) {\n await this.elasticSearchClient.indices.delete({\n index: removableIndices,\n });\n }\n } catch (e) {\n this.logger.error(`Failed to index documents for type ${type}`, e);\n const response = await this.elasticSearchClient.indices.exists({\n index,\n });\n const indexCreated = response.body;\n if (indexCreated) {\n this.logger.info(`Removing created index ${index}`);\n await this.elasticSearchClient.indices.delete({\n index,\n });\n }\n }\n }\n\n async query(query: SearchQuery): Promise<SearchResultSet> {\n const { elasticSearchQuery, documentTypes } = this.translator(query);\n const queryIndices = documentTypes\n ? documentTypes.map(it => this.constructSearchAlias(it))\n : this.constructSearchAlias('*');\n try {\n const result = await this.elasticSearchClient.search({\n index: queryIndices,\n body: elasticSearchQuery,\n });\n return {\n results: result.body.hits.hits.map((d: ElasticSearchResult) => ({\n type: this.getTypeFromIndex(d._index),\n document: d._source,\n })),\n };\n } catch (e) {\n this.logger.error(\n `Failed to query documents for indices ${queryIndices}`,\n e,\n );\n return Promise.reject({ results: [] });\n }\n }\n\n private readonly indexSeparator = '-index__';\n\n private constructIndexName(type: string, postFix: string) {\n return `${this.indexPrefix}${type}${this.indexSeparator}${postFix}`;\n }\n\n private getTypeFromIndex(index: string) {\n return index\n .substring(this.indexPrefix.length)\n .split(this.indexSeparator)[0];\n }\n\n private constructSearchAlias(type: string) {\n const postFix = this.aliasPostfix ? `__${this.aliasPostfix}` : '';\n return `${this.indexPrefix}${type}${postFix}`;\n }\n}\n"],"names":["isEmpty","isNumber","nan","Client","awsGetCredentials","createAWSConnection","esb"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":["../src/engines/ElasticSearchSearchEngine.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n awsGetCredentials,\n createAWSConnection,\n} from '@acuris/aws-es-connection';\nimport { Config } from '@backstage/config';\nimport {\n IndexableDocument,\n SearchEngine,\n SearchQuery,\n SearchResultSet,\n} from '@backstage/search-common';\nimport { Client } from '@elastic/elasticsearch';\nimport esb from 'elastic-builder';\nimport { isEmpty, isNaN as nan, isNumber } from 'lodash';\nimport { Logger } from 'winston';\n\nexport type ConcreteElasticSearchQuery = {\n documentTypes?: string[];\n elasticSearchQuery: Object;\n pageSize: number;\n};\n\ntype ElasticSearchQueryTranslator = (\n query: SearchQuery,\n) => ConcreteElasticSearchQuery;\n\ntype ElasticSearchOptions = {\n logger: Logger;\n config: Config;\n aliasPostfix?: string;\n indexPrefix?: string;\n};\n\ntype ElasticSearchResult = {\n _index: string;\n _type: string;\n _score: number;\n _source: IndexableDocument;\n};\n\nfunction duration(startTimestamp: [number, number]): string {\n const delta = process.hrtime(startTimestamp);\n const seconds = delta[0] + delta[1] / 1e9;\n return `${seconds.toFixed(1)}s`;\n}\n\nfunction isBlank(str: string) {\n return (isEmpty(str) && !isNumber(str)) || nan(str);\n}\n\n/**\n * @public\n */\nexport class ElasticSearchSearchEngine implements SearchEngine {\n constructor(\n private readonly elasticSearchClient: Client,\n private readonly aliasPostfix: string,\n private readonly indexPrefix: string,\n private readonly logger: Logger,\n ) {}\n\n static async fromConfig(options: ElasticSearchOptions) {\n const {\n logger,\n config,\n aliasPostfix = `search`,\n indexPrefix = ``,\n } = options;\n\n return new ElasticSearchSearchEngine(\n await ElasticSearchSearchEngine.constructElasticSearchClient(\n logger,\n config.getConfig('search.elasticsearch'),\n ),\n aliasPostfix,\n indexPrefix,\n logger,\n );\n }\n\n private static async constructElasticSearchClient(\n logger: Logger,\n config?: Config,\n ) {\n if (!config) {\n throw new Error('No elastic search config found');\n }\n\n const clientOptionsConfig = config.getOptionalConfig('clientOptions');\n const sslConfig = clientOptionsConfig?.getOptionalConfig('ssl');\n\n if (config.getOptionalString('provider') === 'elastic') {\n logger.info('Initializing Elastic.co ElasticSearch search engine.');\n const authConfig = config.getConfig('auth');\n return new Client({\n cloud: {\n id: config.getString('cloudId'),\n },\n auth: {\n username: authConfig.getString('username'),\n password: authConfig.getString('password'),\n },\n ...(sslConfig\n ? {\n ssl: {\n rejectUnauthorized:\n sslConfig?.getOptionalBoolean('rejectUnauthorized'),\n },\n }\n : {}),\n });\n }\n if (config.getOptionalString('provider') === 'aws') {\n logger.info('Initializing AWS ElasticSearch search engine.');\n const awsCredentials = await awsGetCredentials();\n const AWSConnection = createAWSConnection(awsCredentials);\n return new Client({\n node: config.getString('node'),\n ...AWSConnection,\n ...(sslConfig\n ? {\n ssl: {\n rejectUnauthorized:\n sslConfig?.getOptionalBoolean('rejectUnauthorized'),\n },\n }\n : {}),\n });\n }\n logger.info('Initializing ElasticSearch search engine.');\n const authConfig = config.getOptionalConfig('auth');\n const auth =\n authConfig &&\n (authConfig.has('apiKey')\n ? {\n apiKey: authConfig.getString('apiKey'),\n }\n : {\n username: authConfig.getString('username'),\n password: authConfig.getString('password'),\n });\n return new Client({\n node: config.getString('node'),\n auth,\n ...(sslConfig\n ? {\n ssl: {\n rejectUnauthorized:\n sslConfig?.getOptionalBoolean('rejectUnauthorized'),\n },\n }\n : {}),\n });\n }\n\n protected translator(query: SearchQuery): ConcreteElasticSearchQuery {\n const { term, filters = {}, types, pageCursor } = query;\n\n const filter = Object.entries(filters)\n .filter(([_, value]) => Boolean(value))\n .map(([key, value]: [key: string, value: any]) => {\n if (['string', 'number', 'boolean'].includes(typeof value)) {\n return esb.matchQuery(key, value.toString());\n }\n if (Array.isArray(value)) {\n return esb\n .boolQuery()\n .should(value.map(it => esb.matchQuery(key, it.toString())));\n }\n this.logger.error(\n 'Failed to query, unrecognized filter type',\n key,\n value,\n );\n throw new Error(\n 'Failed to add filters to query. Unrecognized filter type',\n );\n });\n const esbQuery = isBlank(term)\n ? esb.matchAllQuery()\n : esb\n .multiMatchQuery(['*'], term)\n .fuzziness('auto')\n .minimumShouldMatch(1);\n const pageSize = 25;\n const { page } = decodePageCursor(pageCursor);\n\n return {\n elasticSearchQuery: esb\n .requestBodySearch()\n .query(esb.boolQuery().filter(filter).must([esbQuery]))\n .from(page * pageSize)\n .size(pageSize)\n .toJSON(),\n documentTypes: types,\n pageSize,\n };\n }\n\n setTranslator(translator: ElasticSearchQueryTranslator) {\n this.translator = translator;\n }\n\n async index(type: string, documents: IndexableDocument[]): Promise<void> {\n this.logger.info(\n `Started indexing ${documents.length} documents for index ${type}`,\n );\n const startTimestamp = process.hrtime();\n const alias = this.constructSearchAlias(type);\n const index = this.constructIndexName(type, `${Date.now()}`);\n try {\n const aliases = await this.elasticSearchClient.cat.aliases({\n format: 'json',\n name: alias,\n });\n const removableIndices = aliases.body.map(\n (r: Record<string, any>) => r.index,\n );\n\n await this.elasticSearchClient.indices.create({\n index,\n });\n const result = await this.elasticSearchClient.helpers.bulk({\n datasource: documents,\n onDocument() {\n return {\n index: { _index: index },\n };\n },\n refreshOnCompletion: index,\n });\n\n this.logger.info(\n `Indexing completed for index ${type} in ${duration(startTimestamp)}`,\n result,\n );\n await this.elasticSearchClient.indices.updateAliases({\n body: {\n actions: [\n { remove: { index: this.constructIndexName(type, '*'), alias } },\n { add: { index, alias } },\n ],\n },\n });\n\n this.logger.info('Removing stale search indices', removableIndices);\n if (removableIndices.length) {\n await this.elasticSearchClient.indices.delete({\n index: removableIndices,\n });\n }\n } catch (e) {\n this.logger.error(`Failed to index documents for type ${type}`, e);\n const response = await this.elasticSearchClient.indices.exists({\n index,\n });\n const indexCreated = response.body;\n if (indexCreated) {\n this.logger.info(`Removing created index ${index}`);\n await this.elasticSearchClient.indices.delete({\n index,\n });\n }\n }\n }\n\n async query(query: SearchQuery): Promise<SearchResultSet> {\n const { elasticSearchQuery, documentTypes, pageSize } =\n this.translator(query);\n const queryIndices = documentTypes\n ? documentTypes.map(it => this.constructSearchAlias(it))\n : this.constructSearchAlias('*');\n try {\n const result = await this.elasticSearchClient.search({\n index: queryIndices,\n body: elasticSearchQuery,\n });\n const { page } = decodePageCursor(query.pageCursor);\n const hasNextPage = result.body.hits.total.value > page * pageSize;\n const hasPreviousPage = page > 0;\n const nextPageCursor = hasNextPage\n ? encodePageCursor({ page: page + 1 })\n : undefined;\n const previousPageCursor = hasPreviousPage\n ? encodePageCursor({ page: page - 1 })\n : undefined;\n\n return {\n results: result.body.hits.hits.map((d: ElasticSearchResult) => ({\n type: this.getTypeFromIndex(d._index),\n document: d._source,\n })),\n nextPageCursor,\n previousPageCursor,\n };\n } catch (e) {\n this.logger.error(\n `Failed to query documents for indices ${queryIndices}`,\n e,\n );\n return Promise.reject({ results: [] });\n }\n }\n\n private readonly indexSeparator = '-index__';\n\n private constructIndexName(type: string, postFix: string) {\n return `${this.indexPrefix}${type}${this.indexSeparator}${postFix}`;\n }\n\n private getTypeFromIndex(index: string) {\n return index\n .substring(this.indexPrefix.length)\n .split(this.indexSeparator)[0];\n }\n\n private constructSearchAlias(type: string) {\n const postFix = this.aliasPostfix ? `__${this.aliasPostfix}` : '';\n return `${this.indexPrefix}${type}${postFix}`;\n }\n}\n\nexport function decodePageCursor(pageCursor?: string): { page: number } {\n if (!pageCursor) {\n return { page: 0 };\n }\n\n return {\n page: Number(Buffer.from(pageCursor, 'base64').toString('utf-8')),\n };\n}\n\nexport function encodePageCursor({ page }: { page: number }): string {\n return Buffer.from(`${page}`, 'utf-8').toString('base64');\n}\n"],"names":["isEmpty","isNumber","nan","Client","awsGetCredentials","createAWSConnection","esb"],"mappings":";;;;;;;;;;;;;AAwDA,kBAAkB,gBAA0C;AAC1D,QAAM,QAAQ,QAAQ,OAAO;AAC7B,QAAM,UAAU,MAAM,KAAK,MAAM,KAAK;AACtC,SAAO,GAAG,QAAQ,QAAQ;AAAA;AAG5B,iBAAiB,KAAa;AAC5B,SAAQA,eAAQ,QAAQ,CAACC,gBAAS,QAASC,aAAI;AAAA;gCAMc;AAAA,EAC7D,YACmB,qBACA,cACA,aACA,QACjB;AAJiB;AACA;AACA;AACA;AAsPF,0BAAiB;AAAA;AAAA,eAnPrB,WAAW,SAA+B;AACrD,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA,eAAe;AAAA,MACf,cAAc;AAAA,QACZ;AAEJ,WAAO,IAAI,0BACT,MAAM,0BAA0B,6BAC9B,QACA,OAAO,UAAU,0BAEnB,cACA,aACA;AAAA;AAAA,eAIiB,6BACnB,QACA,QACA;AACA,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,MAAM;AAAA;AAGlB,UAAM,sBAAsB,OAAO,kBAAkB;AACrD,UAAM,YAAY,2DAAqB,kBAAkB;AAEzD,QAAI,OAAO,kBAAkB,gBAAgB,WAAW;AACtD,aAAO,KAAK;AACZ,YAAM,cAAa,OAAO,UAAU;AACpC,aAAO,IAAIC,qBAAO;AAAA,QAChB,OAAO;AAAA,UACL,IAAI,OAAO,UAAU;AAAA;AAAA,QAEvB,MAAM;AAAA,UACJ,UAAU,YAAW,UAAU;AAAA,UAC/B,UAAU,YAAW,UAAU;AAAA;AAAA,WAE7B,YACA;AAAA,UACE,KAAK;AAAA,YACH,oBACE,uCAAW,mBAAmB;AAAA;AAAA,YAGpC;AAAA;AAAA;AAGR,QAAI,OAAO,kBAAkB,gBAAgB,OAAO;AAClD,aAAO,KAAK;AACZ,YAAM,iBAAiB,MAAMC;AAC7B,YAAM,gBAAgBC,oCAAoB;AAC1C,aAAO,IAAIF,qBAAO;AAAA,QAChB,MAAM,OAAO,UAAU;AAAA,WACpB;AAAA,WACC,YACA;AAAA,UACE,KAAK;AAAA,YACH,oBACE,uCAAW,mBAAmB;AAAA;AAAA,YAGpC;AAAA;AAAA;AAGR,WAAO,KAAK;AACZ,UAAM,aAAa,OAAO,kBAAkB;AAC5C,UAAM,OACJ,0BACY,IAAI,YACZ;AAAA,MACE,QAAQ,WAAW,UAAU;AAAA,QAE/B;AAAA,MACE,UAAU,WAAW,UAAU;AAAA,MAC/B,UAAU,WAAW,UAAU;AAAA;AAEvC,WAAO,IAAIA,qBAAO;AAAA,MAChB,MAAM,OAAO,UAAU;AAAA,MACvB;AAAA,SACI,YACA;AAAA,QACE,KAAK;AAAA,UACH,oBACE,uCAAW,mBAAmB;AAAA;AAAA,UAGpC;AAAA;AAAA;AAAA,EAIE,WAAW,OAAgD;AACnE,UAAM,EAAE,MAAM,UAAU,IAAI,OAAO,eAAe;AAElD,UAAM,SAAS,OAAO,QAAQ,SAC3B,OAAO,CAAC,CAAC,GAAG,WAAW,QAAQ,QAC/B,IAAI,CAAC,CAAC,KAAK,WAAsC;AAChD,UAAI,CAAC,UAAU,UAAU,WAAW,SAAS,OAAO,QAAQ;AAC1D,eAAOG,wBAAI,WAAW,KAAK,MAAM;AAAA;AAEnC,UAAI,MAAM,QAAQ,QAAQ;AACxB,eAAOA,wBACJ,YACA,OAAO,MAAM,IAAI,QAAMA,wBAAI,WAAW,KAAK,GAAG;AAAA;AAEnD,WAAK,OAAO,MACV,6CACA,KACA;AAEF,YAAM,IAAI,MACR;AAAA;AAGN,UAAM,WAAW,QAAQ,QACrBA,wBAAI,kBACJA,wBACG,gBAAgB,CAAC,MAAM,MACvB,UAAU,QACV,mBAAmB;AAC1B,UAAM,WAAW;AACjB,UAAM,EAAE,SAAS,iBAAiB;AAElC,WAAO;AAAA,MACL,oBAAoBA,wBACjB,oBACA,MAAMA,wBAAI,YAAY,OAAO,QAAQ,KAAK,CAAC,YAC3C,KAAK,OAAO,UACZ,KAAK,UACL;AAAA,MACH,eAAe;AAAA,MACf;AAAA;AAAA;AAAA,EAIJ,cAAc,YAA0C;AACtD,SAAK,aAAa;AAAA;AAAA,QAGd,MAAM,MAAc,WAA+C;AACvE,SAAK,OAAO,KACV,oBAAoB,UAAU,8BAA8B;AAE9D,UAAM,iBAAiB,QAAQ;AAC/B,UAAM,QAAQ,KAAK,qBAAqB;AACxC,UAAM,QAAQ,KAAK,mBAAmB,MAAM,GAAG,KAAK;AACpD,QAAI;AACF,YAAM,UAAU,MAAM,KAAK,oBAAoB,IAAI,QAAQ;AAAA,QACzD,QAAQ;AAAA,QACR,MAAM;AAAA;AAER,YAAM,mBAAmB,QAAQ,KAAK,IACpC,CAAC,MAA2B,EAAE;AAGhC,YAAM,KAAK,oBAAoB,QAAQ,OAAO;AAAA,QAC5C;AAAA;AAEF,YAAM,SAAS,MAAM,KAAK,oBAAoB,QAAQ,KAAK;AAAA,QACzD,YAAY;AAAA,QACZ,aAAa;AACX,iBAAO;AAAA,YACL,OAAO,EAAE,QAAQ;AAAA;AAAA;AAAA,QAGrB,qBAAqB;AAAA;AAGvB,WAAK,OAAO,KACV,gCAAgC,WAAW,SAAS,mBACpD;AAEF,YAAM,KAAK,oBAAoB,QAAQ,cAAc;AAAA,QACnD,MAAM;AAAA,UACJ,SAAS;AAAA,YACP,EAAE,QAAQ,EAAE,OAAO,KAAK,mBAAmB,MAAM,MAAM;AAAA,YACvD,EAAE,KAAK,EAAE,OAAO;AAAA;AAAA;AAAA;AAKtB,WAAK,OAAO,KAAK,iCAAiC;AAClD,UAAI,iBAAiB,QAAQ;AAC3B,cAAM,KAAK,oBAAoB,QAAQ,OAAO;AAAA,UAC5C,OAAO;AAAA;AAAA;AAAA,aAGJ,GAAP;AACA,WAAK,OAAO,MAAM,sCAAsC,QAAQ;AAChE,YAAM,WAAW,MAAM,KAAK,oBAAoB,QAAQ,OAAO;AAAA,QAC7D;AAAA;AAEF,YAAM,eAAe,SAAS;AAC9B,UAAI,cAAc;AAChB,aAAK,OAAO,KAAK,0BAA0B;AAC3C,cAAM,KAAK,oBAAoB,QAAQ,OAAO;AAAA,UAC5C;AAAA;AAAA;AAAA;AAAA;AAAA,QAMF,MAAM,OAA8C;AACxD,UAAM,EAAE,oBAAoB,eAAe,aACzC,KAAK,WAAW;AAClB,UAAM,eAAe,gBACjB,cAAc,IAAI,QAAM,KAAK,qBAAqB,OAClD,KAAK,qBAAqB;AAC9B,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,oBAAoB,OAAO;AAAA,QACnD,OAAO;AAAA,QACP,MAAM;AAAA;AAER,YAAM,EAAE,SAAS,iBAAiB,MAAM;AACxC,YAAM,cAAc,OAAO,KAAK,KAAK,MAAM,QAAQ,OAAO;AAC1D,YAAM,kBAAkB,OAAO;AAC/B,YAAM,iBAAiB,cACnB,iBAAiB,EAAE,MAAM,OAAO,OAChC;AACJ,YAAM,qBAAqB,kBACvB,iBAAiB,EAAE,MAAM,OAAO,OAChC;AAEJ,aAAO;AAAA,QACL,SAAS,OAAO,KAAK,KAAK,KAAK,IAAI,CAAC;AAA4B,UAC9D,MAAM,KAAK,iBAAiB,EAAE;AAAA,UAC9B,UAAU,EAAE;AAAA;AAAA,QAEd;AAAA,QACA;AAAA;AAAA,aAEK,GAAP;AACA,WAAK,OAAO,MACV,yCAAyC,gBACzC;AAEF,aAAO,QAAQ,OAAO,EAAE,SAAS;AAAA;AAAA;AAAA,EAM7B,mBAAmB,MAAc,SAAiB;AACxD,WAAO,GAAG,KAAK,cAAc,OAAO,KAAK,iBAAiB;AAAA;AAAA,EAGpD,iBAAiB,OAAe;AACtC,WAAO,MACJ,UAAU,KAAK,YAAY,QAC3B,MAAM,KAAK,gBAAgB;AAAA;AAAA,EAGxB,qBAAqB,MAAc;AACzC,UAAM,UAAU,KAAK,eAAe,KAAK,KAAK,iBAAiB;AAC/D,WAAO,GAAG,KAAK,cAAc,OAAO;AAAA;AAAA;0BAIP,YAAuC;AACtE,MAAI,CAAC,YAAY;AACf,WAAO,EAAE,MAAM;AAAA;AAGjB,SAAO;AAAA,IACL,MAAM,OAAO,OAAO,KAAK,YAAY,UAAU,SAAS;AAAA;AAAA;0BAI3B,EAAE,QAAkC;AACnE,SAAO,OAAO,KAAK,GAAG,QAAQ,SAAS,SAAS;AAAA;;;;"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
import { Config } from '@backstage/config';
|
|
1
2
|
import { SearchEngine, SearchQuery, IndexableDocument, SearchResultSet } from '@backstage/search-common';
|
|
2
|
-
import { Logger } from 'winston';
|
|
3
3
|
import { Client } from '@elastic/elasticsearch';
|
|
4
|
-
import {
|
|
4
|
+
import { Logger } from 'winston';
|
|
5
5
|
|
|
6
6
|
declare type ConcreteElasticSearchQuery = {
|
|
7
7
|
documentTypes?: string[];
|
|
8
8
|
elasticSearchQuery: Object;
|
|
9
|
+
pageSize: number;
|
|
9
10
|
};
|
|
10
11
|
declare type ElasticSearchQueryTranslator = (query: SearchQuery) => ConcreteElasticSearchQuery;
|
|
11
12
|
declare type ElasticSearchOptions = {
|
|
@@ -14,15 +15,18 @@ declare type ElasticSearchOptions = {
|
|
|
14
15
|
aliasPostfix?: string;
|
|
15
16
|
indexPrefix?: string;
|
|
16
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* @public
|
|
20
|
+
*/
|
|
17
21
|
declare class ElasticSearchSearchEngine implements SearchEngine {
|
|
18
22
|
private readonly elasticSearchClient;
|
|
19
23
|
private readonly aliasPostfix;
|
|
20
24
|
private readonly indexPrefix;
|
|
21
25
|
private readonly logger;
|
|
22
26
|
constructor(elasticSearchClient: Client, aliasPostfix: string, indexPrefix: string, logger: Logger);
|
|
23
|
-
static fromConfig(
|
|
27
|
+
static fromConfig(options: ElasticSearchOptions): Promise<ElasticSearchSearchEngine>;
|
|
24
28
|
private static constructElasticSearchClient;
|
|
25
|
-
protected translator(
|
|
29
|
+
protected translator(query: SearchQuery): ConcreteElasticSearchQuery;
|
|
26
30
|
setTranslator(translator: ElasticSearchQueryTranslator): void;
|
|
27
31
|
index(type: string, documents: IndexableDocument[]): Promise<void>;
|
|
28
32
|
query(query: SearchQuery): Promise<SearchResultSet>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/plugin-search-backend-module-elasticsearch",
|
|
3
|
-
"
|
|
3
|
+
"description": "A module for the search backend that implements search using ElasticSearch",
|
|
4
|
+
"version": "0.0.6",
|
|
4
5
|
"main": "dist/index.cjs.js",
|
|
5
6
|
"types": "dist/index.d.ts",
|
|
6
7
|
"license": "Apache-2.0",
|
|
@@ -20,17 +21,17 @@
|
|
|
20
21
|
},
|
|
21
22
|
"dependencies": {
|
|
22
23
|
"@acuris/aws-es-connection": "^2.2.0",
|
|
23
|
-
"@backstage/config": "^0.1.
|
|
24
|
-
"@backstage/search-common": "^0.
|
|
25
|
-
"@elastic/elasticsearch": "
|
|
24
|
+
"@backstage/config": "^0.1.8",
|
|
25
|
+
"@backstage/search-common": "^0.2.0",
|
|
26
|
+
"@elastic/elasticsearch": "7.13.0",
|
|
26
27
|
"aws-sdk": "^2.948.0",
|
|
27
28
|
"elastic-builder": "^2.16.0",
|
|
28
29
|
"lodash": "^4.17.21",
|
|
29
30
|
"winston": "^3.2.1"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
|
-
"@backstage/backend-common": "^0.
|
|
33
|
-
"@backstage/cli": "^0.
|
|
33
|
+
"@backstage/backend-common": "^0.9.13",
|
|
34
|
+
"@backstage/cli": "^0.10.1",
|
|
34
35
|
"@elastic/elasticsearch-mock": "^0.3.0"
|
|
35
36
|
},
|
|
36
37
|
"files": [
|
|
@@ -41,5 +42,5 @@
|
|
|
41
42
|
"testEnvironment": "node"
|
|
42
43
|
},
|
|
43
44
|
"configSchema": "config.d.ts",
|
|
44
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "562be0b43016294e27af3ad024191bb86b13b1c1"
|
|
45
46
|
}
|