Skip to content

Commit

Permalink
CIF-1629 - Enrich PLP / PDP automatically with metadata (#416)
Browse files Browse the repository at this point in the history
* CIF-1629 - Enrich PLP / PDP automatically with metadata

- enable caching for Product and ProductList Sling models
- add meta title, description, and keywords

* CIF-1629 - Enrich PLP / PDP automatically with metadata

- add unit tests

* CIF-1629 - Enrich PLP / PDP automatically with metadata

- extend unit tests for launch pages

* CIF-1629 - Enrich PLP / PDP automatically with metadata

- minor fix

* CIF-1629 - Enrich PLP / PDP automatically with metadata

- implement canonical url

* CIF-1629 - Enrich PLP / PDP automatically with metadata

- fix unit test and extend examples to be used in Venia integration tests

* CIF-1629 - Enrich PLP / PDP automatically with metadata

- automate dependency versioning in integration tests

* CIF-1629 - Enrich PLP / PDP automatically with metadata

- fix a bug that the ResourceResolver is sometimes closed after the @PostConstruct method executed

* CIF-1629 - Enrich PLP / PDP automatically with metadata

- fix branch merge issue
  • Loading branch information
cjelger authored Oct 27, 2020
1 parent 4e1681d commit f97436b
Show file tree
Hide file tree
Showing 28 changed files with 612 additions and 33 deletions.
13 changes: 8 additions & 5 deletions .circleci/ci/it-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,18 @@ const qpPath = '/home/circleci/cq';

try {
ci.stage("Integration Tests");
let wcmVersion = ci.sh('mvn help:evaluate -Dexpression=core.wcm.components.version -q -DforceStdout', true);
let magentoGraphqlVersion = ci.sh('mvn help:evaluate -Dexpression=magento.graphql.version -q -DforceStdout', true);
let graphqlClientVersion = ci.sh('mvn help:evaluate -Dexpression=graphql.client.version -q -DforceStdout', true);
ci.dir(qpPath, () => {
// Connect to QP
ci.sh('./qp.sh -v bind --server-hostname localhost --server-port 55555');

// We install the graphql-client by default except with the CIF Add-On
let extras = '--bundle com.adobe.commerce.cif:graphql-client:1.6.1:jar';
let extras = `--bundle com.adobe.commerce.cif:graphql-client:${graphqlClientVersion}:jar`;
if (process.env.AEM == 'classic') {
// The core components are already installed in the Cloud SDK
extras += ' --bundle com.adobe.cq:core.wcm.components.all:2.9.0:zip';
extras += ` --bundle com.adobe.cq:core.wcm.components.all:${wcmVersion}:zip`;
} else if (process.env.AEM == 'addon') {
// Download the CIF Add-On
ci.sh(`curl -s "${process.env.CIF_ADDON_URL}" -o cif-addon.far`);
Expand All @@ -43,9 +46,9 @@ try {
// Start CQ
ci.sh(`./qp.sh -v start --id author --runmode author --port 4502 --qs-jar /home/circleci/cq/author/cq-quickstart.jar \
--bundle org.apache.sling:org.apache.sling.junit.core:1.0.23:jar \
--bundle com.adobe.commerce.cif:magento-graphql:6.0.0-magento235:jar \
--bundle com.adobe.cq:core.wcm.components.examples.ui.apps:2.9.0:zip \
--bundle com.adobe.cq:core.wcm.components.examples.ui.content:2.9.0:zip \
--bundle com.adobe.commerce.cif:magento-graphql:${magentoGraphqlVersion}:jar \
--bundle com.adobe.cq:core.wcm.components.examples.ui.apps:${wcmVersion}:zip \
--bundle com.adobe.cq:core.wcm.components.examples.ui.content:${wcmVersion}:zip \
${extras} \
${ci.addQpFileDependency(config.modules['core-cif-components-apps'])} \
${ci.addQpFileDependency(config.modules['core-cif-components-config'])} \
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*******************************************************************************
*
* Copyright 2020 Adobe. All rights reserved.
* This file is licensed to you 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 REPRESENTATIONS
* 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.page;

import javax.annotation.PostConstruct;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.ScriptVariable;
import org.apache.sling.models.annotations.injectorspecific.Self;

import com.adobe.cq.commerce.core.components.models.page.PageMetadata;
import com.adobe.cq.commerce.core.components.models.product.Product;
import com.adobe.cq.commerce.core.components.models.productlist.ProductList;
import com.adobe.cq.commerce.core.components.utils.SiteNavigation;
import com.day.cq.commons.jcr.JcrConstants;
import com.day.cq.wcm.api.Page;

@Model(
adaptables = SlingHttpServletRequest.class,
adapters = PageMetadata.class)
public class PageMetadataImpl implements PageMetadata {

@Self
private SlingHttpServletRequest request;

@ScriptVariable
private Page currentPage;

@ScriptVariable
private ValueMap properties;

private PageMetadata provider;

@PostConstruct
void initModel() {
if (isProductPage()) {
Product product = request.adaptTo(Product.class);
provider = product;
} else if (isCategoryPage()) {
ProductList productList = request.adaptTo(ProductList.class);
provider = productList;
}
}

@Override
public String getMetaDescription() {
String metaDescription = provider != null ? provider.getMetaDescription() : null;
return metaDescription != null ? metaDescription : properties.get(JcrConstants.JCR_DESCRIPTION, String.class);
}

@Override
public String getMetaKeywords() {
String metaKeywords = provider != null ? provider.getMetaKeywords() : null;
if (metaKeywords == null && currentPage instanceof com.adobe.cq.wcm.core.components.models.Page) {
metaKeywords = ((com.adobe.cq.wcm.core.components.models.Page) currentPage).getKeywords().toString();
}
return metaKeywords;
}

@Override
public String getMetaTitle() {
String metaTitle = provider != null ? provider.getMetaTitle() : null;
return metaTitle != null ? metaTitle : currentPage.getTitle();
}

@Override
public String getCanonicalUrl() {
return provider != null ? provider.getCanonicalUrl() : null;
}

private boolean isProductPage() {
Page productPage = SiteNavigation.getProductPage(currentPage);

// The product page might be in a Launch so we use 'endsWith' to compare the paths, for example
// - product page: /content/launches/2020/09/15/mylaunch/content/venia/us/en/products/category-page
// - current page: /content/venia/us/en/products/category-page
return productPage != null ? productPage.getPath().endsWith(currentPage.getPath()) : false;
}

private boolean isCategoryPage() {
Page categoryPage = SiteNavigation.getCategoryPage(currentPage);

// See comment above
return categoryPage != null ? categoryPage.getPath().endsWith(currentPage.getPath()) : false;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
import com.adobe.cq.sightly.SightlyWCMMode;
import com.adobe.cq.wcm.core.components.models.datalayer.ComponentData;
import com.adobe.cq.wcm.launches.utils.LaunchUtils;
import com.day.cq.commons.Externalizer;
import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.designer.Style;
import com.fasterxml.jackson.core.JsonProcessingException;
Expand All @@ -77,7 +78,8 @@
@Model(
adaptables = SlingHttpServletRequest.class,
adapters = Product.class,
resourceType = ProductImpl.RESOURCE_TYPE)
resourceType = ProductImpl.RESOURCE_TYPE,
cache = true)
public class ProductImpl extends DataLayerComponent implements Product {

protected static final String RESOURCE_TYPE = "core/cif/components/commerce/product/v1/product";
Expand Down Expand Up @@ -107,11 +109,15 @@ public class ProductImpl extends DataLayerComponent implements Product {
@Inject
private XSSAPI xssApi;

@Inject
private Externalizer externalizer;

private Boolean configurable;
private Boolean isGroupedProduct;
private Boolean isVirtualProduct;
private Boolean isBundleProduct;
private Boolean loadClientPrice;
private String canonicalUrl;

private AbstractProductRetriever productRetriever;

Expand Down Expand Up @@ -142,6 +148,12 @@ private void initModel() {
loadClientPrice = false;
}
}

if (!wcmMode.isDisabled()) {
canonicalUrl = externalizer.authorLink(resource.getResourceResolver(), request.getRequestURI());
} else {
canonicalUrl = externalizer.publishLink(resource.getResourceResolver(), request.getRequestURI());
}
}

@Override
Expand Down Expand Up @@ -389,6 +401,26 @@ private String safeDescription(ProductInterface product) {
return xssApi.filterHTML(description.getHtml());
}

@Override
public String getMetaDescription() {
return productRetriever.fetchProduct().getMetaDescription();
}

@Override
public String getMetaKeywords() {
return productRetriever.fetchProduct().getMetaKeyword();
}

@Override
public String getMetaTitle() {
return StringUtils.defaultString(productRetriever.fetchProduct().getMetaTitle(), getName());
}

@Override
public String getCanonicalUrl() {
return canonicalUrl;
}

// DataLayer methods

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ protected ProductInterfaceQueryDefinition generateProductQuery() {
.thumbnail(t -> t.label().url())
.urlKey()
.stockStatus()
.metaDescription()
.metaKeyword()
.metaTitle()
.priceRange(r -> r
.minimumPrice(generatePriceQuery()))
.mediaGallery(g -> g
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import com.adobe.cq.commerce.core.search.models.SearchResultsSet;
import com.adobe.cq.commerce.core.search.services.SearchResultsService;
import com.adobe.cq.wcm.launches.utils.LaunchUtils;
import com.day.cq.commons.Externalizer;
import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.designer.Style;

Expand Down Expand Up @@ -68,6 +69,8 @@ public class ProductCollectionImpl extends DataLayerComponent implements Product
protected SearchResultsService searchResultsService;
@Inject
protected UrlProvider urlProvider;
@Inject
protected Externalizer externalizer;

protected SearchOptionsImpl searchOptions;
protected SearchResultsSet searchResultsSet;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ protected CategoryTreeQueryDefinition generateCategoryQuery() {
.description()
.name()
.image()
.productCount();
.productCount()
.metaDescription()
.metaKeywords()
.metaTitle();

if (categoryQueryHook != null) {
categoryQueryHook.accept(q);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@
import com.adobe.cq.commerce.magento.graphql.ProductInterfaceQuery;
import com.adobe.cq.sightly.SightlyWCMMode;

@Model(adaptables = SlingHttpServletRequest.class, adapters = ProductList.class, resourceType = ProductListImpl.RESOURCE_TYPE)
@Model(
adaptables = SlingHttpServletRequest.class,
adapters = ProductList.class,
resourceType = ProductListImpl.RESOURCE_TYPE,
cache = true)
public class ProductListImpl extends ProductCollectionImpl implements ProductList {

protected static final String RESOURCE_TYPE = "core/cif/components/commerce/productlist/v1/productlist";
Expand All @@ -68,6 +72,7 @@ public class ProductListImpl extends ProductCollectionImpl implements ProductLis

private AbstractCategoryRetriever categoryRetriever;
private boolean usePlaceholderData;
private String canonicalUrl;

private Pair<CategoryInterface, SearchResultsSet> categorySearchResultsSet;

Expand All @@ -88,6 +93,12 @@ private void initModel() {
// Parse category identifier from URL
Pair<CategoryIdentifierType, String> identifier = urlProvider.getCategoryIdentifier(request);

if (!wcmMode.isDisabled()) {
canonicalUrl = externalizer.authorLink(resource.getResourceResolver(), request.getRequestURI());
} else {
canonicalUrl = externalizer.publishLink(resource.getResourceResolver(), request.getRequestURI());
}

// get GraphQL client and query data
if (magentoGraphqlClient != null) {
if (identifier != null && StringUtils.isNotBlank(identifier.getRight())) {
Expand Down Expand Up @@ -203,4 +214,24 @@ protected CategoryInterface getCategory() {
public AbstractCategoryRetriever getCategoryRetriever() {
return categoryRetriever;
}

@Override
public String getMetaDescription() {
return getCategory() != null ? getCategory().getMetaDescription() : null;
}

@Override
public String getMetaKeywords() {
return getCategory() != null ? getCategory().getMetaKeywords() : null;
}

@Override
public String getMetaTitle() {
return StringUtils.defaultString(getCategory() != null ? getCategory().getMetaTitle() : null, getTitle());
}

@Override
public String getCanonicalUrl() {
return canonicalUrl;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*******************************************************************************
*
* Copyright 2020 Adobe. All rights reserved.
* This file is licensed to you 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 REPRESENTATIONS
* 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.models.page;

import com.adobe.cq.commerce.core.components.models.productlist.ProductList;

public interface PageMetadata {

/**
* @return The content for the meta description tag of the HTML page.
*/
String getMetaDescription();

/**
* @return The content for the meta keywords tag of the HTML page.
*/
String getMetaKeywords();

/**
* Although this method refers to "metaTitle", this is used to set the title tag of the HTML page.
* The method is not called <code>getTitle()</code> to avoid confusion with {@link ProductList#getTitle()}
*
* @return The content for the title tag of the HTML page.
*/
String getMetaTitle();

/**
* @return The fully-qualified canonical url, to set the canonical link element of the HTML page.
*/
String getCanonicalUrl();
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@
import java.util.List;

import com.adobe.cq.commerce.core.components.models.common.Price;
import com.adobe.cq.commerce.core.components.models.page.PageMetadata;
import com.adobe.cq.commerce.core.components.models.retriever.AbstractProductRetriever;
import com.adobe.cq.wcm.core.components.models.Component;

/**
* Product is the sling model interface for the CIF core product component.
*/
public interface Product extends Component {
public interface Product extends Component, PageMetadata {

/**
* Name of the boolean resource property indicating if the product component should load prices on the client-side.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@

import javax.annotation.Nullable;

import com.adobe.cq.commerce.core.components.models.page.PageMetadata;
import com.adobe.cq.commerce.core.components.models.productcollection.ProductCollection;
import com.adobe.cq.commerce.core.components.models.retriever.AbstractCategoryRetriever;

public interface ProductList extends ProductCollection {
public interface ProductList extends ProductCollection, PageMetadata {

/**
* Name of the boolean resource property indicating if the product list should render the category title.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*
******************************************************************************/

@Version("2.0.0")
@Version("3.0.0")
package com.adobe.cq.commerce.core.components.models.productlist;

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

0 comments on commit f97436b

Please sign in to comment.