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 post
For PDF Insights in AEM check this post
AEM was now successfully integrated with Analytics, the next steps are...
1. Create a data layer in AEM page component 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 | Github
List View in AEM
Add Data Layer in Page Template
1) 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 exercise
2) 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 in 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 Component
1) 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 Analytics
1) 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 Importer
1) 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/configMgr
Extend Assets List View
1) Create node /apps/dam/gui/content/commons/availablecolumns/views for adding a list view column header
2) Create cq:ClientLibraryFolder /apps/eaem-most-popular-content-fragments/clientlib with categories set to dam.gui.admin.util and dependencies set to lodash
3) 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));
No comments:
Post a Comment