Skip to content

Commit

Permalink
feat(elasticsearch-plugin): Allow full client options to be passed
Browse files Browse the repository at this point in the history
Closes #474
  • Loading branch information
michaelbromley committed Sep 30, 2020
1 parent 2858ebf commit c686509
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 25 deletions.
2 changes: 1 addition & 1 deletion packages/elasticsearch-plugin/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Vendure Elasticsearch Plugin

The `ElasticsearchPlugin` uses Elasticsearch to power the the Vendure product search.
The `ElasticsearchPlugin` uses Elasticsearch to power the Vendure product search.

**Requires Elasticsearch v7.0 or higher.**

Expand Down
20 changes: 12 additions & 8 deletions packages/elasticsearch-plugin/src/elasticsearch.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Client } from '@elastic/elasticsearch';
import { Client, ClientOptions } from '@elastic/elasticsearch';
import { Inject, Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import { SearchResult, SearchResultAsset } from '@vendure/common/lib/generated-types';
import {
Expand Down Expand Up @@ -50,8 +50,12 @@ export class ElasticsearchService implements OnModuleInit, OnModuleDestroy {

onModuleInit(): any {
const { host, port } = this.options;
const node = this.options.clientOptions?.node ?? `${host}:${port}`;
this.client = new Client({
node: `${host}:${port}`,
node,
// `any` cast is there due to a strange error "Property '[Symbol.iterator]' is missing in type... URLSearchParams"
// which looks like possibly a TS/definitions bug.
...(this.options.clientOptions as any),
});
}

Expand Down Expand Up @@ -105,7 +109,7 @@ export class ElasticsearchService implements OnModuleInit, OnModuleDestroy {
body: elasticSearchBody,
});
return {
items: body.hits.hits.map((hit) => this.mapProductToSearchResult(hit)),
items: body.hits.hits.map(hit => this.mapProductToSearchResult(hit)),
totalItems: body.hits.total.value,
};
} else {
Expand All @@ -115,7 +119,7 @@ export class ElasticsearchService implements OnModuleInit, OnModuleDestroy {
body: elasticSearchBody,
});
return {
items: body.hits.hits.map((hit) => this.mapVariantToSearchResult(hit)),
items: body.hits.hits.map(hit => this.mapVariantToSearchResult(hit)),
totalItems: body.hits.total.value,
};
}
Expand Down Expand Up @@ -155,11 +159,11 @@ export class ElasticsearchService implements OnModuleInit, OnModuleDestroy {
const buckets = body.aggregations ? body.aggregations.facetValue.buckets : [];

const facetValues = await this.facetValueService.findByIds(
buckets.map((b) => b.key),
buckets.map(b => b.key),
ctx.languageCode,
);
return facetValues.map((facetValue, index) => {
const bucket = buckets.find((b) => b.key.toString() === facetValue.id.toString());
const bucket = buckets.find(b => b.key.toString() === facetValue.id.toString());
return {
facetValue,
count: bucket ? bucket.doc_count : 0,
Expand Down Expand Up @@ -233,8 +237,8 @@ export class ElasticsearchService implements OnModuleInit, OnModuleDestroy {
min: aggregations.minPriceWithTax.value || 0,
max: aggregations.maxPriceWithTax.value || 0,
},
buckets: aggregations.prices.buckets.map(mapPriceBuckets).filter((x) => 0 < x.count),
bucketsWithTax: aggregations.prices.buckets.map(mapPriceBuckets).filter((x) => 0 < x.count),
buckets: aggregations.prices.buckets.map(mapPriceBuckets).filter(x => 0 < x.count),
bucketsWithTax: aggregations.prices.buckets.map(mapPriceBuckets).filter(x => 0 < x.count),
};
}

Expand Down
35 changes: 27 additions & 8 deletions packages/elasticsearch-plugin/src/options.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ClientOptions } from '@elastic/elasticsearch';
import { DeepRequired, ID, Product, ProductVariant } from '@vendure/core';
import deepmerge from 'deepmerge';

Expand All @@ -13,14 +14,26 @@ import { CustomMapping, ElasticSearchInput } from './types';
export interface ElasticsearchOptions {
/**
* @description
* The host of the Elasticsearch server.
* The host of the Elasticsearch server. May also be specified in `clientOptions.node`.
*
* @default 'http://localhost'
*/
host: string;
host?: string;
/**
* @description
* The port of the Elasticsearch server.
* The port of the Elasticsearch server. May also be specified in `clientOptions.node`.
*
* @default 9200
*/
port: number;
port?: number;
/**
* @description
* Options to pass directly to the
* [Elasticsearch Node.js client](https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html). For example, to
* set authentication or other more advanced options.
* Note that if the `node` or `nodes` option is specified, it will override the values provided in the `host` and `port` options.
*/
clientOptions?: ClientOptions;
/**
* @description
* Prefix for the indices created by the plugin.
Expand Down Expand Up @@ -275,7 +288,11 @@ export interface BoostFieldsConfig {
sku?: number;
}

export const defaultOptions: DeepRequired<ElasticsearchOptions> = {
export type ElasticsearchRuntimeOptions = DeepRequired<Omit<ElasticsearchOptions, 'clientOptions'>> & {
clientOptions?: ClientOptions;
};

export const defaultOptions: ElasticsearchRuntimeOptions = {
host: 'http://localhost',
port: 9200,
indexPrefix: 'vendure-',
Expand All @@ -290,12 +307,14 @@ export const defaultOptions: DeepRequired<ElasticsearchOptions> = {
sku: 1,
},
priceRangeBucketInterval: 1000,
mapQuery: (query) => query,
mapQuery: query => query,
},
customProductMappings: {},
customProductVariantMappings: {},
};

export function mergeWithDefaults(userOptions: ElasticsearchOptions): DeepRequired<ElasticsearchOptions> {
return deepmerge(defaultOptions, userOptions) as DeepRequired<ElasticsearchOptions>;
export function mergeWithDefaults(userOptions: ElasticsearchOptions): ElasticsearchRuntimeOptions {
const { clientOptions, ...pluginOptions } = userOptions;
const merged = deepmerge(defaultOptions, pluginOptions) as ElasticsearchRuntimeOptions;
return { ...merged, clientOptions };
}
44 changes: 36 additions & 8 deletions packages/elasticsearch-plugin/src/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { NodeOptions } from '@elastic/elasticsearch';
import {
AssetEvent,
CollectionModificationEvent,
DeepRequired,
EventBus,
HealthCheckRegistryService,
ID,
Expand All @@ -26,7 +26,7 @@ import { ElasticsearchHealthIndicator } from './elasticsearch.health';
import { ElasticsearchService } from './elasticsearch.service';
import { generateSchemaExtensions } from './graphql-schema-extensions';
import { ElasticsearchIndexerController } from './indexer.controller';
import { ElasticsearchOptions, mergeWithDefaults } from './options';
import { ElasticsearchOptions, ElasticsearchRuntimeOptions, mergeWithDefaults } from './options';

/**
* @description
Expand All @@ -37,11 +37,11 @@ import { ElasticsearchOptions, mergeWithDefaults } from './options';
*
* **Requires Elasticsearch v7.0 or higher.**
*
* `yarn add \@vendure/elasticsearch-plugin`
* `yarn add \@elastic/elasticsearch \@vendure/elasticsearch-plugin`
*
* or
*
* `npm install \@vendure/elasticsearch-plugin`
* `npm install \@elastic/elasticsearch \@vendure/elasticsearch-plugin`
*
* Make sure to remove the `DefaultSearchPlugin` if it is still in the VendureConfig plugins array.
*
Expand Down Expand Up @@ -208,12 +208,14 @@ import { ElasticsearchOptions, mergeWithDefaults } from './options';
? [ShopElasticSearchResolver, CustomMappingsResolver]
: [ShopElasticSearchResolver];
},
schema: () => generateSchemaExtensions(ElasticsearchPlugin.options),
// `any` cast is there due to a strange error "Property '[Symbol.iterator]' is missing in type... URLSearchParams"
// which looks like possibly a TS/definitions bug.
schema: () => generateSchemaExtensions(ElasticsearchPlugin.options as any),
},
workers: [ElasticsearchIndexerController],
})
export class ElasticsearchPlugin implements OnVendureBootstrap {
private static options: DeepRequired<ElasticsearchOptions>;
private static options: ElasticsearchRuntimeOptions;

/** @internal */
constructor(
Expand All @@ -235,17 +237,18 @@ export class ElasticsearchPlugin implements OnVendureBootstrap {
/** @internal */
async onVendureBootstrap(): Promise<void> {
const { host, port } = ElasticsearchPlugin.options;
const nodeName = this.nodeName();
try {
const pingResult = await this.elasticsearchService.checkConnection();
} catch (e) {
Logger.error(`Could not connect to Elasticsearch instance at "${host}:${port}"`, loggerCtx);
Logger.error(`Could not connect to Elasticsearch instance at "${nodeName}"`, loggerCtx);
Logger.error(JSON.stringify(e), loggerCtx);
this.healthCheckRegistryService.registerIndicatorFunction(() =>
this.elasticsearchHealthIndicator.startupCheckFailed(e.message),
);
return;
}
Logger.info(`Sucessfully connected to Elasticsearch instance at "${host}:${port}"`, loggerCtx);
Logger.info(`Successfully connected to Elasticsearch instance at "${nodeName}"`, loggerCtx);

await this.elasticsearchService.createIndicesIfNotExists();
this.elasticsearchIndexService.initJobQueue();
Expand Down Expand Up @@ -315,4 +318,29 @@ export class ElasticsearchPlugin implements OnVendureBootstrap {
}
});
}

/**
* Returns a string representation of the target node(s) that the Elasticsearch
* client is configured to connect to.
*/
private nodeName(): string {
const { host, port, clientOptions } = ElasticsearchPlugin.options;
const node = clientOptions?.node;
const nodes = clientOptions?.nodes;
if (nodes) {
return [...nodes].join(', ');
}
if (node) {
if (Array.isArray(node)) {
return (node as any[])
.map((n: string | NodeOptions) => {
return typeof n === 'string' ? n : n.url.toString();
})
.join(', ');
} else {
return typeof node === 'string' ? node : node.url.toString();
}
}
return `${host}:${port}`;
}
}

0 comments on commit c686509

Please sign in to comment.