@vegan-friendly/strapi-plugin-elasticsearch 0.0.8
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/.vscode/settings.json +3 -0
- package/LICENSE +21 -0
- package/README.md +266 -0
- package/admin/src/components/Initializer/index.js +26 -0
- package/admin/src/components/PluginIcon/index.js +12 -0
- package/admin/src/components/SubNavigation/index.js +48 -0
- package/admin/src/index.js +63 -0
- package/admin/src/pages/App/index.js +29 -0
- package/admin/src/pages/ConfigureCollection/index.js +225 -0
- package/admin/src/pages/ConfigureCollectionList/index.js +266 -0
- package/admin/src/pages/Homepage/index.js +168 -0
- package/admin/src/pages/ViewIndexingRunLog/index.js +124 -0
- package/admin/src/pluginId.js +5 -0
- package/admin/src/translations/en.json +1 -0
- package/admin/src/translations/fr.json +1 -0
- package/admin/src/utils/apiUrls.js +14 -0
- package/admin/src/utils/axiosInstance.js +40 -0
- package/admin/src/utils/getTrad.js +5 -0
- package/package.json +40 -0
- package/server/bootstrap.js +142 -0
- package/server/config/index.js +6 -0
- package/server/content-types/index.js +9 -0
- package/server/content-types/indexing-logs.js +35 -0
- package/server/content-types/name-prefix.js +0 -0
- package/server/content-types/tasks.js +52 -0
- package/server/controllers/configure-indexing.js +66 -0
- package/server/controllers/index.js +15 -0
- package/server/controllers/log-indexing.js +11 -0
- package/server/controllers/perform-indexing.js +28 -0
- package/server/controllers/perform-search.js +31 -0
- package/server/controllers/setup-info.js +14 -0
- package/server/destroy.js +5 -0
- package/server/index.js +25 -0
- package/server/middlewares/index.js +3 -0
- package/server/policies/index.js +3 -0
- package/server/register.js +5 -0
- package/server/routes/configure-indexing.js +42 -0
- package/server/routes/index.js +13 -0
- package/server/routes/perform-indexing.js +24 -0
- package/server/routes/perform-search.js +14 -0
- package/server/routes/run-log.js +12 -0
- package/server/routes/setup-info.js +12 -0
- package/server/services/configure-indexing.js +184 -0
- package/server/services/es-interface.js +187 -0
- package/server/services/helper.js +305 -0
- package/server/services/index.js +19 -0
- package/server/services/log-indexing.js +26 -0
- package/server/services/perform-indexing.js +173 -0
- package/server/services/schedule-indexing.js +65 -0
- package/server/services/transform-content.js +22 -0
- package/strapi-admin.js +3 -0
- package/strapi-server.js +3 -0
package/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2023 Punit Sethi
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
@@ -0,0 +1,266 @@
|
|
1
|
+
# Strapi plugin strapi-plugin-elasticsearch
|
2
|
+
|
3
|
+
A plugin to enable integrating Elasticsearch with Strapi CMS.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
via npm:
|
8
|
+
|
9
|
+
```
|
10
|
+
npm i @geeky-biz/strapi-plugin-elasticsearch
|
11
|
+
```
|
12
|
+
|
13
|
+
via yarn:
|
14
|
+
|
15
|
+
```
|
16
|
+
yarn add @geeky-biz/strapi-plugin-elasticsearch
|
17
|
+
```
|
18
|
+
|
19
|
+
## Plugin Configuration
|
20
|
+
|
21
|
+
Within your Strapi project's `config/plugin.js`, enable the plugin and provide the configuration details:
|
22
|
+
|
23
|
+
```
|
24
|
+
module.exports = {
|
25
|
+
// ...
|
26
|
+
'elasticsearch': {
|
27
|
+
enabled: true,
|
28
|
+
config: {
|
29
|
+
indexingCronSchedule: "<cron schedule>",
|
30
|
+
searchConnector: {
|
31
|
+
host: "<hostname for Elasticsearch>",
|
32
|
+
username: "<username for Elasticsearch>",
|
33
|
+
password: "<password for Elasticsearch>",
|
34
|
+
certificate: "<path to the certificate required to connect to Elasticsearch>"
|
35
|
+
},
|
36
|
+
indexAliasName: "<Alias name for the Elasticsearch index>"
|
37
|
+
}
|
38
|
+
},
|
39
|
+
// ...
|
40
|
+
}
|
41
|
+
```
|
42
|
+
|
43
|
+
Example plugin configuration (with adequate `.env` variables set up):
|
44
|
+
```
|
45
|
+
module.exports = {
|
46
|
+
// ...
|
47
|
+
'elasticsearch': {
|
48
|
+
enabled: true,
|
49
|
+
config: {
|
50
|
+
indexingCronSchedule: "00 23 * * *", //run daily at 11:00 PM
|
51
|
+
searchConnector: {
|
52
|
+
host: process.env.ELASTIC_HOST,
|
53
|
+
username: process.env.ELASTIC_USERNAME,
|
54
|
+
password: process.env.ELASTIC_PASSWORD,
|
55
|
+
certificate: path.join(__dirname, process.env.ELASTIC_CERT_NAME)
|
56
|
+
},
|
57
|
+
indexAliasName: process.env.ELASTIC_ALIAS_NAME
|
58
|
+
}
|
59
|
+
},
|
60
|
+
// ...
|
61
|
+
}
|
62
|
+
```
|
63
|
+
## Ensuring connection to Elasticsearch
|
64
|
+
When connected to Elasticsearch, the `Connected` field within the `Setup Information` screen shall display `true`.
|
65
|
+
|
66
|
+

