Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CIF-2967 - Support product query extension in SearchResults #959

Merged
merged 5 commits into from
Sep 29, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

import java.util.Collection;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;

import javax.annotation.Nonnull;
import javax.annotation.PostConstruct;
Expand All @@ -38,6 +40,8 @@
import com.adobe.cq.commerce.core.search.internal.models.SearchResultsSetImpl;
import com.adobe.cq.commerce.core.search.models.SearchResultsSet;
import com.adobe.cq.commerce.core.search.models.Sorter;
import com.adobe.cq.commerce.magento.graphql.ProductAttributeFilterInput;
import com.adobe.cq.commerce.magento.graphql.ProductInterfaceQuery;

/**
* Concrete implementation of the {@link SearchResults} Sling Model API
Expand All @@ -53,6 +57,10 @@ public class SearchResultsImpl extends ProductCollectionImpl implements SearchRe

private String searchTerm;

private Consumer<ProductInterfaceQuery> productQueryHook;

private Function<ProductAttributeFilterInput, ProductAttributeFilterInput> productAttributeFilterHook;

@PostConstruct
protected void initModel() {
searchTerm = request.getParameter(SearchOptionsImpl.SEARCH_QUERY_PARAMETER_ID);
Expand Down Expand Up @@ -102,7 +110,8 @@ public Collection<ProductListItem> getProducts() {
@Override
public SearchResultsSet getSearchResultsSet() {
if (searchResultsSet == null) {
searchResultsSet = searchResultsService.performSearch(searchOptions, resource, currentPage, request);
searchResultsSet = searchResultsService.performSearch(searchOptions, resource, currentPage, request, productQueryHook,
productAttributeFilterHook);
}
return searchResultsSet;
}
Expand All @@ -116,4 +125,23 @@ public SearchStorefrontContext getSearchStorefrontContext() {
public SearchResultsStorefrontContext getSearchResultsStorefrontContext() {
return new SearchResultsStorefrontContextImpl(getSearchResultsSet(), resource);
}

@Override
public void extendProductQueryWith(Consumer<ProductInterfaceQuery> productQueryHook) {
if (this.productQueryHook == null) {
this.productQueryHook = productQueryHook;
} else {
this.productQueryHook = this.productQueryHook.andThen(productQueryHook);
}
}

@Override
public void extendProductFilterWith(
Function<ProductAttributeFilterInput, ProductAttributeFilterInput> productAttributeFilterHook) {
if (this.productAttributeFilterHook == null) {
this.productAttributeFilterHook = productAttributeFilterHook;
} else {
this.productAttributeFilterHook = this.productAttributeFilterHook.andThen(productAttributeFilterHook);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,14 @@
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
package com.adobe.cq.commerce.core.components.models.searchresults;

import java.util.function.Consumer;
import java.util.function.Function;

import com.adobe.cq.commerce.core.components.models.productcollection.ProductCollection;
import com.adobe.cq.commerce.core.components.storefrontcontext.SearchResultsStorefrontContext;
import com.adobe.cq.commerce.core.components.storefrontcontext.SearchStorefrontContext;
import com.adobe.cq.commerce.magento.graphql.ProductAttributeFilterInput;
import com.adobe.cq.commerce.magento.graphql.ProductInterfaceQuery;

/**
* Don't forget the comment
Expand All @@ -37,4 +42,53 @@ public interface SearchResults extends ProductCollection {
* @return search results context
*/
SearchResultsStorefrontContext getSearchResultsStorefrontContext();

/**
* Extend the product query part of the search GraphQL query with a partial query provided by a lambda hook that sets additional
* fields.
*
* Example:
*
* <pre>
* {@code
* searchResults.extendProductQueryWith(p -> p
* .createdAt()
* .addCustomSimpleField("is_returnable"));
* }
* </pre>
*
* If called multiple times, each hook will be "appended" to the previously registered hook(s).
*
* @param productQueryHook Lambda that extends the product query
*/
void extendProductQueryWith(Consumer<ProductInterfaceQuery> productQueryHook);

/**
* Extends or replaces the product attribute filter with a custom instance defined by a lambda hook.
*
* Example 1 (Extend):
*
* <pre>
* {@code
* searchResults.extendProductFilterWith(f -> f
* .setCustomFilter("my-attribute", new FilterEqualTypeInput()
* .setEq("my-value")));
* }
* </pre>
*
* Example 2 (Replace):
*
* <pre>
* {@code
* searchResults.extendProductFilterWith(f -> new ProductAttributeFilterInput()
* .setSku(new FilterEqualTypeInput()
* .setEq("custom-sku"))
* .setCustomFilter("my-attribute", new FilterEqualTypeInput()
* .setEq("my-value")));
* }
* </pre>
*
* @param productAttributeFilterHook Lambda that extends or replaces the product attribute filter.
*/
void extendProductFilterWith(Function<ProductAttributeFilterInput, ProductAttributeFilterInput> productAttributeFilterHook);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
@Version("4.2.0")
@Version("5.0.0")
package com.adobe.cq.commerce.core.components.models.searchresults;

