From dc2eae4e7ab5b6152fee80fe67f0da683e02ebab Mon Sep 17 00:00:00 2001 From: Dirk Rudolph Date: Tue, 2 Aug 2022 12:33:32 +0200 Subject: [PATCH] CIF-2916 - Remove the queries done by the UrlProvider to get the category uid / sku (#943) * add UrlProvider product and category filter hooks * use product attribute filter input hook from url provider * format code, add more unit tests * implement fuzzy matching for magento gql client local cache * order products queries so that sku,url_key,... are always the first parameters * use urlprovider hook in productlist and breadcrumb * fix nodetype validation error * disable buildtime coverage check in examples * update pom.xml --- .../client/MagentoGraphqlClientImpl.java | 23 +- .../models/v1/breadcrumb/BreadcrumbImpl.java | 25 +- .../v1/breadcrumb/BreadcrumbRetriever.java | 43 ++-- .../models/v1/product/ProductImpl.java | 156 ++++++++----- .../product/ProductPlaceholderRetriever.java | 45 ---- .../models/v1/product/ProductRetriever.java | 2 +- .../v1/productcarousel/ProductsRetriever.java | 6 +- .../CategoryPlaceholderRetriever.java | 44 ---- .../v1/productlist/ProductListImpl.java | 214 +++++++++++------- .../relatedproducts/RelatedProductsImpl.java | 24 +- .../RelatedProductsRetriever.java | 24 +- .../internal/services/UrlProviderImpl.java | 40 ++++ .../servlets/CatalogPageNotFoundFilter.java | 4 +- .../components/services/urls/UrlProvider.java | 34 ++- .../services/urls/package-info.java | 2 +- .../services/SearchResultsServiceImpl.java | 11 +- .../client/MagentoGraphqlClientImplTest.java | 33 +++ .../v1/breadcrumb/BreadcrumbImplTest.java | 10 +- .../breadcrumb/BreadcrumbRetrieverTest.java | 3 +- .../models/v1/page/PageMetadataImplTest.java | 8 +- .../models/v1/product/ProductImplTest.java | 7 +- .../ProductsRetrieverTest.java | 12 +- .../v1/productlist/ProductListImplTest.java | 5 +- .../RelatedProductsImplTest.java | 17 +- .../services/UrlProviderImplTest.java | 31 +++ .../CatalogPageNotFoundFilterTest.java | 4 +- .../test/resources/context/jcr-content.json | 2 +- .../resources/exporter/upsell-products.json | 2 +- ...ento-graphql-crosssellproducts-result.json | 1 + ...agento-graphql-relatedproducts-result.json | 1 + ...magento-graphql-upsellproducts-result.json | 1 + examples/bundle/pom.xml | 22 -- .../examples/servlets/GraphqlServletTest.java | 3 +- 33 files changed, 509 insertions(+), 350 deletions(-) delete mode 100644 bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/product/ProductPlaceholderRetriever.java delete mode 100644 bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/productlist/CategoryPlaceholderRetriever.java diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/client/MagentoGraphqlClientImpl.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/client/MagentoGraphqlClientImpl.java index afa589af78..e7fcade412 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/client/MagentoGraphqlClientImpl.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/client/MagentoGraphqlClientImpl.java @@ -246,8 +246,27 @@ public GraphqlResponse execute(String query, HttpMethod httpMethod private GraphqlResponse executeCached(String query, RequestOptions options) { try { - if (localResponseCache != null && localResponseCache.containsKey(query)) { - return localResponseCache.get(query); + if (localResponseCache != null) { + if (localResponseCache.containsKey(query)) { + LOGGER.debug("Cache hit for query '{}'", query); + return localResponseCache.get(query); + } + + // fuzzy matching (a very simplified version of caching resolved graphql response objects) + // If a cache key (query) starts with the given query trimmed by any trailing curley brackets can assume + // that the cached response queried with the same filter the same fields. This only works if the queries + // we use define the queried fields always in the same order. + // Example: The query to resolve the sku from the url_key done by the UrlProvider can reuse the response + // from the query done by the product detail component. This helps any Commerce Content Fragment or + // Commerce Experience Fragment on a product detail page that is rendered after the product detail + // component to get the product identifier. + String fuzzyKey = StringUtils.removePattern(query, "\\}+$"); + for (Map.Entry> entry : localResponseCache.entrySet()) { + if (entry.getKey().startsWith(fuzzyKey)) { + LOGGER.debug("Fuzzy cache hit for query '{}', return response of query '{}'", query, entry.getKey()); + return entry.getValue(); + } + } } GraphqlRequest request = new GraphqlRequest(query); diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/breadcrumb/BreadcrumbImpl.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/breadcrumb/BreadcrumbImpl.java index b43c0d89bb..57c60c8636 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/breadcrumb/BreadcrumbImpl.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/breadcrumb/BreadcrumbImpl.java @@ -161,20 +161,15 @@ private boolean populateItems(NavigationItem item) { ProductInterface product = null; if (siteStructure.isProductPage(page)) { - String productSku = urlProvider.getProductIdentifier(request); - if (StringUtils.isEmpty(productSku)) { - return false; - } - ProductUrlFormat.Params urlParams = urlProvider.parseProductUrlFormatParameters(request); - categoriesBreadcrumbs = fetchProductBreadcrumbs(productSku, urlParams, magentoGraphqlClient); + categoriesBreadcrumbs = fetchProductBreadcrumbs(); product = retriever.fetchProduct(); isProductPage = true; - } else if (siteStructure.isCategoryPage(page)) { - String categoryUid = urlProvider.getCategoryIdentifier(request); - if (StringUtils.isEmpty(categoryUid)) { + + if (product == null) { return false; } - categoriesBreadcrumbs = fetchCategoryBreadcrumbs(categoryUid, magentoGraphqlClient); + } else if (siteStructure.isCategoryPage(page)) { + categoriesBreadcrumbs = fetchCategoryBreadcrumbs(); isCategoryPage = true; } else { // we reached a content page @@ -292,12 +287,12 @@ public Comparator getCategoryInterfaceComparator() { .reversed(); } - private List fetchProductBreadcrumbs(String productSku, ProductUrlFormat.Params urlParams, - MagentoGraphqlClient magentoGraphqlClient) { + private List fetchProductBreadcrumbs() { retriever = new BreadcrumbRetriever(magentoGraphqlClient); - retriever.setProductIdentifier(productSku); + retriever.setProductIdentifierHook(urlProvider.getProductFilterHook(request)); List categories = retriever.fetchCategoriesBreadcrumbs(); + ProductUrlFormat.Params urlParams = urlProvider.parseProductUrlFormatParameters(request); List alternatives = categories.stream().map(CategoryInterface::getUrlPath).collect(Collectors.toList()); String contextUrlPath = UrlFormatBase.selectUrlPath(null, alternatives, null, urlParams.getCategoryUrlParams().getUrlKey(), urlParams.getCategoryUrlParams().getUrlPath()); @@ -311,9 +306,9 @@ private List fetchProductBreadcrumbs(String product .collect(Collectors.toList()); } - private List fetchCategoryBreadcrumbs(String categoryUid, MagentoGraphqlClient magentoGraphqlClient) { + private List fetchCategoryBreadcrumbs() { retriever = new BreadcrumbRetriever(magentoGraphqlClient); - retriever.setCategoryIdentifier(categoryUid); + retriever.setCategoryIdentifierHook(urlProvider.getCategoryFilterHook(request)); return retriever.fetchCategoriesBreadcrumbs(); } diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/breadcrumb/BreadcrumbRetriever.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/breadcrumb/BreadcrumbRetriever.java index caa1cbfad4..da2b9a316b 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/breadcrumb/BreadcrumbRetriever.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/breadcrumb/BreadcrumbRetriever.java @@ -18,6 +18,7 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.function.UnaryOperator; import org.apache.commons.collections4.CollectionUtils; @@ -27,7 +28,6 @@ import com.adobe.cq.commerce.magento.graphql.CategoryFilterInput; import com.adobe.cq.commerce.magento.graphql.CategoryInterface; import com.adobe.cq.commerce.magento.graphql.CategoryTreeQueryDefinition; -import com.adobe.cq.commerce.magento.graphql.FilterEqualTypeInput; import com.adobe.cq.commerce.magento.graphql.Operations; import com.adobe.cq.commerce.magento.graphql.ProductAttributeFilterInput; import com.adobe.cq.commerce.magento.graphql.ProductInterface; @@ -41,10 +41,8 @@ class BreadcrumbRetriever extends AbstractRetriever { private List categories; private Optional product; - - private String productIdentifier; - - private String categoryIdentifier; + private UnaryOperator productIdentifierHook; + private UnaryOperator categoryIdentifierHook; BreadcrumbRetriever(MagentoGraphqlClient client) { super(client); @@ -65,7 +63,7 @@ protected List fetchCategoriesBreadcrumbs() { /** * Executes the GraphQL query and returns the name of the product. - * This assumes that {@link #setProductIdentifier(String)} has been called before. + * This assumes that {@link #setProductIdentifierHook(UnaryOperator)} has been called before. * For subsequent calls of this method, a cached response is returned. * * @return The product name. @@ -80,25 +78,25 @@ protected ProductInterface fetchProduct() { /** * Set the sku of the product that should be fetched. Setting the a new product, removes any cached data. * - * @param productIdentifier The product sku. + * @param inputHook The product sku. */ - protected void setProductIdentifier(String productIdentifier) { - this.productIdentifier = productIdentifier; + protected void setProductIdentifierHook(UnaryOperator inputHook) { + this.productIdentifierHook = inputHook; } /** * Set the category uid of the category that should be fetched. Setting the a new category, removes any cached * data. * - * @param categoryIdentifier The category uid. + * @param inputHook The category uid. */ - protected void setCategoryIdentifier(String categoryIdentifier) { - this.categoryIdentifier = categoryIdentifier; + protected void setCategoryIdentifierHook(UnaryOperator inputHook) { + this.categoryIdentifierHook = inputHook; } @Override protected void populate() { - if (productIdentifier == null && categoryIdentifier == null) { + if (productIdentifierHook == null && categoryIdentifierHook == null) { categories = Collections.emptyList(); product = Optional.empty(); return; @@ -114,24 +112,29 @@ protected void populate() { Query rootQuery = response.getData(); - if (productIdentifier != null) { + if (productIdentifierHook != null) { List products = rootQuery .getProducts() .getItems(); if (products.size() > 0) { - product = Optional.of(products.get(0)); - categories = product.get().getCategories(); + ProductInterface p = products.get(0); + categories = p.getCategories(); + product = Optional.of(p); + } else { + categories = Collections.emptyList(); + product = Optional.empty(); } } else { categories = rootQuery.getCategoryList(); + product = Optional.empty(); } } @Override protected GraphqlResponse executeQuery() { if (query == null) { - if (productIdentifier != null) { + if (productIdentifierHook != null) { query = generateProductQuery(); } else { query = generateCategoryQuery(); @@ -146,8 +149,7 @@ protected GraphqlResponse executeQuery() { * @return GraphQL query as string */ protected String generateProductQuery() { - FilterEqualTypeInput identifierFilter = new FilterEqualTypeInput().setEq(productIdentifier); - ProductAttributeFilterInput filter = new ProductAttributeFilterInput().setSku(identifierFilter); + ProductAttributeFilterInput filter = productIdentifierHook.apply(new ProductAttributeFilterInput()); QueryQuery.ProductsArgumentsDefinition searchArgs = s -> s.filter(filter); @@ -175,8 +177,7 @@ protected String generateProductQuery() { * @return GraphQL query as string */ protected String generateCategoryQuery() { - FilterEqualTypeInput identifierFilter = new FilterEqualTypeInput().setEq(categoryIdentifier); - CategoryFilterInput filter = new CategoryFilterInput().setCategoryUid(identifierFilter); + CategoryFilterInput filter = categoryIdentifierHook.apply(new CategoryFilterInput()); CategoryListArgumentsDefinition searchArgs = s -> s.filters(filter); diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/product/ProductImpl.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/product/ProductImpl.java index c27e4e2407..f04c8feb2a 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/product/ProductImpl.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/product/ProductImpl.java @@ -16,19 +16,25 @@ package com.adobe.cq.commerce.core.components.internal.models.v1.product; import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; +import java.util.function.UnaryOperator; import java.util.stream.Collectors; import javax.annotation.PostConstruct; import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ValueMap; import org.apache.sling.api.scripting.SlingScriptHelper; import org.apache.sling.models.annotations.Model; @@ -75,11 +81,14 @@ import com.adobe.cq.commerce.magento.graphql.GroupedProduct; import com.adobe.cq.commerce.magento.graphql.GroupedProductItem; import com.adobe.cq.commerce.magento.graphql.MediaGalleryInterface; +import com.adobe.cq.commerce.magento.graphql.ProductAttributeFilterInput; import com.adobe.cq.commerce.magento.graphql.ProductImage; import com.adobe.cq.commerce.magento.graphql.ProductInterface; import com.adobe.cq.commerce.magento.graphql.ProductStockStatus; +import com.adobe.cq.commerce.magento.graphql.Query; import com.adobe.cq.commerce.magento.graphql.SimpleProduct; import com.adobe.cq.commerce.magento.graphql.VirtualProduct; +import com.adobe.cq.commerce.magento.graphql.gson.QueryDeserializer; import com.adobe.cq.sightly.SightlyWCMMode; import com.adobe.cq.wcm.core.components.models.Component; import com.adobe.cq.wcm.core.components.models.datalayer.AssetData; @@ -115,6 +124,28 @@ public class ProductImpl extends DataLayerComponent implements Product { */ private static final String PN_CONFIG_ENABLE_WISH_LISTS = "enableWishLists"; + static Optional PLACEHOLDER_PRODUCT; + + static ProductInterface getPlaceholderProduct() { + if (PLACEHOLDER_PRODUCT == null) { + try { + InputStream data = ProductImpl.class.getClassLoader().getResourceAsStream(PLACEHOLDER_DATA); + if (data != null) { + String json = IOUtils.toString(data, StandardCharsets.UTF_8); + Query rootQuery = QueryDeserializer.getGson().fromJson(json, Query.class); + PLACEHOLDER_PRODUCT = Optional.of(rootQuery.getProducts().getItems().get(0)); + } else { + LOGGER.warn("Could not find placeholder data on classpath: {}", PLACEHOLDER_DATA); + PLACEHOLDER_PRODUCT = Optional.empty(); + } + } catch (IOException ex) { + LOGGER.warn("Could not load placeholder data", ex); + PLACEHOLDER_PRODUCT = Optional.empty(); + } + } + return PLACEHOLDER_PRODUCT.orElse(null); + } + @Self private SlingHttpServletRequest request; @Self(injectionStrategy = InjectionStrategy.OPTIONAL) @@ -136,6 +167,8 @@ public class ProductImpl extends DataLayerComponent implements Product { @OSGiService private Externalizer externalizer; + private ProductInterface product; + private ValueMap properties; private Boolean configurable; private Boolean isGroupedProduct; private Boolean isVirtualProduct; @@ -146,9 +179,7 @@ public class ProductImpl extends DataLayerComponent implements Product { private boolean isAuthor = true; private String canonicalUrl; private boolean enableAddToWishList; - protected AbstractProductRetriever productRetriever; - private Locale locale; @PostConstruct @@ -165,77 +196,98 @@ protected void initModel() { currentStyle = Utils.getStyleProperties(request, resource); } - ComponentsConfiguration configProperties = currentPage.getContentResource() - .adaptTo(ComponentsConfiguration.class); - // Get product selection from dialog - ValueMap properties = request.getResource().getValueMap(); - String sku = properties.get(SELECTION_PROPERTY, String.class); + properties = request.getResource().getValueMap(); + Resource contentResource = currentPage.getContentResource(); + ComponentsConfiguration configProperties = contentResource.adaptTo(ComponentsConfiguration.class); + isAuthor = wcmMode != null && !wcmMode.isDisabled(); + loadClientPrice = properties.get(PN_LOAD_CLIENT_PRICE, currentStyle.get(PN_LOAD_CLIENT_PRICE, LOAD_CLIENT_PRICE_DEFAULT)); + locale = currentPage.getLanguage(false); + enableAddToWishList = configProperties != null ? configProperties.get(PN_CONFIG_ENABLE_WISH_LISTS, Boolean.TRUE) : Boolean.TRUE; + enableAddToWishList = enableAddToWishList + && currentStyle.get(PN_STYLE_ENABLE_ADD_TO_WISHLIST, Product.super.getAddToWishListEnabled()); - if (magentoGraphqlClient != null) { - // If no product is selected via dialog, extract it from the URL - if (StringUtils.isEmpty(sku)) { - sku = urlProvider.getProductIdentifier(request); - } + initProductRetriever(); + } - // Load product data for component - if (StringUtils.isNotBlank(sku)) { + private void initProductRetriever() { + if (magentoGraphqlClient == null) { + return; + } + + String sku = properties.get(SELECTION_PROPERTY, String.class); + + // Load product data for component (pre configured) + if (StringUtils.isNotBlank(sku)) { + productRetriever = new ProductRetriever(magentoGraphqlClient); + productRetriever.setIdentifier(sku); + } else { + UnaryOperator queryHook = urlProvider.getProductFilterHook(request); + + if (queryHook != null) { productRetriever = new ProductRetriever(magentoGraphqlClient); - productRetriever.setIdentifier(sku); - loadClientPrice = properties.get(PN_LOAD_CLIENT_PRICE, - currentStyle.get(PN_LOAD_CLIENT_PRICE, LOAD_CLIENT_PRICE_DEFAULT)); - } else if (isAuthor) { - // In AEM Sites editor, load some dummy placeholder data for the component. - try { - productRetriever = new ProductPlaceholderRetriever(magentoGraphqlClient, PLACEHOLDER_DATA); - } catch (IOException e) { - LOGGER.warn("Cannot use placeholder data", e); + productRetriever.extendProductFilterWith(queryHook); + } + } + } + + private ProductInterface fetchProduct() { + ProductInterface product = null; + + // we never return a product when no graphql client is available + // this is the original behaviour implemented in CIF-1244, even though we could return the placeholder image + // the same way we do it for other error cases + if (magentoGraphqlClient != null) { + if (productRetriever != null) { + product = productRetriever.fetchProduct(); + if (product != null) { + return product; } + } + + if (isAuthor) { usePlaceholderData = true; + product = getPlaceholderProduct(); } } - locale = currentPage.getLanguage(false); - enableAddToWishList = (configProperties != null - ? configProperties.get(PN_CONFIG_ENABLE_WISH_LISTS, Boolean.TRUE) - : Boolean.TRUE) - && currentStyle.get(PN_STYLE_ENABLE_ADD_TO_WISHLIST, Product.super.getAddToWishListEnabled()); + return product; } @Override public Boolean getFound() { - return productRetriever != null && productRetriever.fetchProduct() != null; + return fetchProduct() != null; } @Override public String getName() { - return productRetriever.fetchProduct().getName(); + return fetchProduct().getName(); } @Override public String getDescription() { - return safeDescription(productRetriever.fetchProduct()); + return safeDescription(fetchProduct()); } @Override public String getSku() { - return productRetriever.fetchProduct().getSku(); + return fetchProduct().getSku(); } @Override public Price getPriceRange() { - return new PriceImpl(productRetriever.fetchProduct().getPriceRange(), locale); + return new PriceImpl(fetchProduct().getPriceRange(), locale); } @Override public Boolean getInStock() { - return ProductStockStatus.IN_STOCK.equals(productRetriever.fetchProduct().getStockStatus()); + return ProductStockStatus.IN_STOCK.equals(fetchProduct().getStockStatus()); } @Override public Boolean isConfigurable() { if (configurable == null) { - configurable = productRetriever != null && productRetriever.fetchProduct() instanceof ConfigurableProduct; + configurable = fetchProduct() instanceof ConfigurableProduct; } return configurable; } @@ -243,7 +295,7 @@ public Boolean isConfigurable() { @Override public Boolean isGroupedProduct() { if (isGroupedProduct == null) { - isGroupedProduct = productRetriever != null && productRetriever.fetchProduct() instanceof GroupedProduct; + isGroupedProduct = fetchProduct() instanceof GroupedProduct; } return isGroupedProduct; } @@ -251,7 +303,7 @@ public Boolean isGroupedProduct() { @Override public Boolean isVirtualProduct() { if (isVirtualProduct == null) { - isVirtualProduct = productRetriever != null && productRetriever.fetchProduct() instanceof VirtualProduct; + isVirtualProduct = fetchProduct() instanceof VirtualProduct; } return isVirtualProduct; } @@ -259,7 +311,7 @@ public Boolean isVirtualProduct() { @Override public Boolean isBundleProduct() { if (isBundleProduct == null) { - isBundleProduct = productRetriever != null && productRetriever.fetchProduct() instanceof BundleProduct; + isBundleProduct = fetchProduct() instanceof BundleProduct; } return isBundleProduct; } @@ -267,7 +319,7 @@ public Boolean isBundleProduct() { @Override public Boolean isGiftCardProduct() { if (isGiftCardProduct == null) { - isGiftCardProduct = productRetriever != null && productRetriever.fetchProduct() instanceof GiftCardProduct; + isGiftCardProduct = fetchProduct() instanceof GiftCardProduct; } return isGiftCardProduct; } @@ -290,7 +342,7 @@ public List getVariants() { if (!isConfigurable()) { return Collections.emptyList(); } - ConfigurableProduct product = (ConfigurableProduct) productRetriever.fetchProduct(); + ConfigurableProduct product = (ConfigurableProduct) fetchProduct(); return product.getVariants().parallelStream().map(this::mapVariant).collect(Collectors.toList()); } @@ -301,7 +353,7 @@ public List getGroupedProductItems() { if (!isGroupedProduct()) { return Collections.emptyList(); } - GroupedProduct product = (GroupedProduct) productRetriever.fetchProduct(); + GroupedProduct product = (GroupedProduct) fetchProduct(); return product.getItems() .parallelStream() @@ -312,7 +364,7 @@ public List getGroupedProductItems() { @Override public List getAssets() { - return filterAndSortAssets(productRetriever.fetchProduct().getMediaGallery()); + return filterAndSortAssets(fetchProduct().getMediaGallery()); } @Override @@ -334,7 +386,7 @@ public List getVariantAttributes() { return Collections.emptyList(); } - ConfigurableProduct product = (ConfigurableProduct) productRetriever.fetchProduct(); + ConfigurableProduct product = (ConfigurableProduct) fetchProduct(); List optionList = new ArrayList<>(); for (ConfigurableProductOptions option : product.getConfigurableOptions()) { @@ -346,7 +398,9 @@ public List getVariantAttributes() { @Override public Boolean loadClientPrice() { - return loadClientPrice && !LaunchUtils.isLaunchBasedPath(currentPage.getPath()); + // set usePlaceholderData as side effect of fetchProduct(), usually a noop if any of the other methods was called before + fetchProduct(); + return loadClientPrice && !usePlaceholderData && !LaunchUtils.isLaunchBasedPath(currentPage.getPath()); } @Override @@ -468,17 +522,17 @@ protected String safeDescription(ProductInterface product) { @Override public String getMetaDescription() { - return productRetriever.fetchProduct().getMetaDescription(); + return fetchProduct().getMetaDescription(); } @Override public String getMetaKeywords() { - return productRetriever.fetchProduct().getMetaKeyword(); + return fetchProduct().getMetaKeyword(); } @Override public String getMetaTitle() { - return StringUtils.defaultString(productRetriever.fetchProduct().getMetaTitle(), getName()); + return StringUtils.defaultString(fetchProduct().getMetaTitle(), getName()); } @Override @@ -488,7 +542,7 @@ public String getCanonicalUrl() { return null; } if (canonicalUrl == null) { - ProductInterface product = productRetriever != null ? productRetriever.fetchProduct() : null; + ProductInterface product = fetchProduct(); SitemapLinkExternalizerProvider sitemapLinkExternalizerProvider = sling .getService(SitemapLinkExternalizerProvider.class); @@ -562,13 +616,13 @@ public String getDataLayerDescription() { @Override public CategoryData[] getDataLayerCategories() { - List productCategories = productRetriever.fetchProduct().getCategories(); + List productCategories = fetchProduct().getCategories(); if (productCategories == null || productCategories.size() == 0) { return new CategoryData[0]; } - return productRetriever.fetchProduct().getCategories() + return fetchProduct().getCategories() .stream() .map(c -> new CategoryDataImpl(c.getUid().toString(), c.getName(), c.getImage())) .toArray(CategoryData[]::new); @@ -581,7 +635,7 @@ public AssetData[] getDataLayerAssets() { @Override public ProductStorefrontContext getStorefrontContext() { - return new ProductStorefrontContextImpl(productRetriever.fetchProduct(), resource); + return new ProductStorefrontContextImpl(fetchProduct(), resource); } @Override diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/product/ProductPlaceholderRetriever.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/product/ProductPlaceholderRetriever.java deleted file mode 100644 index eb530c4c8b..0000000000 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/product/ProductPlaceholderRetriever.java +++ /dev/null @@ -1,45 +0,0 @@ -/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - ~ Copyright 2019 Adobe - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ -package com.adobe.cq.commerce.core.components.internal.models.v1.product; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Optional; - -import org.apache.commons.io.IOUtils; - -import com.adobe.cq.commerce.core.components.client.MagentoGraphqlClient; -import com.adobe.cq.commerce.core.components.models.retriever.AbstractProductRetriever; -import com.adobe.cq.commerce.magento.graphql.ProductInterfaceQueryDefinition; -import com.adobe.cq.commerce.magento.graphql.Query; -import com.adobe.cq.commerce.magento.graphql.gson.QueryDeserializer; - -class ProductPlaceholderRetriever extends AbstractProductRetriever { - - ProductPlaceholderRetriever(MagentoGraphqlClient client, String placeholderPath) throws IOException { - super(client); - - String json = IOUtils.toString(getClass().getClassLoader().getResourceAsStream(placeholderPath), StandardCharsets.UTF_8); - Query rootQuery = QueryDeserializer.getGson().fromJson(json, Query.class); - - product = Optional.of(rootQuery.getProducts().getItems().get(0)); - } - - @Override - protected ProductInterfaceQueryDefinition generateProductQuery() { - return null; - } -} diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/product/ProductRetriever.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/product/ProductRetriever.java index ff5dadf910..f0ff202a56 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/product/ProductRetriever.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/product/ProductRetriever.java @@ -62,11 +62,11 @@ private SimpleProductQueryDefinition generateSimpleProductQuery() { protected ProductInterfaceQueryDefinition generateProductQuery() { return q -> { q.sku() + .urlKey() .name() .description(d -> d.html()) .image(i -> i.label().url()) .thumbnail(t -> t.label().url()) - .urlKey() .stockStatus() .metaDescription() .metaKeyword() diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/productcarousel/ProductsRetriever.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/productcarousel/ProductsRetriever.java index f1222d4a44..bad4f338a5 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/productcarousel/ProductsRetriever.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/productcarousel/ProductsRetriever.java @@ -153,12 +153,12 @@ private boolean isCategoryQuery() { protected ProductInterfaceQueryDefinition generateProductQuery() { return q -> { q.sku() - .name() - .thumbnail(t -> t.label() - .url()) .urlKey() .urlPath() .urlRewrites(uq -> uq.url()) + .name() + .thumbnail(t -> t.label() + .url()) .priceRange(r -> r .minimumPrice(generatePriceQuery())) .onConfigurableProduct(cp -> { diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/productlist/CategoryPlaceholderRetriever.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/productlist/CategoryPlaceholderRetriever.java deleted file mode 100644 index cfa10cd5af..0000000000 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/productlist/CategoryPlaceholderRetriever.java +++ /dev/null @@ -1,44 +0,0 @@ -/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - ~ Copyright 2019 Adobe - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ -package com.adobe.cq.commerce.core.components.internal.models.v1.productlist; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Optional; - -import org.apache.commons.io.IOUtils; - -import com.adobe.cq.commerce.core.components.client.MagentoGraphqlClient; -import com.adobe.cq.commerce.core.components.models.retriever.AbstractCategoryRetriever; -import com.adobe.cq.commerce.magento.graphql.CategoryTreeQueryDefinition; -import com.adobe.cq.commerce.magento.graphql.Query; -import com.adobe.cq.commerce.magento.graphql.gson.QueryDeserializer; - -class CategoryPlaceholderRetriever extends AbstractCategoryRetriever { - CategoryPlaceholderRetriever(MagentoGraphqlClient client, String placeholderPath) throws IOException { - super(client); - - String json = IOUtils.toString(getClass().getClassLoader().getResourceAsStream(placeholderPath), StandardCharsets.UTF_8); - Query rootQuery = QueryDeserializer.getGson().fromJson(json, Query.class); - - category = Optional.of(rootQuery.getCategory()); - } - - @Override - protected CategoryTreeQueryDefinition generateCategoryQuery() { - return null; - } -} diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/productlist/ProductListImpl.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/productlist/ProductListImpl.java index 66149fabdc..f56a556b7a 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/productlist/ProductListImpl.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/productlist/ProductListImpl.java @@ -16,6 +16,8 @@ package com.adobe.cq.commerce.core.components.internal.models.v1.productlist; import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -24,13 +26,14 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.function.Consumer; +import java.util.function.UnaryOperator; import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.PostConstruct; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.sling.api.SlingHttpServletRequest; @@ -68,10 +71,12 @@ import com.adobe.cq.commerce.core.search.models.SearchAggregationOption; 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.CategoryFilterInput; import com.adobe.cq.commerce.magento.graphql.CategoryInterface; import com.adobe.cq.commerce.magento.graphql.CategoryProducts; import com.adobe.cq.commerce.magento.graphql.CategoryTree; -import com.adobe.cq.commerce.magento.graphql.ProductInterfaceQuery; +import com.adobe.cq.commerce.magento.graphql.Query; +import com.adobe.cq.commerce.magento.graphql.gson.QueryDeserializer; import com.adobe.cq.sightly.SightlyWCMMode; import com.adobe.granite.ui.components.ValueMapResourceWrapper; @@ -85,12 +90,33 @@ public class ProductListImpl extends ProductCollectionImpl implements ProductLis protected static final String PLACEHOLDER_DATA = "productlist-component-placeholder-data.json"; private static final Logger LOGGER = LoggerFactory.getLogger(ProductListImpl.class); - private static final boolean SHOW_TITLE_DEFAULT = true; private static final boolean SHOW_IMAGE_DEFAULT = true; private static final String CATEGORY_PROPERTY = "category"; static final String CATEGORY_AGGREGATION_ID = "category_id"; + static Optional PLACEHOLDER_CATEGORY; + + static CategoryInterface getPlaceholderCategory() { + if (PLACEHOLDER_CATEGORY == null) { + try { + InputStream data = ProductListImpl.class.getClassLoader().getResourceAsStream(PLACEHOLDER_DATA); + if (data != null) { + String json = IOUtils.toString(data, StandardCharsets.UTF_8); + Query rootQuery = QueryDeserializer.getGson().fromJson(json, Query.class); + PLACEHOLDER_CATEGORY = Optional.of(rootQuery.getCategory()); + } else { + LOGGER.warn("Could not find placeholder data on classpath: {}", PLACEHOLDER_DATA); + PLACEHOLDER_CATEGORY = Optional.empty(); + } + } catch (IOException ex) { + LOGGER.warn("Could not load placeholder data", ex); + PLACEHOLDER_CATEGORY = Optional.empty(); + } + } + return PLACEHOLDER_CATEGORY.orElse(null); + } + private boolean showTitle; private boolean showImage; @@ -112,10 +138,8 @@ public class ProductListImpl extends ProductCollectionImpl implements ProductLis private boolean usePlaceholderData; private boolean isAuthor; private String canonicalUrl; - private Pair categorySearchResultsSet; - - protected List fragments = new ArrayList<>(); + protected List fragments; @PostConstruct protected void initModel() { @@ -134,72 +158,31 @@ protected void initModel() { Map searchFilters = createFilterMap(request.getParameterMap()); - if (StringUtils.isBlank(categoryUid)) { - // If not provided via the category property extract category identifier from - // URL - categoryUid = urlProvider.getCategoryIdentifier(request); - } - if (magentoGraphqlClient != null) { if (StringUtils.isNotBlank(categoryUid)) { categoryRetriever = new CategoryRetriever(magentoGraphqlClient); categoryRetriever.setIdentifier(categoryUid); - } else if (isAuthor) { - usePlaceholderData = true; - loadClientPrice = false; - try { - categoryRetriever = new CategoryPlaceholderRetriever(magentoGraphqlClient, PLACEHOLDER_DATA); - } catch (IOException e) { - LOGGER.warn("Cannot use placeholder data", e); - } } else { - // There isn't any selector on publish instance - searchResultsSet = new SearchResultsSetImpl(); - categorySearchResultsSet = Pair.of(null, searchResultsSet); - return; - } - } - - if (usePlaceholderData) { - searchResultsSet = new SearchResultsSetImpl(); - } else { - Resource fragmentsNode = resource.getChild(ProductList.NN_FRAGMENTS); - if (fragmentsNode != null && fragmentsNode.hasChildren()) { - Iterable configuredFragments = fragmentsNode.getChildren(); - for (Resource fragment : configuredFragments) { - ValueMap fragmentVm = fragment.getValueMap(); - Integer fragmentPage = fragmentVm.get(PN_FRAGMENT_PAGE, -1); - if (fragmentPage.equals(currentPageIndex)) { - String fragmentCssClass = fragment.getValueMap().get(PN_FRAGMENT_CSS_CLASS, String.class); - ValueMapResourceWrapper resourceWrapper = new ValueMapResourceWrapper( - fragment, - CommerceExperienceFragmentImpl.RESOURCE_TYPE); - String fragmentLocation = fragment.getValueMap().get(PN_FRAGMENT_LOCATION, - String.class); - resourceWrapper.getValueMap().put(PN_FRAGMENT_LOCATION, fragmentLocation); - if (!fragmentsRetriever - .getExperienceFragmentsForCategory(categoryUid, fragmentLocation, currentPage) - .isEmpty()) { - fragments.add(new CommerceExperienceFragmentContainerImpl(resourceWrapper, - fragmentCssClass)); - } - } + UnaryOperator hook = urlProvider.getCategoryFilterHook(request); + if (hook != null) { + categoryRetriever = new CategoryRetriever(magentoGraphqlClient); + categoryRetriever.extendCategoryFilterWith(hook); } } + } - searchOptions = new SearchOptionsImpl(); - searchOptions.setCurrentPage(currentPageIndex); - searchOptions.setPageSize(navPageSize); - searchOptions.setAttributeFilters(searchFilters); + searchOptions = new SearchOptionsImpl(); + searchOptions.setCurrentPage(currentPageIndex); + searchOptions.setPageSize(navPageSize); + searchOptions.setAttributeFilters(searchFilters); - // configure sorting - String defaultSortField = properties.get(PN_DEFAULT_SORT_FIELD, String.class); - String defaultSortOrder = properties.get(PN_DEFAULT_SORT_ORDER, Sorter.Order.ASC.name()); + // configure sorting + String defaultSortField = properties.get(PN_DEFAULT_SORT_FIELD, String.class); + String defaultSortOrder = properties.get(PN_DEFAULT_SORT_ORDER, Sorter.Order.ASC.name()); - if (StringUtils.isNotBlank(defaultSortField)) { - Sorter.Order value = Sorter.Order.fromString(defaultSortOrder, Sorter.Order.ASC); - searchOptions.setDefaultSorter(defaultSortField, value); - } + if (StringUtils.isNotBlank(defaultSortField)) { + Sorter.Order value = Sorter.Order.fromString(defaultSortOrder, Sorter.Order.ASC); + searchOptions.setDefaultSorter(defaultSortField, value); } } @@ -234,23 +217,50 @@ public boolean showImage() { @Nonnull @Override public Collection getProducts() { - if (usePlaceholderData) { - CategoryInterface category = getCategory(); - CategoryProducts categoryProducts = category.getProducts(); - ProductToProductListItemConverter converter = new ProductToProductListItemConverter(currentPage, request, - urlProvider, getId(), - category); - return categoryProducts.getItems().stream() - .map(converter) - .filter(Objects::nonNull) // the converter returns null if the conversion fails - .collect(Collectors.toList()); - } else { - return getSearchResultsSet().getProductListItems(); - } + return getSearchResultsSet().getProductListItems(); } @Override public List getExperienceFragments() { + if (fragments == null) { + CategoryInterface categoryInterface = getCategory(); + + if (usePlaceholderData || categoryInterface == null) { + // when using placeholder data or when the category does not exist / the category uid is unknown, we can skip the logic + // to get the fragments completely + fragments = Collections.emptyList(); + return fragments; + } + + Resource fragmentsNode = resource.getChild(ProductList.NN_FRAGMENTS); + int currentPageIndex = searchOptions.getCurrentPage(); + String categoryUidToSearchFor = categoryInterface.getUid().toString(); + fragments = new ArrayList<>(); + + if (fragmentsNode != null && fragmentsNode.hasChildren()) { + Iterable configuredFragments = fragmentsNode.getChildren(); + for (Resource fragment : configuredFragments) { + ValueMap fragmentVm = fragment.getValueMap(); + Integer fragmentPage = fragmentVm.get(PN_FRAGMENT_PAGE, -1); + if (fragmentPage.equals(currentPageIndex)) { + String fragmentCssClass = fragment.getValueMap().get(PN_FRAGMENT_CSS_CLASS, String.class); + ValueMapResourceWrapper resourceWrapper = new ValueMapResourceWrapper( + fragment, + CommerceExperienceFragmentImpl.RESOURCE_TYPE); + String fragmentLocation = fragment.getValueMap().get(PN_FRAGMENT_LOCATION, + String.class); + resourceWrapper.getValueMap().put(PN_FRAGMENT_LOCATION, fragmentLocation); + if (!fragmentsRetriever + .getExperienceFragmentsForCategory(categoryUidToSearchFor, fragmentLocation, currentPage) + .isEmpty()) { + fragments.add(new CommerceExperienceFragmentContainerImpl(resourceWrapper, + fragmentCssClass)); + } + } + } + } + } + return fragments; } @@ -331,22 +341,60 @@ private void processCategoryAggregation(List searchAggregatio private Pair getCategorySearchResultsSet() { if (categorySearchResultsSet == null) { - Consumer productQueryHook = categoryRetriever != null - ? categoryRetriever.getProductQueryHook() - : null; - categorySearchResultsSet = searchResultsService - .performSearch(searchOptions, resource, currentPage, request, productQueryHook, categoryRetriever); + if (categoryRetriever != null) { + // the retriever may be null, for example if there is no category information in the url + categorySearchResultsSet = searchResultsService.performSearch(searchOptions, resource, currentPage, request, + categoryRetriever.getProductQueryHook(), categoryRetriever); + } + if (categorySearchResultsSet == null || categorySearchResultsSet.getLeft() == null) { + // category not found + setPlaceholderData(); + } + if (categorySearchResultsSet == null) { + // fallback + categorySearchResultsSet = Pair.of(null, new SearchResultsSetImpl()); + } } return categorySearchResultsSet; } - protected CategoryInterface getCategory() { - if (usePlaceholderData) { - return categoryRetriever.fetchCategory(); + private void setPlaceholderData() { + // this logic is to preserve existing behaviour. we could show placeholder data also if there is no gql client + if (isAuthor && magentoGraphqlClient != null) { + usePlaceholderData = true; + + CategoryInterface placeholderCategory = getPlaceholderCategory(); + + if (placeholderCategory != null) { + SearchResultsSetImpl searchResultsSetImpl = new SearchResultsSetImpl(); + CategoryProducts categoryProducts = placeholderCategory.getProducts(); + ProductToProductListItemConverter converter = new ProductToProductListItemConverter(currentPage, request, + urlProvider, getId(), + placeholderCategory); + List productListItems = categoryProducts.getItems().stream() + .map(converter) + .filter(Objects::nonNull) // the converter returns null if the conversion fails + .collect(Collectors.toList()); + + searchResultsSetImpl.setProductListItems(productListItems); + searchResultsSet = searchResultsSetImpl; + categorySearchResultsSet = Pair.of(placeholderCategory, searchResultsSetImpl); + } } + } + + protected CategoryInterface getCategory() { return getCategorySearchResultsSet().getLeft(); } + @Override + public boolean loadClientPrice() { + // make sure we query first to see if we show placeholder data or not + // usePlaceholderData is set as side effect of getCategorySearchResultsSet + getCategorySearchResultsSet(); + return !usePlaceholderData && super.loadClientPrice(); + } + @Override public AbstractCategoryRetriever getCategoryRetriever() { return categoryRetriever; diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/relatedproducts/RelatedProductsImpl.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/relatedproducts/RelatedProductsImpl.java index 9cf0fbeaa4..1f9e06bf2d 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/relatedproducts/RelatedProductsImpl.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/relatedproducts/RelatedProductsImpl.java @@ -76,16 +76,12 @@ public class RelatedProductsImpl extends ProductCarouselBase { @ScriptVariable private ValueMap properties; - private AbstractProductsRetriever productsRetriever; + private RelatedProductsRetriever productsRetriever; private RelationType relationType; private String productSku; @PostConstruct private void initModel() { - if (!isConfigured()) { - return; - } - if (magentoGraphqlClient == null) { LOGGER.error("Cannot get a GraphqlClient using the resource at {}", resource.getPath()); } else { @@ -95,29 +91,28 @@ private void initModel() { @Override public boolean isConfigured() { - return properties.get(PN_PRODUCT, String.class) != null - || urlProvider.getProductIdentifier(request) != null; + return productSku != null || (productsRetriever != null && productsRetriever.fetchSku() != null); } private void configureProductsRetriever() { String relationTypeProperty = properties.get(PN_RELATION_TYPE, String.class); String product = properties.get(PN_PRODUCT, String.class); + relationType = relationTypeProperty != null ? RelationType.valueOf(relationTypeProperty) : RelationType.RELATED_PRODUCTS; + productsRetriever = new RelatedProductsRetriever(magentoGraphqlClient, relationType); + if (StringUtils.isNotBlank(product)) { productSku = product; // The picker is configured to return the SKU + productsRetriever.setIdentifiers(Collections.singletonList(productSku)); } else { - productSku = urlProvider.getProductIdentifier(request); + productsRetriever.extendProductFilterWith(urlProvider.getProductFilterHook(request)); } - - relationType = relationTypeProperty != null ? RelationType.valueOf(relationTypeProperty) : RelationType.RELATED_PRODUCTS; - productsRetriever = new RelatedProductsRetriever(magentoGraphqlClient, relationType); - productsRetriever.setIdentifiers(Collections.singletonList(productSku)); } @Override @JsonIgnore public List getProducts() { - if (productsRetriever == null || !isConfigured()) { + if (!isConfigured()) { return Collections.emptyList(); } @@ -184,6 +179,9 @@ public RelationType getRelationType() { */ @JsonSerialize(as = CommerceIdentifier.class) public CommerceIdentifier getCommerceIdentifier() { + if (productSku == null) { + productSku = productsRetriever.fetchSku(); + } return new ListItemIdentifier(productSku); } } diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/relatedproducts/RelatedProductsRetriever.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/relatedproducts/RelatedProductsRetriever.java index fb1a83b28b..fbfd70fbb5 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/relatedproducts/RelatedProductsRetriever.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/relatedproducts/RelatedProductsRetriever.java @@ -16,6 +16,7 @@ package com.adobe.cq.commerce.core.components.internal.models.v1.relatedproducts; import java.util.List; +import java.util.Optional; import com.adobe.cq.commerce.core.components.client.MagentoGraphqlClient; import com.adobe.cq.commerce.core.components.models.retriever.AbstractProductsRetriever; @@ -47,6 +48,7 @@ String getText() { } private RelationType relationtype; + private Optional fetchedProduct; RelatedProductsRetriever(MagentoGraphqlClient client, RelationType relationType) { super(client); @@ -55,8 +57,9 @@ String getText() { @Override protected String generateQuery(List identifiers) { + String firstIdentifier = identifiers != null && !identifiers.isEmpty() ? identifiers.get(0) : null; ProductAttributeFilterInput filter = new ProductAttributeFilterInput(); - FilterEqualTypeInput skuFilter = new FilterEqualTypeInput().setEq(identifiers.get(0)); + FilterEqualTypeInput skuFilter = new FilterEqualTypeInput().setEq(firstIdentifier); filter.setSku(skuFilter); // Apply product attribute filter hook @@ -69,25 +72,34 @@ protected String generateQuery(List identifiers) { ProductInterfaceQueryDefinition def; if (RelationType.UPSELL_PRODUCTS.equals(relationtype)) { - def = p -> p.upsellProducts(generateProductQuery()); + def = p -> p.sku().upsellProducts(generateProductQuery()); } else if (RelationType.CROSS_SELL_PRODUCTS.equals(relationtype)) { - def = p -> p.crosssellProducts(generateProductQuery()); + def = p -> p.sku().crosssellProducts(generateProductQuery()); } else { - def = p -> p.relatedProducts(generateProductQuery()); + def = p -> p.sku().relatedProducts(generateProductQuery()); } ProductsQueryDefinition queryArgs = q -> q.items(def); return Operations.query(query -> query.products(searchArgs, queryArgs)).toString(); } + public String fetchSku() { + if (fetchedProduct == null) { + populate(); + } + return fetchedProduct.map(ProductInterface::getSku).orElse(null); + } + @Override protected void populate() { super.populate(); if (products == null || products.isEmpty()) { + fetchedProduct = Optional.empty(); return; } ProductInterface product = products.get(0); + fetchedProduct = Optional.of(product); if (RelationType.UPSELL_PRODUCTS.equals(relationtype)) { products = product.getUpsellProducts(); @@ -102,11 +114,11 @@ protected void populate() { protected ProductInterfaceQueryDefinition generateProductQuery() { return q -> { q.sku() - .name() - .thumbnail(t -> t.label().url()) .urlKey() .urlPath() .urlRewrites(uq -> uq.url()) + .name() + .thumbnail(t -> t.label().url()) .priceRange(r -> r .minimumPrice(generatePriceQuery())) .onConfigurableProduct(cp -> cp diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/services/UrlProviderImpl.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/services/UrlProviderImpl.java index cad0d80908..a3bbe66a58 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/services/UrlProviderImpl.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/services/UrlProviderImpl.java @@ -20,6 +20,7 @@ import java.util.Map; import java.util.function.BiFunction; import java.util.function.Function; +import java.util.function.UnaryOperator; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -60,7 +61,10 @@ import com.adobe.cq.commerce.core.components.services.urls.ProductUrlFormat; import com.adobe.cq.commerce.core.components.services.urls.UrlFormat; import com.adobe.cq.commerce.core.components.services.urls.UrlProvider; +import com.adobe.cq.commerce.magento.graphql.CategoryFilterInput; import com.adobe.cq.commerce.magento.graphql.CategoryInterface; +import com.adobe.cq.commerce.magento.graphql.FilterEqualTypeInput; +import com.adobe.cq.commerce.magento.graphql.ProductAttributeFilterInput; import com.adobe.cq.commerce.magento.graphql.ProductInterface; import com.adobe.cq.dam.cfm.content.FragmentRenderService; import com.day.cq.wcm.api.Page; @@ -480,6 +484,24 @@ public String getProductIdentifier(SlingHttpServletRequest request) { return identifier; } + @Override + public UnaryOperator getProductFilterHook(SlingHttpServletRequest request) { + Page page = getCurrentPage(request); + ProductUrlFormat format = getProductUrlFormatFromContext(request, page); + ProductUrlFormat.Params params = format.parse(request.getRequestPathInfo(), request.getRequestParameterMap()); + + if (StringUtils.isNotEmpty(params.getSku())) { + FilterEqualTypeInput eq = new FilterEqualTypeInput().setEq(params.getSku()); + return input -> new ProductAttributeFilterInput().setSku(eq); + } else if (StringUtils.isNotEmpty(params.getUrlKey())) { + FilterEqualTypeInput eq = new FilterEqualTypeInput().setEq(params.getUrlKey()); + return input -> new ProductAttributeFilterInput().setUrlKey(eq); + } else { + // no usable filter input known + return null; + } + } + @Override public String getCategoryIdentifier(SlingHttpServletRequest request) { String identifier = getIdentifierFromRequest(request); @@ -529,6 +551,24 @@ public String getCategoryIdentifier(SlingHttpServletRequest request) { return identifier; } + @Override + public UnaryOperator getCategoryFilterHook(SlingHttpServletRequest request) { + Page page = getCurrentPage(request); + CategoryUrlFormat format = getCategoryUrlFormatFromContext(request, page); + CategoryUrlFormat.Params params = format.parse(request.getRequestPathInfo(), request.getRequestParameterMap()); + + if (StringUtils.isNotEmpty(params.getUid())) { + return input -> new CategoryFilterInput().setCategoryUid(new FilterEqualTypeInput().setEq(params.getUid())); + } else if (StringUtils.isNotEmpty(params.getUrlPath())) { + return input -> new CategoryFilterInput().setUrlPath(new FilterEqualTypeInput().setEq(params.getUrlPath())); + } else if (StringUtils.isNotEmpty(params.getUrlKey())) { + return input -> new CategoryFilterInput().setUrlKey(new FilterEqualTypeInput().setEq(params.getUrlKey())); + } else { + // unusable filter input + return null; + } + } + /** * When the FragmentRenderService executes an internal request it passes its * configuration as attribute to the internal request. diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/servlets/CatalogPageNotFoundFilter.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/servlets/CatalogPageNotFoundFilter.java index 0651fca740..196b745306 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/servlets/CatalogPageNotFoundFilter.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/servlets/CatalogPageNotFoundFilter.java @@ -120,7 +120,9 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo ProductList productList = commerceModelFinder.findProductListComponentModel(slingRequest, currentPage.getContentResource()); if (productList != null) { AbstractCategoryRetriever categoryRetriever = productList.getCategoryRetriever(); - if (categoryRetriever == null || categoryRetriever.fetchCategory() == null) { + // since CIF-2916 the categoryRetriever is null when using the placeholder data, however the product list still + // returns the placeholder products and so we check additionally if the list is empty. + if ((categoryRetriever == null || categoryRetriever.fetchCategory() == null) && productList.getProducts().isEmpty()) { slingResponse.sendError(HttpServletResponse.SC_NOT_FOUND, "Category not found"); return; } diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/services/urls/UrlProvider.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/services/urls/UrlProvider.java index 7ebd22d456..eb8c8df2a1 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/services/urls/UrlProvider.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/services/urls/UrlProvider.java @@ -17,12 +17,16 @@ import java.util.HashMap; import java.util.Map; +import java.util.function.Function; +import java.util.function.UnaryOperator; import javax.annotation.Nullable; import org.apache.sling.api.SlingHttpServletRequest; import org.osgi.annotation.versioning.ProviderType; +import com.adobe.cq.commerce.magento.graphql.CategoryFilterInput; +import com.adobe.cq.commerce.magento.graphql.ProductAttributeFilterInput; import com.day.cq.wcm.api.Page; @ProviderType @@ -88,7 +92,7 @@ public interface UrlProvider { * Either {@code request} or {@code page} parameter can be * null but not both. * If both are null an {@link IllegalArgumentException} is thrown. - * + * * @param request The current Sling HTTP request. * @param page This parameter can be null if the URL template does set a * {{page}} parameter and a request is given. @@ -112,7 +116,7 @@ public interface UrlProvider { * Either {@code request} or {@code page} parameter can be * null but not both. * If both are null an {@link IllegalArgumentException} is thrown. - * + * * @param request The current Sling HTTP request. * @param page This parameter can be null if the URL template does set a * {{page}} parameter and a request is given. @@ -214,6 +218,19 @@ public interface UrlProvider { */ String getProductIdentifier(SlingHttpServletRequest request); + /** + * Returns a hook that replaces a given {@link ProductAttributeFilterInput} with a new instance constructed from the identifiers + * available by the given request. + *

+ * The hook can be passed to + * {@link com.adobe.cq.commerce.core.components.models.retriever.AbstractProductRetriever#extendProductFilterWith(Function)} or + * {@link com.adobe.cq.commerce.core.components.models.retriever.AbstractProductsRetriever#extendProductFilterWith(Function)}. + * + * @param request the current request + * @return a unary operator that excepts a {@link ProductAttributeFilterInput} and returns a new instance to replace it + */ + UnaryOperator getProductFilterHook(SlingHttpServletRequest request); + /** * Parses and returns the {@link ProductUrlFormat.Params} used in the given {@link SlingHttpServletRequest} based on the URLProvider * configuration for product page URLs. @@ -231,6 +248,19 @@ public interface UrlProvider { */ String getCategoryIdentifier(SlingHttpServletRequest request); + /** + * Returns a hook that replaces a given {@link CategoryFilterInput} with a new instance constructed from the identifiers available by + * the given request. + *

+ * The hook can be passed to + * {@link com.adobe.cq.commerce.core.components.models.retriever.AbstractCategoryRetriever#extendCategoryFilterWith(Function)} or + * {@link com.adobe.cq.commerce.core.components.models.retriever.AbstractCategoriesRetriever#extendCategoryFilterWith(Function)}. + * + * @param request the current request + * @return a unary operator that excepts a {@link ProductAttributeFilterInput} and returns a new instance to replace it + */ + UnaryOperator getCategoryFilterHook(SlingHttpServletRequest request); + /** * Parses and returns the {@link CategoryUrlFormat.Params} used in the given Sling HTTP request based on the URLProvider configuration * for category page URLs. diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/services/urls/package-info.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/services/urls/package-info.java index 3dae0cd927..53d4c82e0f 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/services/urls/package-info.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/services/urls/package-info.java @@ -13,7 +13,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ -@Version("1.2.0") +@Version("1.3.0") package com.adobe.cq.commerce.core.components.services.urls; import org.osgi.annotation.versioning.Version; \ No newline at end of file diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/internal/services/SearchResultsServiceImpl.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/internal/services/SearchResultsServiceImpl.java index 67fae9c0cc..420f002c2e 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/internal/services/SearchResultsServiceImpl.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/internal/services/SearchResultsServiceImpl.java @@ -153,7 +153,12 @@ public Pair performSearch( categoryRetriever.setIdentifier(mutableSearchOptions.getCategoryUid().get()); } category = categoryRetriever.fetchCategory(); - if (category != null && category.getUid() != null) { + if (category == null) { + // category not found + LOGGER.debug("Category not found."); + return new ImmutablePair<>(null, searchResultsSet); + } + if (category.getUid() != null) { mutableSearchOptions.setCategoryUid(category.getUid().toString()); } } @@ -417,11 +422,11 @@ private ProductInterfaceQueryDefinition generateProductQuery( final Consumer productQueryHook) { return (ProductInterfaceQuery q) -> { q.sku() - .name() - .smallImage(i -> i.url()) .urlKey() .urlPath() .urlRewrites(uq -> uq.url()) + .name() + .smallImage(i -> i.url()) .priceRange(r -> r .minimumPrice(generatePriceQuery())) .onConfigurableProduct(cp -> cp diff --git a/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/client/MagentoGraphqlClientImplTest.java b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/client/MagentoGraphqlClientImplTest.java index 0a8d9e6e2d..4461d79025 100644 --- a/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/client/MagentoGraphqlClientImplTest.java +++ b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/client/MagentoGraphqlClientImplTest.java @@ -40,6 +40,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatcher; import org.mockito.Mockito; +import org.mockito.internal.verification.AtMost; import com.adobe.cq.commerce.core.components.client.MagentoGraphqlClient; import com.adobe.cq.commerce.core.components.internal.services.ComponentsConfigurationAdapterFactory; @@ -66,6 +67,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.doThrow; @@ -157,6 +159,37 @@ public void testCustomHeaders() { hasItems("value1", "value2", "value3", "=value3=3=3=3=3")); } + @Test + public void testLocalCacheExactMatch() { + context.currentPage("/content/pageD"); + MagentoGraphqlClient client = new MagentoGraphqlClientImpl(context.currentResource(), null, context.request()); + GraphqlResponse expected = mock(GraphqlResponse.class); + when(graphqlClient.execute(any(), any(), any(), any())).thenReturn(expected); + + // execute the same query twice + GraphqlResponse resp1 = client.execute("{dummy}"); + GraphqlResponse resp2 = client.execute("{dummy}"); + + verify(graphqlClient, new AtMost(1)).execute(any(), any(), any(), any()); + assertSame(resp1, resp2); + } + + @Test + public void testLocalCacheFuzzyMatch() { + context.currentPage("/content/pageD"); + MagentoGraphqlClient client = new MagentoGraphqlClientImpl(context.currentResource(), null, context.request()); + GraphqlResponse expected = mock(GraphqlResponse.class); + when(graphqlClient.execute(any(), any(), any(), any())).thenReturn(expected); + + // execute the same query twice + GraphqlResponse resp1 = client.execute( + "{products(filter:{sku:{eq:\"ABC\"}}){items{__typename,sku,name,url_key,price_range{minimum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}}}"); + GraphqlResponse resp2 = client.execute("{products(filter:{sku:{eq:\"ABC\"}}){items{__typename,sku}}"); + + verify(graphqlClient, new AtMost(1)).execute(any(), any(), any(), any()); + assertSame(resp1, resp2); + } + @Test public void testMagentoStorePropertyWithConfigBuilder() { Page pageWithConfig = spy(context.pageManager().getPage(PAGE_A)); diff --git a/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/breadcrumb/BreadcrumbImplTest.java b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/breadcrumb/BreadcrumbImplTest.java index eaeb5005df..f49fe54fca 100644 --- a/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/breadcrumb/BreadcrumbImplTest.java +++ b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/breadcrumb/BreadcrumbImplTest.java @@ -293,10 +293,8 @@ public void testProductSpecificPageOnLaunch() throws Exception { @Test public void testCategoryPage() throws Exception { - Utils.setupHttpResponse("graphql/magento-graphql-category-uid.json", httpClient, HttpStatus.SC_OK, - "{categoryList(filters:{url_path"); Utils.setupHttpResponse("graphql/magento-graphql-category-breadcrumb-result.json", httpClient, HttpStatus.SC_OK, - "{categoryList(filters:{category_uid"); + "{categoryList(filters:{url_path"); prepareModel("/content/venia/us/en/products/category-page"); MockRequestPathInfo requestPathInfo = (MockRequestPathInfo) context.request().getRequestPathInfo(); @@ -317,10 +315,8 @@ public void testCategoryPage() throws Exception { @Test public void testCategorySpecificPage() throws Exception { - Utils.setupHttpResponse("graphql/magento-graphql-category-uid.json", httpClient, HttpStatus.SC_OK, - "{categoryList(filters:{url_path"); Utils.setupHttpResponse("graphql/magento-graphql-category-breadcrumb-result.json", httpClient, HttpStatus.SC_OK, - "{categoryList(filters:{category_uid"); + "{categoryList(filters:{url_path"); prepareModel("/content/venia/us/en/products/category-page/category-specific-page"); // TODO: CIF-2469 @@ -399,7 +395,7 @@ public void testNoGraphqlClient() throws Exception { @Test public void testGraphqlClientError() throws Exception { Utils.setupHttpResponse("graphql/magento-graphql-product-result.json", httpClient, HttpStatus.SC_OK, "{products(filter:{url_key"); - Utils.setupHttpErrorResponse(httpClient, 404, "{products(filter:{sku"); + Utils.setupHttpErrorResponse(httpClient, 404, "{products(filter:{url_key"); prepareModel("/content/venia/us/en/products/product-page"); MockRequestPathInfo requestPathInfo = (MockRequestPathInfo) context.request().getRequestPathInfo(); diff --git a/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/breadcrumb/BreadcrumbRetrieverTest.java b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/breadcrumb/BreadcrumbRetrieverTest.java index 8c2c3fbb45..04273a05d3 100644 --- a/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/breadcrumb/BreadcrumbRetrieverTest.java +++ b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/breadcrumb/BreadcrumbRetrieverTest.java @@ -25,6 +25,7 @@ import com.adobe.cq.commerce.core.components.client.MagentoGraphqlClient; import com.adobe.cq.commerce.core.testing.Utils; import com.adobe.cq.commerce.graphql.client.GraphqlResponse; +import com.adobe.cq.commerce.magento.graphql.FilterEqualTypeInput; import com.adobe.cq.commerce.magento.graphql.Query; import com.adobe.cq.commerce.magento.graphql.gson.Error; import com.adobe.cq.commerce.magento.graphql.gson.QueryDeserializer; @@ -55,7 +56,7 @@ public void testProductQueryWithSku() throws Exception { BreadcrumbRetriever retriever = new BreadcrumbRetriever(client); - retriever.setProductIdentifier(testSKU); + retriever.setProductIdentifierHook(input -> input.setSku(new FilterEqualTypeInput().setEq(testSKU))); retriever.fetchCategoriesBreadcrumbs(); retriever.fetchCategoriesBreadcrumbs(); retriever.fetchProduct(); diff --git a/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/page/PageMetadataImplTest.java b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/page/PageMetadataImplTest.java index 869b1bcd28..14016817ca 100644 --- a/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/page/PageMetadataImplTest.java +++ b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/page/PageMetadataImplTest.java @@ -144,7 +144,7 @@ public void testPageMetadataModelOnProductPage() throws Exception { assertFalse("The product doesn't have staged data", productModel.isStaged()); // Verify that GraphQL client is only called once, so Sling model caching works as expected - verify(graphqlClient, times(2)).execute(any(), any(), any(), any()); + verify(graphqlClient, times(1)).execute(any(), any(), any(), any()); verify(graphqlClient, never()).execute(any(), any(), any()); // Asserts that the right product resource is used when PageMetadataImpl adapts the request to the Product component @@ -220,7 +220,7 @@ public void testPageMetadataModelOnCategoryPage() throws Exception { // Verify that GraphQL client is only called 5 times, so Sling model caching works as expected // --> see testPageMetadataModelOnCategoryPage(String) to see why we expect 5 queries - verify(graphqlClient, times(5)).execute(any(), any(), any(), any()); + verify(graphqlClient, times(4)).execute(any(), any(), any(), any()); verify(graphqlClient, never()).execute(any(), any(), any()); // Asserts that the right productlist resource is used when PageMetadataImpl adapts the request to the ProductList component @@ -273,10 +273,8 @@ private void testPageMetadataModelOnCategoryPage(String pagePath) throws Excepti Utils.setupHttpResponse("graphql/magento-graphql-introspection-result.json", httpClient, HttpStatus.SC_OK, "{__type"); Utils.setupHttpResponse("graphql/magento-graphql-attributes-result.json", httpClient, HttpStatus.SC_OK, "{customAttributeMetadata"); - Utils.setupHttpResponse("graphql/magento-graphql-category-uid.json", httpClient, HttpStatus.SC_OK, - "{categoryList(filters:{url_path"); Utils.setupHttpResponse("graphql/magento-graphql-search-category-result-category.json", httpClient, HttpStatus.SC_OK, - "{categoryList(filters:{category_uid"); + "{categoryList(filters:{url_path"); Utils.setupHttpResponse("graphql/magento-graphql-search-category-result-products.json", httpClient, HttpStatus.SC_OK, "{products"); diff --git a/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/product/ProductImplTest.java b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/product/ProductImplTest.java index f1cbb818ac..fe7d6930e2 100644 --- a/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/product/ProductImplTest.java +++ b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/product/ProductImplTest.java @@ -173,16 +173,15 @@ protected void adaptToProduct() { public void testGetIdentifierFromSelector() { adaptToProduct(); - String identifier = (String) Whitebox.getInternalState(productModel.getProductRetriever(), "identifier"); - assertEquals("MJ01", identifier); + assertEquals("MJ01", productModel.getSku()); } @Test public void testGetIdentifierFromProperty() { // Use different page - context.currentResource("/content/product-of-the-week"); context.request().setServletPath("/content/product-of-the-week/jcr:content/root/responsivegrid/product.beaumont-summit-kit.html"); productResource = context.resourceResolver().getResource("/content/product-of-the-week/jcr:content/root/responsivegrid/product"); + context.currentResource(productResource); // Update product properties in sling bindings SlingBindings slingBindings = (SlingBindings) context.request().getAttribute(SlingBindings.class.getName()); @@ -192,7 +191,7 @@ public void testGetIdentifierFromProperty() { adaptToProduct(); String sku = (String) Whitebox.getInternalState(productModel.getProductRetriever(), "identifier"); - assertEquals("MJ01", sku); + assertEquals("MJ02", sku); } @Test diff --git a/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/productcarousel/ProductsRetrieverTest.java b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/productcarousel/ProductsRetrieverTest.java index 0985948ebf..1875991171 100644 --- a/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/productcarousel/ProductsRetrieverTest.java +++ b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/productcarousel/ProductsRetrieverTest.java @@ -75,7 +75,7 @@ public void testCategoryListQuery() { final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); verify(mockClient, times(1)).execute(captor.capture()); - String expectedQuery = "{categoryList(filters:{category_uid:{eq:\"uid-1\"}}){uid,url_key,url_path,products(currentPage:1,pageSize:5){items{__typename,sku,name,thumbnail{label,url},url_key,url_path,url_rewrites{url},price_range{minimum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}},... on ConfigurableProduct{price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}},... on BundleProduct{price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}}}}}}"; + String expectedQuery = "{categoryList(filters:{category_uid:{eq:\"uid-1\"}}){uid,url_key,url_path,products(currentPage:1,pageSize:5){items{__typename,sku,url_key,url_path,url_rewrites{url},name,thumbnail{label,url},price_range{minimum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}},... on ConfigurableProduct{price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}},... on BundleProduct{price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}}}}}}"; Assert.assertEquals(expectedQuery, captor.getValue()); } @@ -89,7 +89,7 @@ public void testExtendedProductQuery() { final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); verify(mockClient, times(1)).execute(captor.capture()); - String expectedQuery = "{products(filter:{sku:{in:[\"sku-a\",\"sku-b\"]}}){items{__typename,sku,name,thumbnail{label,url},url_key,url_path,url_rewrites{url},price_range{minimum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}},... on ConfigurableProduct{variants{product{sku,name,thumbnail{label,url},price_range{minimum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}}},price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}},... on BundleProduct{price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}},created_at,is_returnable_custom_:is_returnable}}}"; + String expectedQuery = "{products(filter:{sku:{in:[\"sku-a\",\"sku-b\"]}}){items{__typename,sku,url_key,url_path,url_rewrites{url},name,thumbnail{label,url},price_range{minimum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}},... on ConfigurableProduct{variants{product{sku,name,thumbnail{label,url},price_range{minimum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}}},price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}},... on BundleProduct{price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}},created_at,is_returnable_custom_:is_returnable}}}"; Assert.assertEquals(expectedQuery, captor.getValue()); } @@ -103,7 +103,7 @@ public void testExtendedVariantQuery() { final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); verify(mockClient, times(1)).execute(captor.capture()); - String expectedQuery = "{products(filter:{sku:{in:[\"sku-a\",\"sku-b\"]}}){items{__typename,sku,name,thumbnail{label,url},url_key,url_path,url_rewrites{url},price_range{minimum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}},... on ConfigurableProduct{variants{product{sku,name,thumbnail{label,url},price_range{minimum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}},weight,volume_custom_:volume}},price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}},... on BundleProduct{price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}}}}}"; + String expectedQuery = "{products(filter:{sku:{in:[\"sku-a\",\"sku-b\"]}}){items{__typename,sku,url_key,url_path,url_rewrites{url},name,thumbnail{label,url},price_range{minimum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}},... on ConfigurableProduct{variants{product{sku,name,thumbnail{label,url},price_range{minimum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}},weight,volume_custom_:volume}},price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}},... on BundleProduct{price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}}}}}"; Assert.assertEquals(expectedQuery, captor.getValue()); } @@ -118,7 +118,7 @@ public void testExtendedCategoryListQuery() { final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); verify(mockClient, times(1)).execute(captor.capture()); - String expectedQuery = "{categoryList(filters:{category_uid:{eq:\"uid-1\"}}){uid,url_key,url_path,products(currentPage:1,pageSize:5){items{__typename,sku,name,thumbnail{label,url},url_key,url_path,url_rewrites{url},price_range{minimum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}},... on ConfigurableProduct{price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}},... on BundleProduct{price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}},created_at,is_returnable_custom_:is_returnable}}}}"; + String expectedQuery = "{categoryList(filters:{category_uid:{eq:\"uid-1\"}}){uid,url_key,url_path,products(currentPage:1,pageSize:5){items{__typename,sku,url_key,url_path,url_rewrites{url},name,thumbnail{label,url},price_range{minimum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}},... on ConfigurableProduct{price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}},... on BundleProduct{price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}},created_at,is_returnable_custom_:is_returnable}}}}"; Assert.assertEquals(expectedQuery, captor.getValue()); } @@ -130,7 +130,7 @@ public void testExtendCategoryFilterWithHook() { String query = retriever.generateQuery(Collections.singletonList("abc")); Assert.assertEquals( - "{categoryList(filters:{category_uid:{eq:\"uid-1\"},name:{match:\"my-name\"}}){uid,url_key,url_path,products(currentPage:1,pageSize:5){items{__typename,sku,name,thumbnail{label,url},url_key,url_path,url_rewrites{url},price_range{minimum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}},... on ConfigurableProduct{price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}},... on BundleProduct{price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}}}}}}", + "{categoryList(filters:{category_uid:{eq:\"uid-1\"},name:{match:\"my-name\"}}){uid,url_key,url_path,products(currentPage:1,pageSize:5){items{__typename,sku,url_key,url_path,url_rewrites{url},name,thumbnail{label,url},price_range{minimum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}},... on ConfigurableProduct{price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}},... on BundleProduct{price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}}}}}}", query); } @@ -143,7 +143,7 @@ public void testReplaceAndExtendCategoryFilterWithHook() { String query = retriever.generateQuery(Collections.singletonList("abc")); Assert.assertEquals( - "{categoryList(filters:{name:{match:\"my-name\"},url_path:{eq:\"a/b/my-category\"}}){uid,url_key,url_path,products(currentPage:1,pageSize:5){items{__typename,sku,name,thumbnail{label,url},url_key,url_path,url_rewrites{url},price_range{minimum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}},... on ConfigurableProduct{price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}},... on BundleProduct{price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}}}}}}", + "{categoryList(filters:{name:{match:\"my-name\"},url_path:{eq:\"a/b/my-category\"}}){uid,url_key,url_path,products(currentPage:1,pageSize:5){items{__typename,sku,url_key,url_path,url_rewrites{url},name,thumbnail{label,url},price_range{minimum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}},... on ConfigurableProduct{price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}},... on BundleProduct{price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}}}}}}", query); } } diff --git a/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/productlist/ProductListImplTest.java b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/productlist/ProductListImplTest.java index 49b4f20b6f..011f4c2d45 100644 --- a/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/productlist/ProductListImplTest.java +++ b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/productlist/ProductListImplTest.java @@ -174,11 +174,12 @@ public void setUp() throws Exception { "{__type"); Utils.setupHttpResponse("graphql/magento-graphql-attributes-result.json", httpClient, HttpStatus.SC_OK, "{customAttributeMetadata"); - Utils.setupHttpResponse("graphql/magento-graphql-category-uid.json", httpClient, HttpStatus.SC_OK, + Utils.setupHttpResponse("graphql/magento-graphql-search-category-result-category.json", httpClient, + HttpStatus.SC_OK, "{categoryList(filters:{url_path"); Utils.setupHttpResponse("graphql/magento-graphql-search-category-result-category.json", httpClient, HttpStatus.SC_OK, - "{categoryList(filters:{category_uid"); + "{categoryList(filters:{category_uid:{eq:\"MTI==\"}"); Utils.setupHttpResponse("graphql/magento-graphql-search-category-result-products.json", httpClient, HttpStatus.SC_OK, "pageSize:6"); Utils.setupHttpResponse("graphql/magento-graphql-search-category-result-products.json", httpClient, diff --git a/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/relatedproducts/RelatedProductsImplTest.java b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/relatedproducts/RelatedProductsImplTest.java index c2f5d48668..f4a1211e01 100644 --- a/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/relatedproducts/RelatedProductsImplTest.java +++ b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/models/v1/relatedproducts/RelatedProductsImplTest.java @@ -113,8 +113,6 @@ public void setUp() throws Exception { graphqlClient = Mockito.spy(new GraphqlClientImpl()); context.registerInjectActivateService(graphqlClient, "httpMethod", "POST"); - Utils.setupHttpResponse("graphql/magento-graphql-product-result.json", httpClient, 200, "{products(filter:{url_key"); - SlingBindings slingBindings = (SlingBindings) context.request().getAttribute(SlingBindings.class.getName()); Style style = mock(Style.class); when(style.get(Mockito.anyString(), Mockito.anyInt())).then(i -> i.getArgumentAt(1, Object.class)); @@ -144,7 +142,14 @@ private void setUp(RelationType relationType, String jsonResponsePath, boolean a ProductInterface product = rootQuery.getProducts().getItems().get(0); products = PRODUCTS_GETTER.get(relationType).apply(product); - Utils.setupHttpResponse(jsonResponsePath, httpClient, HttpStatus.SC_OK, "{products(filter:{sku"); + if (addSlugInSelector) { + // search by url_key + Utils.setupHttpResponse(jsonResponsePath, httpClient, HttpStatus.SC_OK, + "{products(filter:{url_key:{eq:\"endurance-watch\""); + } else { + // search by sku + Utils.setupHttpResponse(jsonResponsePath, httpClient, HttpStatus.SC_OK, "{products(filter:{sku:{eq:\"24-MG01\""); + } context.registerAdapter(Resource.class, GraphqlClient.class, (Function) input -> input.getValueMap() .get("cq:graphqlClient", String.class) != null ? graphqlClient : null); } @@ -206,7 +211,7 @@ public void testQueryExtensions() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); verify(clientSpy, times(1)).execute(captor.capture()); - String expectedQuery = "{products(filter:{sku:{eq:\"24-MG01\"}}){items{__typename,related_products{__typename,sku,name,thumbnail{label,url},url_key,url_path,url_rewrites{url},price_range{minimum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}},... on ConfigurableProduct{price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}},... on BundleProduct{price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}},... on ConfigurableProduct{variants{product{sku}}},description{html}}}}}"; + String expectedQuery = "{products(filter:{sku:{eq:\"24-MG01\"}}){items{__typename,sku,related_products{__typename,sku,url_key,url_path,url_rewrites{url},name,thumbnail{label,url},price_range{minimum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}},... on ConfigurableProduct{price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}},... on BundleProduct{price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}},... on ConfigurableProduct{variants{product{sku}}},description{html}}}}}"; Assert.assertEquals(expectedQuery, captor.getValue()); } @@ -215,7 +220,7 @@ public void testExtendAndReplaceFilterExtensions() throws Exception { setUp(RelationType.RELATED_PRODUCTS, "graphql/magento-graphql-relatedproducts-result.json", false); AbstractProductsRetriever retriever = relatedProducts.getProductsRetriever(); retriever.extendProductFilterWith(f -> new ProductAttributeFilterInput().setUrlKey(new FilterEqualTypeInput().setEq("my-product"))); - retriever.extendProductFilterWith(f -> f.setSku(new FilterEqualTypeInput().setEq("my-name"))); + retriever.extendProductFilterWith(f -> f.setSku(new FilterEqualTypeInput().setEq("24-MG01"))); MagentoGraphqlClient client = (MagentoGraphqlClient) Whitebox.getInternalState(retriever, "client"); MagentoGraphqlClient clientSpy = Mockito.spy(client); @@ -226,7 +231,7 @@ public void testExtendAndReplaceFilterExtensions() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); verify(clientSpy, times(1)).execute(captor.capture()); - String expectedQuery = "{products(filter:{sku:{eq:\"my-name\"},url_key:{eq:\"my-product\"}}){items{__typename,related_products{__typename,sku,name,thumbnail{label,url},url_key,url_path,url_rewrites{url},price_range{minimum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}},... on ConfigurableProduct{price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}},... on BundleProduct{price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}}}}}}"; + String expectedQuery = "{products(filter:{sku:{eq:\"24-MG01\"},url_key:{eq:\"my-product\"}}){items{__typename,sku,related_products{__typename,sku,url_key,url_path,url_rewrites{url},name,thumbnail{label,url},price_range{minimum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}},... on ConfigurableProduct{price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}},... on BundleProduct{price_range{maximum_price{regular_price{value,currency},final_price{value,currency},discount{amount_off,percent_off}}}}}}}}"; Assert.assertEquals(expectedQuery, captor.getValue()); } diff --git a/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/services/UrlProviderImplTest.java b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/services/UrlProviderImplTest.java index d5d2aff0ad..7434a0a639 100644 --- a/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/services/UrlProviderImplTest.java +++ b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/services/UrlProviderImplTest.java @@ -20,6 +20,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.UnaryOperator; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpStatus; @@ -62,6 +63,7 @@ import com.adobe.cq.commerce.graphql.client.GraphqlClient; import com.adobe.cq.commerce.graphql.client.GraphqlRequest; import com.adobe.cq.commerce.graphql.client.impl.GraphqlClientImpl; +import com.adobe.cq.commerce.magento.graphql.ProductAttributeFilterInput; import com.adobe.cq.commerce.magento.graphql.UrlRewrite; import com.day.cq.wcm.api.Page; import com.day.cq.wcm.scripting.WCMBindingsConstants; @@ -72,6 +74,8 @@ import static com.adobe.cq.commerce.core.testing.TestContext.newAemContext; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.mockito.Matchers.any; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; @@ -970,4 +974,31 @@ public Params retainParsableParameters(Params parameters) { return copy; } } + + @Test + public void testProductFilterHook() { + setCurrentPage("/content"); + // no information in the url + UnaryOperator inputHook = urlProvider.getProductFilterHook(request); + assertNull(inputHook); + + // default url format (url_key) + ((MockRequestPathInfo) request.getRequestPathInfo()).setSuffix("/foobar.html"); + ProductAttributeFilterInput input = urlProvider.getProductFilterHook(request).apply(null); + assertNotNull(input); + assertNull(input.getSku()); + assertNotNull(input.getUrlKey()); + assertEquals("foobar", input.getUrlKey().getEq()); + + // with sku and url_key, sku takes precedence + ((MockRequestPathInfo) request.getRequestPathInfo()).setSuffix("/FOO007/foobar.html"); + MockOsgi.deactivate(urlProvider, context.bundleContext()); + MockOsgi.activate(urlProvider, context.bundleContext(), + UrlFormat.PRODUCT_PAGE_URL_FORMAT, ProductPageWithSkuAndUrlKey.PATTERN); + input = urlProvider.getProductFilterHook(request).apply(null); + assertNotNull(input); + assertNull(input.getUrlKey()); + assertNotNull(input.getSku()); + assertEquals("FOO007", input.getSku().getEq()); + } } diff --git a/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/servlets/CatalogPageNotFoundFilterTest.java b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/servlets/CatalogPageNotFoundFilterTest.java index 3ff5358b26..15b7de01c0 100644 --- a/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/servlets/CatalogPageNotFoundFilterTest.java +++ b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/servlets/CatalogPageNotFoundFilterTest.java @@ -114,10 +114,8 @@ public void setup() throws IOException { "{products(filter:{sku:{eq:\"MJ01\"}}"); Utils.setupHttpResponse("graphql/magento-graphql-product-sku.json", httpClient, HttpStatus.SC_OK, "{products(filter:{url_key:{eq:\"beaumont-summit-kit\"}}"); - Utils.setupHttpResponse("graphql/magento-graphql-category-uid.json", httpClient, HttpStatus.SC_OK, - "{categoryList(filters:{url_path:{eq:\"men/tops-men/jackets-men\"}}"); Utils.setupHttpResponse("graphql/magento-graphql-category-list-result.json", httpClient, HttpStatus.SC_OK, - "{categoryList(filters:{category_uid:{eq:\"MTI==\"}}"); + "{categoryList(filters:{url_path:{eq:\"men/tops-men/jackets-men\"}}"); Utils.setupHttpResponse(null, httpClient, HttpStatus.SC_NOT_FOUND, "url_key:{eq:\"does-not-exist\"}}"); } diff --git a/bundles/core/src/test/resources/context/jcr-content.json b/bundles/core/src/test/resources/context/jcr-content.json index 0f07523ba5..dfd64360ba 100644 --- a/bundles/core/src/test/resources/context/jcr-content.json +++ b/bundles/core/src/test/resources/context/jcr-content.json @@ -642,7 +642,7 @@ "root": { "responsivegrid": { "product": { - "selection": "MJ01", + "selection": "MJ02", "sling:resourceType": "venia/components/commerce/product" } } diff --git a/bundles/core/src/test/resources/exporter/upsell-products.json b/bundles/core/src/test/resources/exporter/upsell-products.json index 2761c768d8..01e4c88f56 100644 --- a/bundles/core/src/test/resources/exporter/upsell-products.json +++ b/bundles/core/src/test/resources/exporter/upsell-products.json @@ -6,7 +6,7 @@ "addToWishListEnabled":false, "commerceIdentifier": { "entityType": "PRODUCT", - "value": "MJ01", + "value": "24-MG01", "type": "SKU" }, "titleType": "h3", diff --git a/bundles/core/src/test/resources/graphql/magento-graphql-crosssellproducts-result.json b/bundles/core/src/test/resources/graphql/magento-graphql-crosssellproducts-result.json index 964232e785..3e161b096c 100644 --- a/bundles/core/src/test/resources/graphql/magento-graphql-crosssellproducts-result.json +++ b/bundles/core/src/test/resources/graphql/magento-graphql-crosssellproducts-result.json @@ -4,6 +4,7 @@ "items": [ { "__typename": "SimpleProduct", + "sku": "24-MG01", "crosssell_products": [ { "__typename": "SimpleProduct", diff --git a/bundles/core/src/test/resources/graphql/magento-graphql-relatedproducts-result.json b/bundles/core/src/test/resources/graphql/magento-graphql-relatedproducts-result.json index c1b1f861bd..1e3a0d3f08 100644 --- a/bundles/core/src/test/resources/graphql/magento-graphql-relatedproducts-result.json +++ b/bundles/core/src/test/resources/graphql/magento-graphql-relatedproducts-result.json @@ -4,6 +4,7 @@ "items": [ { "__typename": "SimpleProduct", + "sku": "24-MG01", "related_products": [ { "__typename": "SimpleProduct", diff --git a/bundles/core/src/test/resources/graphql/magento-graphql-upsellproducts-result.json b/bundles/core/src/test/resources/graphql/magento-graphql-upsellproducts-result.json index 293ecfc4ff..382f947832 100644 --- a/bundles/core/src/test/resources/graphql/magento-graphql-upsellproducts-result.json +++ b/bundles/core/src/test/resources/graphql/magento-graphql-upsellproducts-result.json @@ -4,6 +4,7 @@ "items": [ { "__typename": "SimpleProduct", + "sku": "24-MG01", "upsell_products": [ { "__typename": "SimpleProduct", diff --git a/examples/bundle/pom.xml b/examples/bundle/pom.xml index 5a3692d926..e2d9ee0ce8 100644 --- a/examples/bundle/pom.xml +++ b/examples/bundle/pom.xml @@ -138,28 +138,6 @@ restore-instrumented-classes - - check-coverage - - check - - - true - ${jacoco.data.file} - - - BUNDLE - - - BRANCH - COVEREDRATIO - 0.8 - - - - - - report prepare-package diff --git a/examples/bundle/src/test/java/com/adobe/cq/commerce/core/examples/servlets/GraphqlServletTest.java b/examples/bundle/src/test/java/com/adobe/cq/commerce/core/examples/servlets/GraphqlServletTest.java index 1f93d76c92..04fe670315 100644 --- a/examples/bundle/src/test/java/com/adobe/cq/commerce/core/examples/servlets/GraphqlServletTest.java +++ b/examples/bundle/src/test/java/com/adobe/cq/commerce/core/examples/servlets/GraphqlServletTest.java @@ -422,7 +422,8 @@ public void testUnknownProductListModel() throws ServletException { requestPathInfo.setSuffix("/unknown-category.html"); ProductList productListModel = context.request().adaptTo(ProductList.class); - Assert.assertNull(productListModel.getCategoryRetriever()); + Assert.assertNotNull(productListModel.getCategoryRetriever()); + Assert.assertNull(productListModel.getCategoryRetriever().fetchCategory()); } @Test