|
67
|
+
|
68
|
+
## Configuring collections & attributes to be indexed
|
69
|
+
The `Configure Collections` view displays the collections and the fields setup to be indexed.
|
70
|
+
|
71
|
+

|
72
|
+
|
73
|
+
From this view, individual collection can be selected to modify configuration:
|
74
|
+
|
75
|
+

|
76
|
+
|
77
|
+
## Configuring indexing for dynamic zone or component attributes
|
78
|
+
To enable indexing content for attributes of type `component` or `dynamiczone`, additional information needs to be provided via JSON in the following format:
|
79
|
+
|
80
|
+
```
|
81
|
+
{
|
82
|
+
"subfields": [
|
83
|
+
{
|
84
|
+
"component": "<component name within schema.json>",
|
85
|
+
"field": "<field name from within that component>"
|
86
|
+
},
|
87
|
+
{...},
|
88
|
+
{...}
|
89
|
+
]
|
90
|
+
}
|
91
|
+
```
|
92
|
+
### Example 1:
|
93
|
+
If we have an attribute called `seo_details` of type `component` like the following within our collection `schema.json`:
|
94
|
+
```
|
95
|
+
"seo_details": {
|
96
|
+
"type": "component",
|
97
|
+
"repeatable": false,
|
98
|
+
"component": "metainfo.seo"
|
99
|
+
},
|
100
|
+
```
|
101
|
+
And, if we seek to index the contents of the `meta_description` field belonging to the component `seo`, our `subfields` configuration should be:
|
102
|
+
```
|
103
|
+
{
|
104
|
+
"subfields": [
|
105
|
+
{
|
106
|
+
"component": "metainfo.seo",
|
107
|
+
"field": "meta_description"
|
108
|
+
}
|
109
|
+
]
|
110
|
+
}
|
111
|
+
```
|
112
|
+

|
113
|
+
|
114
|
+
### Example 2:
|
115
|
+
If we have an attribute called `sections` of type `dynamiczone` within our collection `schema.json`:
|
116
|
+
```
|
117
|
+
"sections": {
|
118
|
+
"type": "dynamiczone",
|
119
|
+
"components": [
|
120
|
+
"content.footer",
|
121
|
+
"content.paragraph",
|
122
|
+
"content.separator",
|
123
|
+
"content.heading"
|
124
|
+
]
|
125
|
+
},
|
126
|
+
...
|
127
|
+
```
|
128
|
+
And, if we seek to index the contents of the fields `title` for `content.heading` and `details` as well as `subtext` for `content.paragraph`, our `subfields` configuration should be:
|
129
|
+
```
|
130
|
+
{
|
131
|
+
"subfields": [
|
132
|
+
{
|
133
|
+
"component": "content.paragraph",
|
134
|
+
"field": "details"
|
135
|
+
},
|
136
|
+
{
|
137
|
+
"component": "content.paragraph",
|
138
|
+
"field": "subtext"
|
139
|
+
},
|
140
|
+
{
|
141
|
+
"component": "content.heading",
|
142
|
+
"field": "title"
|
143
|
+
}
|
144
|
+
]
|
145
|
+
}
|
146
|
+
```
|
147
|
+
The subfields JSON also supports multiple level of nesting:
|
148
|
+
```
|
149
|
+
{
|
150
|
+
"subfields": [
|
151
|
+
{
|
152
|
+
"component": "content.footer",
|
153
|
+
"field": "footer_link",
|
154
|
+
"subfields": [
|
155
|
+
{
|
156
|
+
"component": "content.link",
|
157
|
+
"field": "display_text"
|
158
|
+
}
|
159
|
+
]
|
160
|
+
}
|
161
|
+
]
|
162
|
+
}
|
163
|
+
```
|
164
|
+
Note: Indexing of `relations` attributes isn't yet supported.
|
165
|
+
|
166
|
+
## Exporting and Importing indexing configuration
|
167
|
+
To enable backing up the indexing configuration or transferring it between various environments, these can be Exported / Imported from the `Configure Collections` view.
|
168
|
+
|
169
|
+

