@sugarcube/plugin-elasticsearch
Version:
Use [Elasticsearch](https://www.elastic.co/products/elasticsearch) for SugarCube data.
256 lines (176 loc) • 8.9 kB
Markdown
# `@sugarcube/plugin-elasticsearch`
Use [Elasticsearch](https://www.elastic.co/products/elasticsearch) for
SugarCube data.
## Installation
```shell
npm install --save @sugarcube/plugin-elasticsearch
```
## Plugins
### `elastic_export`
Store data of the current envelope in Elasticsearch.
**Configuration Options:**
- `elastic.host`: Set the hostname of the Elasticsearch server. Defaults to `localhost`.
- `elastic.port`: Set the port of the Elasticsearch server. Defaults to `9200`.
- `elastic.index`: Define the prefix to be used for index names. Defaults to `sugarcube`.
- `elastic.omit_fields`: Omit those fields from being stored in Elasticsearch. Define multiple fields by separating them with a comma.
- `elastic.mappings`: Supply a path to a JSON file that contains custom mapping definitions. Those custom mappings are merged into the default mappings, which can be found in [`./src/mappings.js`](./src/mappings.js).
**Example:**
```shell
sugarcube -Q ddg_search:Keith\ Johnstone -p ddg_search,elastic_export
```
To use custom mapping, write your mappings in a JSON file:
```json
{
"cities": {"type": "nested"}
}
```
```shell
sugarcube -Q ddg_search:Keith\ Johnstone \
-p ddg_search,elastic_export \
--elastic.index dancers \
--elastic.mappings mappings.json
```
Indices are created the first time an export happens. In order to change the mappings of an existing index see [this](https://www.elastic.co/blog/changing-mapping-with-zero-downtime), [this](https://www.elastic.co/blog/reindex-is-coming) and [this](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html).
**Metrics:**
- `total`: The total number of units exported.
- `new`: The number of new units indexed.
- `existing`: The number of existing units updated.
- `fail`: The number of units that failed to export.
### `elastic_import`
Search for units in Elasticsearch and import them into the pipeline. Search bodies can be defined in JSON files and imported using the `glob_pattern` query type or supplied directly using the `elastic_query` source type. All search bodies are regular Elasticsearch [request bodies](https://www.elastic.co/guide/en/elasticsearch/reference/6.2/search-request-body.html) and use the [Elasticsearch query DSL](https://www.elastic.co/guide/en/elasticsearch/reference/6.2/query-dsl.html). See [`queries.md`](./queries.md) for more examples.
**Configuration Options:**
- `elastic.host`: Set the hostname of the Elasticsearch server. Defaults to `localhost`.
- `elastic.port`: Set the port of the Elasticsearch server. Defaults to `9200`.
- `elastic.index`: Define the prefix to be used for index names. Defaults to `sugarcube`.
- `elastic.amount`: Set the maximum numbers of units to import. Defaults to `1000`.
- `elastic.include_fields`: Only fetch the fields specified. Defaults to fetch all fields. `_sc_id_hash` and `_sc_content_hash` are always fetched and don't need to be specified when using this option.
**Example:**
The following request body selects all units fetched in 2018. Place it in a file named `2018.json`.
```json
{
"query": {
"range" : {
"$sc_pubdates.fetch" : {
"gte" : "2018-01-01",
"lt" : "2018-12-31",
"format": "YYYY-MM-dd"
}
}
}
}
```
To retrieve units based on this query and export them to a CSV file run the following pipeline:
```shell
sugarcube -Q glob_pattern:2018.json -p elastic_import,csv_export
```
Let's say I have two types of lists, one are keywords, and the other one are city names. The following shell script iterates over two arrays, and calls a pipeline for every possible combination of keywords/cities:
```shell
#!/bin/bash
declare -a cities=("aberdeen" "sheffield");
declare -a keywords=("qlikview" "expedian");
for i in "${cities[@]}"
do
for j in "${keywords[@]}"
do
sugarcube -Q elastic_query:'{"query": {"bool": {"must": [{"match": {"href_text": "'"$i"'"}},{"match": {"href_text": "'"$j"'"}}]}}}' -p elastic_import --csv.filename $i-$j.csv --elastic.index my-index
done
done
```
I can then call this script like that:
```shell
./pipelines.sh
```
**Metrics:**
- `total`: The total number of units imported.
### `elastic_complement`
Complement data in the pipeline with existing data stored in Elasticsearch. This merges new data into existing data.
**Configuration Options:**
- `elastic.host`: Set the hostname of the Elasticsearch server. Defaults to `localhost`.
- `elastic.port`: Set the port of the Elasticsearch server. Defaults to `9200`.
- `elastic.index`: Define the prefix to be used for index names. Defaults to `sugarcube`.
**Example:**
Search DuckDuckGo and update it with date already stored in Elasticsearch before importing the updated version.
```shell
sugarcube -Q ddg_search:Keith\ Johnstone \
-p ddg_search,elastic_complement,elastic_export
```
**Metrics:**
- `new`: The number of new units that weren't complemented.
- `existing`: The number of units that were complemented.
### `elastic_complement_left`
Complement data in the pipeline with existing data stored in Elasticsearch. This merges existing data into new data.
**Configuration Options:**
- `elastic.host`: Set the hostname of the Elasticsearch server. Defaults to `localhost`.
- `elastic.port`: Set the port of the Elasticsearch server. Defaults to `9200`.
- `elastic.index`: Define the prefix to be used for index names. Defaults to `sugarcube`.
**Example:**
Search DuckDuckGo and update any data stored in Elasticsearch with the new data before exporting it again to Elasticsearch.
```shell
sugarcube -Q ddg_search:Keith\ Johnstone \
-p ddg_search,elastic_complement_left,elastic_export
```
**Metrics:**
- `new`: The number of new units that weren't complemented.
- `existing`: The number of units that were complemented.
### `elastic_supplement`
This is an alias for the `elastic_complement_left` plugin.
## Indexes
This plugin will create all indexes with custom mappings when they don't yet exist. Using the `elastic.index` option it is possible to specify a prefix for the indexes scheme. The following indexes are created with custom mappings:
- `<prefix>-web-searches`: Store all units that originate from a DuckDuckGo Or Google search.
- `<prefix>-feed`: Store all units that originate from an Facebook or Twitter feed, a Twitter search or a YouTube channel.
- `<prefix>-units`: Any unit that doesn't go into one of the above indexes is stored in this catchall index.
## API
This module exports the abstraction layer that is used to communicate with Elasticsearch. It can be used to write custom scripts using the same API.
### `Elastic.Do`
```hs
Do :: (G: Generator, {host: String, port: Number, mappings: {}}): [Array, Array]
```
The `Do` function creates a context, in which a full interaction with Elasticsearch takes place. It takes a generator function that forms the interaction context and an configuration object containing the host and port of the Elasticsearch server. Additionally it accepts an object containing custom mappings. The `Do` context returns a tuple containing any results and the history of the interaction with Elasticsearch. The generator function receives a configured API as it's argument. This API is valid within a single interaction context:
```js
const [results, history] = await Elastic.Do(function* ({queryByIds}) {
yield queryByIds("sugarcube", ["id1", "id2"]);
}, {host: "localhost", port: 9200});
history.forEach(([k, meta]) => console.log(`${k}: ${JSON.stringify(meta)}.`));
// Do something with the results.
```
Every `Do` context receives the following API to Elasticsearch:
#### `query`
```hs
query :: (index: String, body: Object, amount: Number): Array
```
Search Elasticsearch using a request body in the format of the Elasticsearch Query DSL.
```js
Elastic.Do(function* fetchTenDocuments({query}) {
const body = {
query: {
href_text: "search me",
},
};
yield query("sugarcube", body, 10);
});
```
`query` returns an array containing any fetched documents.
#### `queryByIds`
```hs
queryByIds :: (index: String, ids: Array): Array
```
Fetch documents by their ID. It returns an array of any document fetched from Elasticsearch.
```js
Elastic.Do(function* fetchUnits({queryByIds}) {
yield queryByIds("sugarcube", [1, 2, 3, 4]);
});
```
#### `bulk`
```hs
bulk :: (index: String, ops: Object): Array
```
Run a bulk operation. The `ops` object contains all the units for the different bulk operations. Currently units can only be indexed. The `bulk` operation returns an array containing any errors that occured.
```js
Elastic.Do(function* bulkIndex({bulk}) {
const units = envelope.data;
const errors = yield bulk("sugarcube", {index: units});
if (errors.length > 0) { // ... deal with errrors }
});
```
## License
[GPL3](./LICENSE) @ [Christo](christo@cryptodrunks.net)