import org.osgi.annotation.versioning.Version;
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
import com.adobe.cq.commerce.magento.graphql.ProductAttributeFilterInput;
import com.shopify.graphql.support.Input;

/**
* @deprecated Use {@link ProductAttributeFilterInput#setCustomFilter} instead.
*/
@Deprecated
public class GenericProductAttributeFilterInput extends ProductAttributeFilterInput {

private Map<String, Input<FilterEqualTypeInput>> equalInputs = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

import javax.annotation.Nonnull;
Expand All @@ -33,6 +34,7 @@
import org.apache.commons.lang3.tuple.Pair;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.jetbrains.annotations.NotNull;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
Expand Down Expand Up @@ -65,6 +67,7 @@
import com.adobe.cq.commerce.magento.graphql.FilterMatchTypeInput;
import com.adobe.cq.commerce.magento.graphql.FilterRangeTypeInput;
import com.adobe.cq.commerce.magento.graphql.Operations;
import com.adobe.cq.commerce.magento.graphql.ProductAttributeFilterInput;
import com.adobe.cq.commerce.magento.graphql.ProductAttributeSortInput;
import com.adobe.cq.commerce.magento.graphql.ProductInterface;
import com.adobe.cq.commerce.magento.graphql.ProductInterfaceQuery;
Expand Down Expand Up @@ -113,7 +116,15 @@ public SearchResultsSet performSearch(
final Page productPage,
final SlingHttpServletRequest request,
final Consumer<ProductInterfaceQuery> productQueryHook) {
return performSearch(searchOptions, resource, productPage, request, productQueryHook, null).getRight();
return performSearch(searchOptions, resource, productPage, request, productQueryHook, null, null).getRight();
}

@NotNull
@Override
public SearchResultsSet performSearch(SearchOptions searchOptions, Resource resource, Page productPage,
SlingHttpServletRequest request, Consumer<ProductInterfaceQuery> productQueryHook,
Function<ProductAttributeFilterInput, ProductAttributeFilterInput> productAttributeFilterHook) {
return performSearch(searchOptions, resource, productPage, request, productQueryHook, productAttributeFilterHook, null).getRight();
}

@Nonnull
Expand All @@ -125,6 +136,17 @@ public Pair<CategoryInterface, SearchResultsSet> performSearch(
final SlingHttpServletRequest request,
final Consumer<ProductInterfaceQuery> productQueryHook,
AbstractCategoryRetriever categoryRetriever) {
return performSearch(searchOptions, resource, productPage, request, productQueryHook, null, categoryRetriever);
}

private Pair<CategoryInterface, SearchResultsSet> performSearch(
final SearchOptions searchOptions,
final Resource resource,
final Page productPage,
final SlingHttpServletRequest request,
final Consumer<ProductInterfaceQuery> productQueryHook,
Function<ProductAttributeFilterInput, ProductAttributeFilterInput> productAttributeFilterHook,
AbstractCategoryRetriever categoryRetriever) {

SearchResultsSetImpl searchResultsSet = new SearchResultsSetImpl();
SearchOptionsImpl mutableSearchOptions = new SearchOptionsImpl(searchOptions);
Expand Down Expand Up @@ -187,6 +209,7 @@ public Pair<QueryQuery.CategoryListArgumentsDefinition, CategoryTreeQueryDefinit
SorterKey currentSorterKey = findSortKey(mutableSearchOptions);

String productsQueryString = generateProductsQueryString(mutableSearchOptions, availableFilters, productQueryHook,
productAttributeFilterHook,
currentSorterKey);
LOGGER.debug("Generated products query string {}", productsQueryString);
GraphqlResponse<Query, Error> response = magentoGraphqlClient.execute(productsQueryString);
Expand Down Expand Up @@ -306,8 +329,15 @@ private String generateProductsQueryString(
final SearchOptions searchOptions,
final List<FilterAttributeMetadata> availableFilters,
final Consumer<ProductInterfaceQuery> productQueryHook,
Function<ProductAttributeFilterInput, ProductAttributeFilterInput> productAttributeFilterHook,
final SorterKey sorterKey) {
GenericProductAttributeFilterInput filterInputs = new GenericProductAttributeFilterInput();
ProductAttributeFilterInput filterInputs = new ProductAttributeFilterInput();

// Apply product attribute filter hook if set
if (productAttributeFilterHook != null) {
filterInputs = productAttributeFilterHook.apply(filterInputs);
}
final ProductAttributeFilterInput filterInputs2 = filterInputs;

searchOptions.getAllFilters().entrySet()
.stream()
Expand All @@ -323,19 +353,19 @@ private String generateProductsQueryString(
if ("FilterEqualTypeInput".equals(filterAttributeMetadata.getFilterInputType())) {
FilterEqualTypeInput filter = new FilterEqualTypeInput();
filter.setEq(value);
filterInputs.addEqualTypeInput(code, filter);
filterInputs2.setCustomFilter(code, filter);
} else if ("FilterMatchTypeInput".equals(filterAttributeMetadata.getFilterInputType())) {
FilterMatchTypeInput filter = new FilterMatchTypeInput();
filter.setMatch(value);
filterInputs.addMatchTypeInput(code, filter);
filterInputs2.setCustomFilter(code, filter);
} else if ("FilterRangeTypeInput".equals(filterAttributeMetadata.getFilterInputType())) {
FilterRangeTypeInput filter = new FilterRangeTypeInput();
final String[] rangeValues = value.split("_");
if (rangeValues.length == 1 && StringUtils.isNumeric(rangeValues[0])) {
// The range has a single value like '60'
filter.setFrom(rangeValues[0]);
filter.setTo(rangeValues[0]);
filterInputs.addRangeTypeInput(code, filter);
filterInputs2.setCustomFilter(code, filter);
} else if (rangeValues.length > 1) {
// For values such as '*_60', the from range should be left empty
if (StringUtils.isNumeric(rangeValues[0])) {
Expand All @@ -345,7 +375,7 @@ private String generateProductsQueryString(
if (StringUtils.isNumeric(rangeValues[1])) {
filter.setTo(rangeValues[1]);
}
filterInputs.addRangeTypeInput(code, filter);
filterInputs2.setCustomFilter(code, filter);
}
}
});
Expand All @@ -358,7 +388,7 @@ private String generateProductsQueryString(
}
productArguments.currentPage(searchOptions.getCurrentPage());
productArguments.pageSize(searchOptions.getPageSize());
productArguments.filter(filterInputs);
productArguments.filter(filterInputs2);
if (sorterKey != null) {
String sortKey = sorterKey.getName();
String sortOrder = sorterKey.getOrder().name();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package com.adobe.cq.commerce.core.search.services;

import java.util.function.Consumer;
import java.util.function.Function;

import javax.annotation.Nonnull;

Expand All @@ -28,6 +29,7 @@
import com.adobe.cq.commerce.core.search.models.SearchOptions;
import com.adobe.cq.commerce.core.search.models.SearchResultsSet;
import com.adobe.cq.commerce.magento.graphql.CategoryInterface;
import com.adobe.cq.commerce.magento.graphql.ProductAttributeFilterInput;
import com.adobe.cq.commerce.magento.graphql.ProductInterfaceQuery;
import com.day.cq.wcm.api.Page;

Expand Down Expand Up @@ -76,6 +78,29 @@ SearchResultsSet performSearch(
SlingHttpServletRequest request,
Consumer<ProductInterfaceQuery> productQueryHook);

/**
* Perform a search against the commerce backend and return a {@link SearchResultsSet} for consumption by the frontend. When the search
* is performed the implementing concrete classes are responsible for correctly applying the provided filters.
*
* This method allows an override query hook to be provided.
*
* @param searchOptions the search options for thigns like filters, query, etc
* @param resource resource for adaption
* @param productPage product page to provide context to the search service
* @param request the original request object
* @param productQueryHook an optional query hook parameter
* @param productAttributeFilterHook an optional filter hook parameter
* @return a {@link SearchResultsSet} with search results and metadata
*/
@Nonnull
SearchResultsSet performSearch(
SearchOptions searchOptions,
Resource resource,
Page productPage,
SlingHttpServletRequest request,
Consumer<ProductInterfaceQuery> productQueryHook,
Function<ProductAttributeFilterInput, ProductAttributeFilterInput> productAttributeFilterHook);

/**
* Perform a search against the commerce backend and return a {@link SearchResultsSet} for consumption by the frontend. When the search
* is performed the implementing concrete classes are responsible for correctly applying the provided filters.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
@Version("1.12.0")
@Version("1.13.0")
package com.adobe.cq.commerce.core.search.services;

import org.osgi.annotation.versioning.Version;
Loading