|
170
|
+
|
171
|
+
## Scheduling Indexing
|
172
|
+
Once the collection attributes are configured for indexing, any changes to the respective collections & attributes is marked for indexing. The cron job (configured via `indexingCronSchedule`) makes actual indexing requests to the connected Elasticsearch instance.
|
173
|
+
|
174
|
+
- `Trigger Indexing` triggers the cron job immediately to perform the pending indexing tasks without waiting for the next scheduled run.
|
175
|
+
- `Rebuild Indexing` completely rebuilds the index. It may be used if the Elasticsearch appears to be out of sync from the data within Strapi.
|
176
|
+
|
177
|
+

|
178
|
+
|
179
|
+
Whenever a collection is configured for indexing, it may already have data that needs to be indexed. To facilitate indexing of the past data, a collection can be scheduled for indexing in the next cron run from the `Configure Collections` view:
|
180
|
+
|
181
|
+

|
182
|
+
|
183
|
+
## Searching
|
184
|
+
You may directly use the Elasticsearch search API or you may use the Search API exposed by the plugin (at `/api/elasticsearch/search`). The plugin search API is just a wrapper around the Elasticsearch search API that passes the query parameter to the Elasticsearch search API and returns the results coming from Elasticsearch:
|
185
|
+
|
186
|
+
For example, the below API call would result into the Elasticsearch search API being triggered with the query
|
187
|
+
```
|
188
|
+
`/api/elasticsearch/search?query=query%5Bbool%5D%5Bshould%5D%5B0%5D%5Bmatch%5D%5Bcity%5D=atlanta`
|
189
|
+
```
|
190
|
+
would result into the Elasticsearch search API being triggered with query
|
191
|
+
```
|
192
|
+
query[bool][should][0][match][city]=atlanta
|
193
|
+
```
|
194
|
+
The plugin's API would return the response from the Elasticsearch search API.
|
195
|
+
|
196
|
+
Note: To use the `search` API (at `/api/elasticsearch/search`), you will have to provide access via `Settings` -> `Users & Permissions Plugin` -> `Roles` -> (Select adequate role) -> `Elasticsearch` -> `search`.
|
197
|
+
|
198
|
+
### Extending Search API
|
199
|
+
The recommended was to enhance the Search API is to write your own route and controller. Below is an example of how this can be achieved (this example adds pagination capability to the search API):
|
200
|
+
|
201
|
+
- Within your setup, create `src/extensions/elasticsearch/strapi-server.js` with the following contents:
|
202
|
+
|
203
|
+
```
|
204
|
+
const { Client } = require('@elastic/elasticsearch')
|
205
|
+
const qs = require('qs');
|
206
|
+
|
207
|
+
let client = null;
|
208
|
+
|
209
|
+
module.exports = (plugin) => {
|
210
|
+
|
211
|
+
client = new Client({
|
212
|
+
node: plugin.config.searchConnector.host,
|
213
|
+
auth: {
|
214
|
+
username: plugin.config.searchConnector.username,
|
215
|
+
password: plugin.config.searchConnector.password
|
216
|
+
},
|
217
|
+
tls: {
|
218
|
+
ca: plugin.config.searchConnector.certificate,
|
219
|
+
rejectUnauthorized: false
|
220
|
+
}
|
221
|
+
});
|
222
|
+
|
223
|
+
plugin.controllers['performSearch'].enhancedSearch = async (ctx) => {
|
224
|
+
try
|
225
|
+
{
|
226
|
+
const params = qs.parse(ctx.request.query)
|
227
|
+
const query = params.search;
|
228
|
+
const pagesize = params.pagesize;
|
229
|
+
const from = params.from;
|
230
|
+
const result= await client.search({
|
231
|
+
index: plugin.config.indexAliasName,
|
232
|
+
query: { "bool" : { "should" : [ { "match": { "content": "dummy"} } ] } },
|
233
|
+
size: pagesize,
|
234
|
+
from: from
|
235
|
+
});
|
236
|
+
return result;
|
237
|
+
}
|
238
|
+
catch(err)
|
239
|
+
{
|
240
|
+
console.log('Search : elasticClient.enhancedSearch : Error encountered while making a search request to ElasticSearch.')
|
241
|
+
throw err;
|
242
|
+
}
|
243
|
+
}
|
244
|
+
|
245
|
+
plugin.routes['search'].routes.push({
|
246
|
+
method: 'GET',
|
247
|
+
path: '/enhanced-search',
|
248
|
+
handler: 'performSearch.enhancedSearch',
|
249
|
+
});
|
250
|
+
|
251
|
+
|
252
|
+
return plugin;
|
253
|
+
};
|
254
|
+
|
255
|
+
```
|
256
|
+
|
257
|
+
- This will create a new route `/api/elasticsearch/enhanced-search` being served by the function defined above.
|
258
|
+
- You can add / modify the routes and controllers as necessary.
|
259
|
+
|
260
|
+
## Bugs
|
261
|
+
For any bugs, please create an issue [here](https://github.com/geeky-biz/strapi-plugin-elasticsearch/issues).
|
262
|
+
|
263
|
+
## About
|
264
|
+
- This plugin is created by [Punit Sethi](https://punits.dev).
|
265
|
+
- I'm an independent developer working on Strapi migrations, customizations, configuration projects (see [here](https://punits.dev/strapi-customizations/)).
|
266
|
+
- For any Strapi implementation requirement, write to me at `punit@tezify.com`.
|
@@ -0,0 +1,26 @@
|
|
1
|
+
/**
|
2
|
+
*
|
3
|
+
* Initializer
|
4
|
+
*
|
5
|
+
*/
|
6
|
+
|
7
|
+
import { useEffect, useRef } from 'react';
|
8
|
+
import PropTypes from 'prop-types';
|
9
|
+
import pluginId from '../../pluginId';
|
10
|
+
|
11
|
+
const Initializer = ({ setPlugin }) => {
|
12
|
+
const ref = useRef();
|
13
|
+
ref.current = setPlugin;
|
14
|
+
|
15
|
+
useEffect(() => {
|
16
|
+
ref.current(pluginId);
|
17
|
+
}, []);
|
18
|
+
|
19
|
+
return null;
|
20
|
+
};
|
21
|
+
|
22
|
+
Initializer.propTypes = {
|
23
|
+
setPlugin: PropTypes.func.isRequired,
|
24
|
+
};
|
25
|
+
|
26
|
+
export default Initializer;
|
@@ -0,0 +1,48 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { Connector } from '@strapi/icons';
|
3
|
+
import { Box } from '@strapi/design-system';
|
4
|
+
import {
|
5
|
+
SubNav,
|
6
|
+
SubNavHeader,
|
7
|
+
SubNavSection,
|
8
|
+
SubNavSections,
|
9
|
+
SubNavLink,
|
10
|
+
} from '@strapi/design-system/v2';
|
11
|
+
import { NavLink } from 'react-router-dom';
|
12
|
+
import pluginId from "../../pluginId";
|
13
|
+
|
14
|
+
export const SubNavigation = ({activeUrl}) => {
|
15
|
+
const links = [ {
|
16
|
+
id: 1,
|
17
|
+
label : 'Setup Information',
|
18
|
+
icon : Connector,
|
19
|
+
to : `/plugins/${pluginId}/home`,
|
20
|
+
},
|
21
|
+
{
|
22
|
+
id: 2,
|
23
|
+
label : 'Configure Collections',
|
24
|
+
icon : Connector,
|
25
|
+
to : `/plugins/${pluginId}/configure-collections`,
|
26
|
+
},
|
27
|
+
{
|
28
|
+
id: 3,
|
29
|
+
label : 'Indexing Run Logs',
|
30
|
+
icon : Connector,
|
31
|
+
to : `/plugins/${pluginId}/view-indexing-logs`,
|
32
|
+
}];
|
33
|
+
return (<Box style={{
|
34
|
+
height: '100vh'
|
35
|
+
}} background="neutral200">
|
36
|
+
<SubNav ariaLabel="Settings sub nav">
|
37
|
+
<SubNavHeader label="Strapi Elasticsearch" />
|
38
|
+
<SubNavSections>
|
39
|
+
<SubNavSection>
|
40
|
+
{links.map(link => link.icon && <SubNavLink
|
41
|
+
as={NavLink} to={link.to} icon={link.icon} key={link.id} >
|
42
|
+
{link.label}
|
43
|
+
</SubNavLink>)}
|
44
|
+
</SubNavSection>
|
45
|
+
</SubNavSections>
|
46
|
+
</SubNav>
|
47
|
+
</Box>);
|
48
|
+
}
|
@@ -0,0 +1,63 @@
|
|
1
|
+
import { prefixPluginTranslations } from '@strapi/helper-plugin';
|
2
|
+
import pluginPkg from '../../package.json';
|
3
|
+
import pluginId from './pluginId';
|
4
|
+
import Initializer from './components/Initializer';
|
5
|
+
import PluginIcon from './components/PluginIcon';
|
6
|
+
|
7
|
+
const name = pluginPkg.strapi.name;
|
8
|
+
|
9
|
+
export default {
|
10
|
+
register(app) {
|
11
|
+
app.addMenuLink({
|
12
|
+
to: `/plugins/${pluginId}`,
|
13
|
+
icon: PluginIcon,
|
14
|
+
intlLabel: {
|
15
|
+
id: `${pluginId}.plugin.name`,
|
16
|
+
defaultMessage: 'Elasticsearch',
|
17
|
+
},
|
18
|
+
Component: async () => {
|
19
|
+
const component = await import(/* webpackChunkName: "[request]" */ './pages/App');
|
20
|
+
|
21
|
+
return component;
|
22
|
+
},
|
23
|
+
permissions: [
|
24
|
+
// Uncomment to set the permissions of the plugin here
|
25
|
+
// {
|
26
|
+
// action: '', // the action name should be plugin::plugin-name.actionType
|
27
|
+
// subject: null,
|
28
|
+
// },
|
29
|
+
],
|
30
|
+
});
|
31
|
+
app.registerPlugin({
|
32
|
+
id: pluginId,
|
33
|
+
initializer: Initializer,
|
34
|
+
isReady: false,
|
35
|
+
name,
|
36
|
+
});
|
37
|
+
},
|
38
|
+
|
39
|
+
bootstrap(app) {},
|
40
|
+
async registerTrads({ locales }) {
|
41
|
+
const importedTrads = await Promise.all(
|
42
|
+
locales.map((locale) => {
|
43
|
+
return import(
|
44
|
+
/* webpackChunkName: "translation-[request]" */ `./translations/${locale}.json`
|
45
|
+
)
|
46
|
+
.then(({ default: data }) => {
|
47
|
+
return {
|
48
|
+
data: prefixPluginTranslations(data, pluginId),
|
49
|
+
locale,
|
50
|
+
};
|
51
|
+
})
|
52
|
+
.catch(() => {
|
53
|
+
return {
|
54
|
+
data: {},
|
55
|
+
locale,
|
56
|
+
};
|
57
|
+
});
|
58
|
+
})
|
59
|
+
);
|
60
|
+
|
61
|
+
return Promise.resolve(importedTrads);
|
62
|
+
},
|
63
|
+
};
|
@@ -0,0 +1,29 @@
|
|
1
|
+
/**
|
2
|
+
*
|
3
|
+
* This component is the skeleton around the actual pages, and should only
|
4
|
+
* contain code that should be seen on all pages. (e.g. navigation bar)
|
5
|
+
*
|
6
|
+
*/
|
7
|
+
|
8
|
+
import React from 'react';
|
9
|
+
import { Switch, Route, Redirect } from 'react-router-dom';
|
10
|
+
import { AnErrorOccurred } from '@strapi/helper-plugin';
|
11
|
+
import pluginId from '../../pluginId';
|
12
|
+
import ConfigureCollectionList from '../ConfigureCollectionList';
|
13
|
+
import ConfigureCollection from '../ConfigureCollection';
|
14
|
+
import ViewIndexingRunLog from '../ViewIndexingRunLog';
|
15
|
+
import Homepage from '../Homepage';
|
16
|
+
const App = () => {
|
17
|
+
return (
|
18
|
+
<Switch>
|
19
|
+
<Route path={`/plugins/${pluginId}`} render={() => (<Redirect to={`/plugins/${pluginId}/home`} />)} exact />
|
20
|
+
<Route path={`/plugins/${pluginId}/home`} component={Homepage} exact />
|
21
|
+
<Route path={`/plugins/${pluginId}/configure-collections`} component={ConfigureCollectionList} exact />
|
22
|
+
<Route path={`/plugins/${pluginId}/configure-collections/:collectionName`} component={ConfigureCollection} exact />
|
23
|
+
<Route path={`/plugins/${pluginId}/view-indexing-logs`} component={ViewIndexingRunLog} />
|
24
|
+
<Route component={AnErrorOccurred} />
|
25
|
+
</Switch>
|
26
|
+
);
|
27
|
+
};
|
28
|
+
|
29
|
+
export default App;
|