AEM 6420 - Content Fragment Insights - Importing CF Analytics into AEM


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

Content Fragments Report in Analytics

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

        window.eaemData = {
            page : {
                pageInfo : {
                    pagePath : "${currentPage.path @ context='scriptString'}"
                    paths : []

            addContentFragmentPath: function(cfPath){

                var cfPaths =;

                if(cfPaths.indexOf(cfPath) >= 0){


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">
        window.eaemData.addContentFragmentPath("${fragment.cfPath @ context='scriptString'}");

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 : };
} );

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 with custom scheme eaemReport, add the following code...

package apps.experienceaem.assets;

import org.apache.commons.lang3.StringUtils;
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.*;

        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;

    private SitecatalystWebservice sitecatalystWebservice;

    private ResourceResolverFactory resolverFactory;

    private ConfigurationManagerFactory cfgManagerFactory;

    private SlingSettingsService settingsService;

    protected void activate(EAEMListVariablesReportConfiguration configuration) {
        reportFetchDelay = configuration.reportFetchDelay();
        reportFetchAttempts = configuration.reportFetchAttempts();

    public void importData(String scheme, String dataSource, Resource target) throws ImportException {"Importing analytics data for list var - " + dataSource);

        try {
            String useAnalyticsFrameworkPath = target.getValueMap().get(USE_ANALYTICS_FRAMEWORK, String.class);

                log.warn("Analytics framework path property " + USE_ANALYTICS_FRAMEWORK + " missing on " + target.getPath());

            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);

            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");


            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);

  "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);

            cfJcrContent = cfResource.getChild("jcr:content").adaptTo(Node.class);

            cfJcrContent.setProperty(EAEM_ANALYTICS_VIEWS, metrics.getJSONArray("counts").getString(0));


    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);

            name = "EAEM Analytics Report Importer",
            description = "Imports Analytics List Variable Reports periodically into AEM"
    public @interface EAEMListVariablesReportConfiguration {

                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;

                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/ 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="" xmlns:jcr=""

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="" xmlns:jcr="" xmlns:rep="internal"

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/ with write access to update content fragment nodes

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="" xmlns:jcr=""

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


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",
        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) {

        var $childPages = $(e.currentTarget).find(SEL_DAM_ADMIN_CHILD_PAGES);


        firstLoad = false;



    function addFragmentViews(e){
        if(!e.currentTarget || !isFragmentViewsEnabled()){

        var $currentTarget = $(e.currentTarget),
            foundationLayout = $"foundation-layout");


        var layoutId = foundationLayout.layoutId,
            folderPath = getFolderPath();

        if(layoutId !== LAYOUT_LIST_VIEW){

        $.ajax(folderPath + ".2.json").done(function(data){
            $(".foundation-collection-item").each(function(index, item){
                itemHandler(data, layoutId, $(item) );

    function itemHandler(data, layoutId, $item){

        var itemPath = $"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));
            $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