public virtual async Task <SearchCategoryResponse> Handle(SearchCategoryQuery request, CancellationToken cancellationToken)
        {
            var   essentialTerms = new List <string>();
            Store store          = null;

            if (!string.IsNullOrWhiteSpace(request.StoreId))
            {
                store = await _storeService.GetByIdAsync(request.StoreId);

                if (store == null)
                {
                    throw new ArgumentException($"Store with Id: {request.StoreId} is absent");
                }

                essentialTerms.Add($"__outline:{store.Catalog}");
            }

            var searchRequest = new IndexSearchRequestBuilder()
                                .WithFuzzy(request.Fuzzy, request.FuzzyLevel)
                                .ParseFilters(_phraseParser, request.Filter)
                                .WithSearchPhrase(request.Query)
                                .WithPaging(request.Skip, request.Take)
                                .AddObjectIds(request.ObjectIds)
                                .AddSorting(request.Sort)
                                //Limit search result with store catalog
                                .AddTerms(essentialTerms)
                                .WithIncludeFields(IndexFieldsMapper.MapToIndexIncludes(request.IncludeFields).ToArray())
                                .Build();

            var searchResult = await _searchProvider.SearchAsync(KnownDocumentTypes.Category, searchRequest);

            var categories = searchResult.Documents?.Select(x => _mapper.Map <ExpCategory>(x, options =>
            {
                options.Items["store"]       = store;
                options.Items["cultureName"] = request.CultureName;
            })).ToList() ?? new List <ExpCategory>();

            var result = new SearchCategoryResponse
            {
                Query      = request,
                Results    = categories,
                Store      = store,
                TotalCount = (int)searchResult.TotalCount
            };

            await _pipeline.Execute(result);

            return(result);
        }
