Browse Source

feat(elasticsearch-plugin): Add inStock attribute and filter (#1130)

Relates to #870
Artem Danilov 4 years ago
parent
commit
53cfb8e772

+ 10 - 0
packages/elasticsearch-plugin/src/build-elastic-body.ts

@@ -27,6 +27,7 @@ export function buildElasticBody(
         priceRangeWithTax,
         priceRange,
         facetValueFilters,
+        inStock,
     } = input;
     const query: any = {
         bool: {},
@@ -100,6 +101,15 @@ export function buildElasticBody(
         query.bool.filter = query.bool.filter.concat(createPriceFilters(priceRangeWithTax, true));
     }
 
+    if (inStock !== undefined) {
+        ensureBoolFilterExists(query);
+        if (groupByProduct) {
+            query.bool.filter.push({ term: { productInStock: inStock } });
+        } else {
+            query.bool.filter.push({ term: { inStock } });
+        }
+    }
+
     const sortArray = [];
     if (sort) {
         if (sort.name) {

+ 5 - 3
packages/elasticsearch-plugin/src/elasticsearch.service.ts

@@ -1,6 +1,6 @@
 import { Client } from '@elastic/elasticsearch';
 import { Inject, Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
-import { SearchResult, SearchResultAsset } from '@vendure/common/lib/generated-types';
+import { SearchResultAsset } from '@vendure/common/lib/generated-types';
 import {
     Collection,
     CollectionService,
@@ -25,6 +25,7 @@ import {
     CustomMapping,
     ElasticSearchInput,
     ElasticSearchResponse,
+    ElasticSearchResult,
     ProductIndexItem,
     SearchHit,
     SearchPriceData,
@@ -457,7 +458,7 @@ export class ElasticsearchService implements OnModuleInit, OnModuleDestroy {
         return job!;
     }
 
-    private mapVariantToSearchResult(hit: SearchHit<VariantIndexItem>): SearchResult {
+    private mapVariantToSearchResult(hit: SearchHit<VariantIndexItem>): ElasticSearchResult {
         const source = hit._source;
         const { productAsset, productVariantAsset } = this.getSearchResultAssets(source);
         const result = {
@@ -482,7 +483,7 @@ export class ElasticsearchService implements OnModuleInit, OnModuleDestroy {
         return result;
     }
 
-    private mapProductToSearchResult(hit: SearchHit<VariantIndexItem>): SearchResult {
+    private mapProductToSearchResult(hit: SearchHit<VariantIndexItem>): ElasticSearchResult {
         const source = hit._source;
         const { productAsset, productVariantAsset } = this.getSearchResultAssets(source);
         const result = {
@@ -508,6 +509,7 @@ export class ElasticsearchService implements OnModuleInit, OnModuleDestroy {
                 max: source.productPriceWithTaxMax,
             },
             channelIds: [],
+            inStock: source.productInStock,
             score: hit._score || 0,
         };
         ElasticsearchService.addCustomMappings(result, source, this.options.customProductMappings, true);

+ 5 - 0
packages/elasticsearch-plugin/src/graphql-schema-extensions.ts

@@ -10,6 +10,10 @@ export function generateSchemaExtensions(options: ElasticsearchOptions): Documen
             prices: SearchResponsePriceData!
         }
 
+        extend type SearchResult {
+            inStock: Boolean
+        }
+
         type SearchResponsePriceData {
             range: PriceRange!
             rangeWithTax: PriceRange!
@@ -25,6 +29,7 @@ export function generateSchemaExtensions(options: ElasticsearchOptions): Documen
         extend input SearchInput {
             priceRange: PriceRangeInput
             priceRangeWithTax: PriceRangeInput
+            inStock: Boolean
         }
 
         input PriceRangeInput {

+ 4 - 0
packages/elasticsearch-plugin/src/indexer.controller.ts

@@ -744,6 +744,8 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
             ),
             productCollectionSlugs: unique(productCollectionTranslations.map(c => c.slug)),
             productChannelIds: v.product.channels.map(c => c.id),
+            inStock: v.stockOnHand > 0,
+            productInStock: variants.some(variant => variant.stockOnHand > 0),
         };
         const variantCustomMappings = Object.entries(this.options.customProductVariantMappings);
         for (const [name, def] of variantCustomMappings) {
@@ -804,6 +806,8 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
             productCollectionIds: [],
             productCollectionSlugs: [],
             productChannelIds: product.channels.map(c => c.id),
+            inStock: false,
+            productInStock: false
         };
     }
 

+ 2 - 0
packages/elasticsearch-plugin/src/indexing-utils.ts

@@ -59,6 +59,8 @@ export async function createIndices(
         productPriceWithTaxMin: { type: 'long' },
         price: { type: 'long' },
         priceWithTax: { type: 'long' },
+        inStock: { type: 'boolean' },
+        productInStock: { type: 'boolean' },
         ...indexMappingProperties,
     };
 

+ 4 - 1
packages/elasticsearch-plugin/src/plugin.ts

@@ -210,7 +210,10 @@ import { ElasticsearchOptions, ElasticsearchRuntimeOptions, mergeWithDefaults }
             useFactory: () => ElasticsearchPlugin.options.bufferUpdates === true,
         },
     ],
-    adminApiExtensions: { resolvers: [AdminElasticSearchResolver, EntityElasticSearchResolver] },
+    adminApiExtensions: {
+        resolvers: [AdminElasticSearchResolver, EntityElasticSearchResolver],
+        schema: () => generateSchemaExtensions(ElasticsearchPlugin.options as any),
+    },
     shopApiExtensions: {
         resolvers: () => {
             const { options } = ElasticsearchPlugin;

+ 8 - 0
packages/elasticsearch-plugin/src/types.ts

@@ -10,13 +10,19 @@ import {
 import { ID, JsonCompatible } from '@vendure/common/lib/shared-types';
 import { Asset, SerializedRequestContext } from '@vendure/core';
 
+export type ElasticSearchResult = SearchResult & {
+    inStock: boolean;
+};
+
 export type ElasticSearchInput = SearchInput & {
     priceRange?: PriceRange;
     priceRangeWithTax?: PriceRange;
+    inStock?: boolean;
 };
 
 export type ElasticSearchResponse = SearchResponse & {
     priceRange: SearchPriceData;
+    items: ElasticSearchResult[];
 };
 
 export type SearchPriceData = {
@@ -61,6 +67,8 @@ export type VariantIndexItem = Omit<
         productCollectionSlugs: string[];
         productChannelIds: ID[];
         [customMapping: string]: any;
+        inStock: boolean;
+        productInStock: boolean;
     };
 
 export type ProductIndexItem = IndexItemAssets & {