Goal
Before
importing analytics data into AEM you need to set up AEM for
Adobe Analytics. For configuring
Adobe Launch and
Adobe Analytics in AEM
check this postAEM is now successfully integrated with Analytics, the next steps are...
1. Create the
data layer in AEM page template head for storing page path and content fragments paths rendered on page
2. Extend otb content fragment component to
add the content fragment path rendered by the component
3. Refresh page and make sure the cf paths are
sent to Analytics in list variable list1 (l1)4. Setup a
polling importer to import Analytics data into AEM at regular intervals
5. Extend
Assets List View to show the
Fragment Views fetched from Analytics
Demo |
Package Install |
GithubList View in AEM
Content Fragments Report in AnalyticsAdd Data Layer in Page Template1) Time to setup the data layer
window.eaemData in your page
head.html with the
page path and content fragments paths data structure (the data layer root
window.eaemData was setup in
Adobe Launch extension
Context Hub in the
previous exercise2) For simplicity,
attached package install contains the core components page
/apps/core/wcm/components/page/v2/page/head.html modified to add the data layer, ideally you'd be extending the core components page component or add it a new page component
<script>
window.eaemData = {
page : {
pageInfo : {
pagePath : "${currentPage.path @ context='scriptString'}"
},
contentFraments:{
paths : []
}
},
addContentFragmentPath: function(cfPath){
if(!cfPath){
return;
}
var cfPaths = window.eaemData.page.contentFraments.paths;
if(cfPaths.indexOf(cfPath) >= 0){
return;
}
cfPaths.push(cfPath);
}
}
</script>
3) The
content fragment component rendered on page can now add CF path using api
window.eaemData.addContentFragmentPath()
Extend Content Fragment Component1) Extend the otb content fragment component
/libs/dam/cfm/components/contentfragment to create a custom content fragment component
EAEM Content Fragment Component -
/apps/eaem-most-popular-content-fragments/components/content-fragment
2) Add the following code in
/apps/eaem-most-popular-content-fragments/components/content-fragment/content-fragment.html<section data-sly-include="/libs/dam/cfm/components/contentfragment/contentfragment.html" data-sly-unwrap></section>
<div data-sly-use.fragment="helper.js">
<script>
window.eaemData.addContentFragmentPath("${fragment.cfPath @ context='scriptString'}");
</script>
</div>
3) Add the following code in
/apps/eaem-most-popular-content-fragments/components/content-fragment/helper.js"use strict";
use( ["/libs/wcm/foundation/components/utils/ResourceUtils.js" ], function(ResourceUtils){
return { cfPath : granite.resource.properties.fileReference };
} );
Verify CF Paths sent to Analytics1) Drag and drop the content fragment component created above
EAEM Content Fragment Component and select a CF
2) Refresh the page in published mode (wcmmode=disabled) and check the data sent to Analytics
Setup Polling Importer1) Create
Polling Importer apps.experienceaem.assets.EAEMListVariablesReportImporter extending
com.day.cq.polling.importer.Importer with custom scheme
eaemReport, add the following code...
package apps.experienceaem.assets;
import com.day.cq.analytics.sitecatalyst.SitecatalystUtil;
import com.day.cq.analytics.sitecatalyst.SitecatalystWebservice;
import com.day.cq.polling.importer.ImportException;
import com.day.cq.polling.importer.Importer;
import com.day.cq.wcm.webservicesupport.ConfigurationManager;
import com.day.cq.wcm.webservicesupport.ConfigurationManagerFactory;
import com.day.cq.wcm.webservicesupport.Configuration;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.commons.json.JSONArray;
import org.apache.sling.commons.json.JSONObject;
import org.apache.sling.settings.SlingSettingsService;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.AttributeType;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.Node;
import javax.jcr.Session;
import java.text.SimpleDateFormat;
import java.util.*;
@Component(
immediate = true,
service = { Importer.class },
property = {
Importer.SCHEME_PROPERTY + "=" + EAEMListVariablesReportImporter.SCHEME_VALUE
}
)
@Designate(ocd = EAEMListVariablesReportImporter.EAEMListVariablesReportConfiguration.class)
public class EAEMListVariablesReportImporter implements Importer{
private Logger log = LoggerFactory.getLogger(getClass());
public static final String SCHEME_VALUE = "eaemReport";
private static final String USE_ANALYTICS_FRAMEWORK = "use-analytics-framework";
private static final String GET_ANALYTICS_FOR_LAST_DAYS = "get-analytics-for-last-days";
private static final String EAEM_ANALYTICS_VIEWS = "eaemAnalyticsViews";
private static final String UPDATE_ANALYTICS_SERVICE = "eaem-update-cf-with-analytics";
private static final long DEFAULT_REPORT_FETCH_DELAY = 10000;
private static final long DEFAULT_REPORT_FETCH_ATTEMPTS = 10;
private long reportFetchDelay = DEFAULT_REPORT_FETCH_DELAY;
private long reportFetchAttempts = DEFAULT_REPORT_FETCH_ATTEMPTS;
@Reference
private SitecatalystWebservice sitecatalystWebservice;
@Reference
private ResourceResolverFactory resolverFactory;
@Reference
private ConfigurationManagerFactory cfgManagerFactory;
@Reference
private SlingSettingsService settingsService;
@Activate
protected void activate(EAEMListVariablesReportConfiguration configuration) {
reportFetchDelay = configuration.reportFetchDelay();
reportFetchAttempts = configuration.reportFetchAttempts();
}
public void importData(String scheme, String dataSource, Resource target) throws ImportException {
log.info("Importing analytics data for list var - " + dataSource);
try {
String useAnalyticsFrameworkPath = target.getValueMap().get(USE_ANALYTICS_FRAMEWORK, String.class);
if(StringUtils.isEmpty(useAnalyticsFrameworkPath)){
log.warn("Analytics framework path property " + USE_ANALYTICS_FRAMEWORK + " missing on " + target.getPath());
return;
}
Configuration configuration = getConfiguration(useAnalyticsFrameworkPath, target.getResourceResolver());
String reportSuiteID = SitecatalystUtil.getReportSuites(settingsService, configuration);
log.debug("Report Suite ID - " + reportSuiteID);
JSONObject reportDescription = getReportDescription(target, dataSource, reportSuiteID);
String queueReportResponse = sitecatalystWebservice.queueReport(configuration, reportDescription);
log.debug("queueReportResponse - " + queueReportResponse);
JSONObject jsonObj = new JSONObject(queueReportResponse);
Long queuedReportId = jsonObj.optLong("reportID");
if(queuedReportId == 0L) {
log.error("Could not queue report, queueReportResponse - " + queueReportResponse);
return;
}
boolean reportReady = false;
JSONObject report = null;
for(int attemptNo = 1; attemptNo <= reportFetchAttempts; ++attemptNo) {
log.debug("Attempt number " + attemptNo + " to fetch queued report " + queuedReportId);
String reportData = sitecatalystWebservice.getReport(configuration, queuedReportId.toString());
log.debug("Get report " + queuedReportId + " result: " + reportData);
jsonObj = new JSONObject(reportData);
String errorResponse = jsonObj.optString("error");
reportReady = ((errorResponse == null) || !"report_not_ready".equalsIgnoreCase(errorResponse));
if(reportReady) {
report = jsonObj.optJSONObject("report");
break;
}
Thread.sleep(reportFetchDelay);
}
if(report == null) {
log.error("Could not fetch queued report [" + queuedReportId + "] after " + reportFetchAttempts + " attempts");
}
ResourceResolver rResolverForUpdate = resolverFactory.getServiceResourceResolver(
Collections.singletonMap("sling.service.subservice", (Object)UPDATE_ANALYTICS_SERVICE));
saveAnalyticsData(report, rResolverForUpdate);
log.info("Successfully imported analytics data with report id - " + queuedReportId);
}catch(Exception e){
log.error("Error importing analytics data for list var - " + dataSource, e);
}
}
private void saveAnalyticsData(JSONObject report, ResourceResolver resolver ) throws Exception{
JSONArray data = report.optJSONArray("data");
JSONObject metrics = null;
String cfPath;
Resource cfResource;
Node cfJcrContent;
for(int d = 0, len = data.length(); d < len; d++){
metrics = data.getJSONObject(d);
cfPath = metrics.getString("name");
cfResource = resolver.getResource(cfPath);
if(cfResource == null){
log.warn("Content fragment not found - " + cfPath);
continue;
}
cfJcrContent = cfResource.getChild("jcr:content").adaptTo(Node.class);
cfJcrContent.setProperty(EAEM_ANALYTICS_VIEWS, metrics.getJSONArray("counts").getString(0));
}
resolver.adaptTo(Session.class).save();
}
private JSONObject getReportDescription(Resource target, String dataSource, String reportSuiteID) throws Exception{
Calendar cal = new GregorianCalendar();
cal.setTime(new Date());
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
Integer daysCount = target.getValueMap().get(GET_ANALYTICS_FOR_LAST_DAYS, Integer.class);
if(daysCount == null){
daysCount = -90;
}
JSONObject reportDescription = new JSONObject();
reportDescription.put("elements", getElements(dataSource));
reportDescription.put("metrics", getMetrics());
reportDescription.put("reportSuiteID", reportSuiteID);
reportDescription.put("dateTo", formatter.format(cal.getTime()));
cal.add(Calendar.DAY_OF_MONTH, daysCount);
reportDescription.put("dateFrom", formatter.format(cal.getTime()));
return reportDescription;
}
private JSONArray getElements(String dataSource) throws Exception{
JSONObject elements = new JSONObject();
elements.put("id", dataSource);
elements.put("top", 10000);
elements.put("startingWith", 1);
return new JSONArray().put(elements);
}
private JSONArray getMetrics() throws Exception{
JSONObject metrics = new JSONObject();
metrics.put("id", "pageviews");
return new JSONArray().put(metrics);
}
private Configuration getConfiguration(String analyticsFrameworkPath, ResourceResolver resourceResolver)
throws Exception {
ConfigurationManager cfgManager = cfgManagerFactory.getConfigurationManager(resourceResolver);
String[] services = new String[]{ analyticsFrameworkPath };
return cfgManager.getConfiguration("sitecatalyst", services);
}
public void importData(String scheme,String dataSource,Resource target,String login,String password)
throws ImportException {
importData(scheme, dataSource, target);
}
@ObjectClassDefinition(
name = "EAEM Analytics Report Importer",
description = "Imports Analytics List Variable Reports periodically into AEM"
)
public @interface EAEMListVariablesReportConfiguration {
@AttributeDefinition(
name = "Fetch delay",
description = "Number in milliseconds between attempts to fetch a queued report. Default is set to 10000 (10s)",
defaultValue = { "" + DEFAULT_REPORT_FETCH_DELAY },
type = AttributeType.LONG
)
long reportFetchDelay() default DEFAULT_REPORT_FETCH_DELAY;
@AttributeDefinition(
name = "Fetch attempts",
description = "Number of attempts to fetch a queued report. Default is set to 10",
defaultValue = { "" + DEFAULT_REPORT_FETCH_ATTEMPTS },
type = AttributeType.LONG
)
long reportFetchAttempts() default DEFAULT_REPORT_FETCH_ATTEMPTS;
}
}
2) Create a
managed poll configuration sling:OsgiConfig /apps/eaem-most-popular-content-fragments/config/com.day.cq.polling.importer.impl.ManagedPollConfigImpl-eaem-popular-cf and set
id as
eaem-list-var-report-days source as
eaemReport:listvar1 among other values. Here we trying to pull a report created, with data collected in
list variable list1 which is configured for
Content Fragments in
Analytics (
list1 would have made more sense, but for some reason analytics api expects
listvar1)
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="sling:OsgiConfig"
enabled="{Boolean}true"
id="eaem-list-var-report-days"
interval="{Long}86400"
reference="{Boolean}true"
source="eaemReport:listvar1"/>
3) Create
cq:PollConfigFolder /etc/eaem-most-popular-content-fragments/analytics-list-vars-report-poll-config with a reference to the managed poll configuration created above
eaem-list-var-report-days<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal"
jcr:mixinTypes="[rep:AccessControllable]"
jcr:primaryType="cq:PollConfigFolder"
managedPollConfig="eaem-list-var-report-days"
use-analytics-framework="/etc/cloudservices/sitecatalyst/ags-959-analytics/experience-aem-analytics-framework"
get-analytics-for-last-days="{Long}-90"
source="eaemReport:listvar1"/>
4) In the above poll config folder configuration, we are also adding analytics framework path
/etc/cloudservices/sitecatalyst/ags-959-analytics/experience-aem-analytics-framework set with
use-analytics-framework created in
previous exercise and configuring property
get-analytics-for-last-days to get the
last 90 days data
5) Create a
service user mapping/apps/eaem-most-popular-content-fragments/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-eaem-popular-cf with write access to
update content fragment nodes
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="sling:OsgiConfig"
user.mapping="[apps.experienceaem.assets.eaem-most-popular-content-fragments-bundle:eaem-update-cf-with-analytics=dam-update-service]"/>
6) The polling importer configuration is set to import every 24 hours, if you'd like to change it for quick testing, change it in OSGI config manager
http://localhost:4502/system/console/configMgrExtend Assets List View1) Create node
/apps/dam/gui/content/commons/availablecolumns/views for adding a
list view column header2) Create cq:ClientLibraryFolder /apps/eaem-most-popular-content-fragments/clientlib with
categories set to
dam.gui.admin.util and
dependencies set to
lodash3) Create file /apps/eaem-most-popular-content-fragments/clientlib/js.txt with content
show-fragment-views.js
4) Create file /apps/eaem-most-popular-content-fragments/clientlib/show-fragment-views.js with the following code
(function ($, $document) {
"use strict";
var firstLoad = true,
LAYOUT_LIST_VIEW = "list",
VIEWS_COUNT = "VIEWS_COUNT",
EAEM_ANALYTICS_VIEWS = "eaemAnalyticsViews",
EAEM_MARKER_CSS = "eaem-fragment-cell",
FOUNDATION_CONTENT_LOADED = "foundation-contentloaded",
FRAGMENT_VIEWS_TEXT = "Fragment Views",
SEL_DAM_ADMIN_CHILD_PAGES = ".cq-damadmin-admin-childpages";
$document.on("cui-contentloaded", function (e) {
if(!firstLoad){
return;
}
var $childPages = $(e.currentTarget).find(SEL_DAM_ADMIN_CHILD_PAGES);
if(_.isEmpty($childPages)){
return;
}
firstLoad = false;
$childPages.trigger(FOUNDATION_CONTENT_LOADED);
});
$document.on(FOUNDATION_CONTENT_LOADED, SEL_DAM_ADMIN_CHILD_PAGES, addFragmentViews);
function addFragmentViews(e){
if(!e.currentTarget || !isFragmentViewsEnabled()){
return;
}
var $currentTarget = $(e.currentTarget),
foundationLayout = $currentTarget.data("foundation-layout");
if(_.isEmpty(foundationLayout)){
return;
}
var layoutId = foundationLayout.layoutId,
folderPath = getFolderPath();
if(layoutId !== LAYOUT_LIST_VIEW){
return;
}
$.ajax(folderPath + ".2.json").done(function(data){
$(".foundation-collection-item").each(function(index, item){
itemHandler(data, layoutId, $(item) );
});
});
}
function itemHandler(data, layoutId, $item){
if(isFragmentViewsAdded($item)){
return;
}
var itemPath = $item.data("foundation-collection-item-id"),
itemName = getStringAfterLastSlash(itemPath);
var eaemAnalyticsViews = "";
if(data[itemName] && data[itemName] && data[itemName]["jcr:content"]
&& data[itemName]["jcr:content"][EAEM_ANALYTICS_VIEWS]){
eaemAnalyticsViews = data[itemName]["jcr:content"][EAEM_ANALYTICS_VIEWS];
}
$item.append(getListCellHtml().replace("VIEWS_COUNT", eaemAnalyticsViews));
/*
if($item.find(".coral3-Icon--dragHandle").length > 0){
$item.find(".coral3-Icon--dragHandle").closest("td").before(getListCellHtml().replace("VIEWS_COUNT", eaemAnalyticsViews));
}else{
$item.append(getListCellHtml().replace("VIEWS_COUNT", eaemAnalyticsViews));
}
*/
}
function getStringAfterLastSlash(str){
if(!str || (str.indexOf("/") == -1)){
return "";
}
return str.substr(str.lastIndexOf("/") + 1);
}
function getFolderPath(){
return $(SEL_DAM_ADMIN_CHILD_PAGES).data("foundationCollectionId");
}
function getListCellHtml(){
return '<td is="coral-table-cell" class="coral-Table-cell ' + EAEM_MARKER_CSS + '" alignment="column">VIEWS_COUNT</td>';
}
function isFragmentViewsEnabled(){
var $viewsTd = $(SEL_DAM_ADMIN_CHILD_PAGES).find("thead")
.find("coral-table-headercell-content:contains('" + (FRAGMENT_VIEWS_TEXT) + "')");
return ($viewsTd.length > 0);
}
function isFragmentViewsAdded($item){
return ($item.find("td." + EAEM_MARKER_CSS).length > 0);
}
})(jQuery, jQuery(document));