Esempio n. 2
0
        public virtual async Task <SearchProductResponse> Handle(SearchProductQuery request, CancellationToken cancellationToken)
        {
            var allStoreCurrencies = await _storeCurrencyResolver.GetAllStoreCurrenciesAsync(request.StoreId, request.CultureName);

            var currency = await _storeCurrencyResolver.GetStoreCurrencyAsync(request.CurrencyCode, request.StoreId, request.CultureName);

            var store = await _storeService.GetByIdAsync(request.StoreId);

            var responseGroup = EnumUtility.SafeParse(request.GetResponseGroup(), ExpProductResponseGroup.None);

            var builder = new IndexSearchRequestBuilder()
                          .WithCurrency(currency.Code)
                          .WithFuzzy(request.Fuzzy, request.FuzzyLevel)
                          .ParseFilters(_phraseParser, request.Filter)
                          .WithSearchPhrase(request.Query)
                          .WithPaging(request.Skip, request.Take)
                          .AddObjectIds(request.ObjectIds)
                          .AddSorting(request.Sort)
                          .WithIncludeFields(IndexFieldsMapper.MapToIndexIncludes(request.IncludeFields).ToArray());

            if (request.ObjectIds.IsNullOrEmpty())
            {
                AddDefaultTerms(builder, store.Catalog);
            }

            var criteria = new ProductIndexedSearchCriteria
            {
                StoreId      = request.StoreId,
                Currency     = request.CurrencyCode ?? store.DefaultCurrency,
                LanguageCode = store.Languages.Contains(request.CultureName) ? request.CultureName : store.DefaultLanguage,
                CatalogId    = store.Catalog
            };

            //Use predefined  facets for store  if the facet filter expression is not set
            if (responseGroup.HasFlag(ExpProductResponseGroup.LoadFacets))
            {
                var predefinedAggregations = await _aggregationConverter.GetAggregationRequestsAsync(criteria, new FiltersContainer());

                builder.WithCultureName(criteria.LanguageCode);
                builder.ParseFacets(_phraseParser, request.Facet, predefinedAggregations)
                .ApplyMultiSelectFacetSearch();
            }

            var searchRequest = builder.Build();
            var searchResult  = await _searchProvider.SearchAsync(KnownDocumentTypes.Product, searchRequest);

            var resultAggregations = await ConvertResultAggregations(criteria, searchRequest, searchResult);

            searchRequest.SetAppliedAggregations(resultAggregations.ToArray());

            var products = searchResult.Documents?.Select(x => _mapper.Map <ExpProduct>(x)).ToList() ?? new List <ExpProduct>();

            var result = new SearchProductResponse
            {
                Query = request,
                AllStoreCurrencies = allStoreCurrencies,
                Currency           = currency,
                Store   = store,
                Results = products,
                Facets  = resultAggregations?.ApplyLanguageSpecificFacetResult(criteria.LanguageCode)
                          .Select(x => _mapper.Map <FacetResult>(x, options =>
                {
                    options.Items["cultureName"] = criteria.LanguageCode;
                })).ToList(),
                TotalCount = (int)searchResult.TotalCount
            };

            await _pipeline.Execute(result);

            return(result);

            async Task <Aggregation[]> ConvertResultAggregations(ProductIndexedSearchCriteria criteria, SearchRequest searchRequest, SearchResponse searchResult)
            {
                // Preconvert resulting aggregations to be properly understandable by catalog module
                var preconvertedAggregations = new List <AggregationResponse>();
                //Remember term facet ids to distinguish the resulting aggregations are range or term
                var termsInRequest = new List <string>(searchRequest.Aggregations.Where(x => x is TermAggregationRequest).Select(x => x.Id ?? x.FieldName));

                foreach (var aggregation in searchResult.Aggregations)
                {
                    if (!termsInRequest.Contains(aggregation.Id))
                    {
                        // There we'll go converting range facet result
                        var fieldName = new Regex(@"^(?<fieldName>[A-Za-z0-9]+)(-.+)*$", RegexOptions.IgnoreCase).Match(aggregation.Id).Groups["fieldName"].Value;
                        if (!fieldName.IsNullOrEmpty())
                        {
                            preconvertedAggregations.AddRange(aggregation.Values.Select(x =>
                            {
                                var matchId = new Regex(@"^(?<left>[0-9*]+)-(?<right>[0-9*]+)$", RegexOptions.IgnoreCase).Match(x.Id);
                                var left    = matchId.Groups["left"].Value;
                                var right   = matchId.Groups["right"].Value;
                                x.Id        = left == "*" ? $@"under-{right}" : x.Id;
                                x.Id        = right == "*" ? $@"over-{left}" : x.Id;
                                return(new AggregationResponse()
                                {
                                    Id = $@"{fieldName}-{x.Id}", Values = new List <AggregationResponseValue> {
                                        x
                                    }
                                });
                            }
                                                                                        ));
                        }
                    }
                    else
                    {
                        // This is term aggregation, should skip converting and put resulting aggregation as is
                        preconvertedAggregations.Add(aggregation);
                    }
                }

                //Call the catalog aggregation converter service to convert AggregationResponse to proper Aggregation type (term, range, filter)
                return(await _aggregationConverter.ConvertAggregationsAsync(preconvertedAggregations, criteria));
            }
        }
        public virtual async Task <SearchProductResponse> Handle(SearchProductQuery request, CancellationToken cancellationToken)
        {
            var allStoreCurrencies = await _storeCurrencyResolver.GetAllStoreCurrenciesAsync(request.StoreId, request.CultureName);

            var currency = await _storeCurrencyResolver.GetStoreCurrencyAsync(request.CurrencyCode, request.StoreId, request.CultureName);

            var store = await _storeService.GetByIdAsync(request.StoreId);

            var responseGroup = EnumUtility.SafeParse(request.GetResponseGroup(), ExpProductResponseGroup.None);

            var builder = new IndexSearchRequestBuilder()
                          .WithFuzzy(request.Fuzzy, request.FuzzyLevel)
                          .ParseFilters(_phraseParser, request.Filter)
                          .WithSearchPhrase(request.Query)
                          .WithPaging(request.Skip, request.Take)
                          .AddObjectIds(request.ObjectIds)
                          .AddSorting(request.Sort)
                          .WithIncludeFields(IndexFieldsMapper.MapToIndexIncludes(request.IncludeFields).ToArray());

            if (request.ObjectIds.IsNullOrEmpty())
            {
                //filter products only the store catalog and visibility status when search
                builder.AddTerms(new[] { "status:visible" });//Only visible, exclude variations from search result
                builder.AddTerms(new[] { $"__outline:{store.Catalog}" });
            }

            //Use predefined  facets for store  if the facet filter expression is not set
            if (responseGroup.HasFlag(ExpProductResponseGroup.LoadFacets))
            {
                var predefinedAggregations = await _aggregationConverter.GetAggregationRequestsAsync(new ProductIndexedSearchCriteria
                {
                    StoreId  = request.StoreId,
                    Currency = request.CurrencyCode,
                }, new FiltersContainer());

                builder.ParseFacets(_phraseParser, request.Facet, predefinedAggregations)
                .ApplyMultiSelectFacetSearch();
            }

            var searchRequest = builder.Build();
            var searchResult  = await _searchProvider.SearchAsync(KnownDocumentTypes.Product, searchRequest);

            var criteria = new ProductIndexedSearchCriteria
            {
                StoreId  = request.StoreId,
                Currency = request.CurrencyCode,
            };
            //TODO: move later to own implementation
            //Call the catalog aggregation converter service to convert AggregationResponse to proper Aggregation type (term, range, filter)
            var resultAggregations = await _aggregationConverter.ConvertAggregationsAsync(searchResult.Aggregations, criteria);

            searchRequest.SetAppliedAggregations(resultAggregations);

            var products = searchResult.Documents?.Select(x => _mapper.Map <ExpProduct>(x)).ToList() ?? new List <ExpProduct>();

            var result = new SearchProductResponse
            {
                Query = request,
                AllStoreCurrencies = allStoreCurrencies,
                Currency           = currency,
                Store      = store,
                Results    = products,
                Facets     = resultAggregations?.Select(x => _mapper.Map <FacetResult>(x)).ToList(),
                TotalCount = (int)searchResult.TotalCount
            };

            await _pipeline.Execute(result);

            return(result);
        }