Monday, March 18, 2019

AEM 6420 - Assets PDF Insights - Importing PDF Analytics into AEM

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 Content Fragments Insights 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 pdf path (link on page for download)

2. Create a new HTL component to render the PDF download link on page

3. Refresh page, click on download link and make sure the pdf path is sent to Analytics in evar variable evar1 (v1)

4. Setup a polling importer to import Analytics data into AEM at regular intervals

5. Add the data in jcr:content/performance@dam:impressionCount (otb Impressions column in Assets List View displays this value)

Demo | Package Install | Github


List View in AEM








PDF Views Report in Analytics





Configure Adobe Launch

1) First step is add Rules and Data Elements in Adobe Launch to get data from the custom data layer (created in next section). In the launch configuration exercise, we added necessary configuration for content fragments, here we add the configuration for capturing downloaded pdf path

2) In the Adobe Context Hub Launch extension, add pdfPath json schema element and create a data element pdf_on_page


3) In Rules section, add a new rule to set the Analytics variable eVar1 when user clicks on link with css selector .eaem-pdf-link-click which is the pdf download link (in the next section we'll create an AEM component to set this css class on download link). The assumption here, there is only one download PDF link on page



4) Here we don't configure a Send Beacon action as clicking on download link sends a beacon; we just set  the eVar1 (v1) value to pdf path that is sent along... Publish the changes to Production Launch (only for demonstration, always test on Stage Launch and Publish to Production Launch...)



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 pdf path data structure (the data layer root window.eaemData was referred in Adobe Launch extension Context Hub in this exercise) pdfPath schema element added in previous section of this post

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 : [  ]
                },
                pdfPath: undefined
            }
        }
    </script>


2) The download pdf component rendered on page can now set PDF path with window.eaemData.page.pdfPath = "some_pdf_path_in_aem_assets"; 


Download PDF Component

1) Create a new component /apps/eaem-assets-pdf-insights/components/eaem-download-pdf-component to create the download link for selected PDF



2) Add the following code in /apps/eaem-assets-pdf-insights/components/eaem-download-pdf-component/eaem-download-pdf-component.html. Here the download link anchor is set with css class eaem-pdf-link-click so Adobe Launch can trigger Analytics Set Variables action when user clicks on the link

<div style="margin: 10px 0 10px 0">
    <div data-sly-test="${!properties.eaemPDFPath}" data-sly-unwrap>
        No PDF selected to View / Download
    </div>
    <div data-sly-test="${properties.eaemPDFPath}" data-sly-unwrap>
        Click on the link to
        <a class="eaem-pdf-link-click" href="${properties.eaemPDFPath}" download>
            View / Download "${properties.eaemTitle}"
        </a>
        <script>
            window.eaemData.page.pdfPath = "${properties.eaemPDFPath @ context='scriptString'}";
        </script>
    </div>
</div>


Verify PDF Path sent to Analytics

1) Drag and drop the component created above EAEM PDF Component and select a pdf



2) Refresh the page in published mode (wcmmode=disabled), click on the download PDF link and check the pdf path sent to Analytics. It may take some time for the views to show up in Analytics reports. Also, alternatively you can use the variable pev1 to get a report instead of configured evar v1 (check the Analytics data collection parameters and api to access them)



Setup Polling Importer

1) Create Polling Importer apps.experienceaem.assets.PDFViewsImporter 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.Configuration;
import com.day.cq.wcm.webservicesupport.ConfigurationManager;
import com.day.cq.wcm.webservicesupport.ConfigurationManagerFactory;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.resource.*;
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.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;

@Component(
        immediate = true,
        service = {Importer.class},
        property = {
                Importer.SCHEME_PROPERTY + "=" + PDFViewsImporter.SCHEME_VALUE
        }
)
@Designate(ocd = PDFViewsImporter.PDFViewsImporterConfiguration.class)
public class PDFViewsImporter 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 UPDATE_ANALYTICS_SERVICE = "eaem-update-pdf-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(PDFViewsImporterConfiguration configuration) {
        reportFetchDelay = configuration.reportFetchDelay();
        reportFetchAttempts = configuration.reportFetchAttempts();
    }

    public void importData(String scheme, String dataSource, Resource target) throws ImportException {
        log.info("Importing analytics data for evar - " + 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 pdfPath;

        Resource pdfResource;
        Node pdfJcrContent;
        ModifiableValueMap modifiableValueMap = null;

        for(int d = 0, len = data.length(); d < len; d++){
            metrics = data.getJSONObject(d);
            pdfPath = metrics.getString("name");

            pdfResource = resolver.getResource(pdfPath);

            if(pdfResource == null){
                log.warn("PDF Asset not found - " + pdfPath);
                continue;
            }

            Resource performanceRes = ResourceUtil.getOrCreateResource(pdfResource.getResourceResolver(),
                                pdfPath + "/" + "jcr:content" + "/" + "performance", (String)null, (String)null, false);

            modifiableValueMap = performanceRes.adaptTo(ModifiableValueMap.class);

            modifiableValueMap.put("dam:impressionCount", metrics.getJSONArray("counts").getString(0));
            modifiableValueMap.put("jcr:lastModified", Calendar.getInstance());
        }

        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 = -365;
        }

        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 PDFViewsImporterConfiguration {
        @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-assets-pdf-insights/config/com.day.cq.polling.importer.impl.ManagedPollConfigImpl-eaem-pdf-insights and set id as eaem-evar-pdf-report-days source as evar1 among other values. Here we trying to pull a report created, with data collected in custom variable evar1 which is configured for PDF Views in Analytics

<?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-evar-pdf-report-days"
    interval="{Long}86400"
    reference="{Boolean}true"
    source="eaemReport:evar1"/>

3) Create cq:PollConfigFolder /etc/eaem-pdf-insights/analytics-evars-report-poll-config with a reference to the managed poll configuration created above eaem-evar-pdf-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-evar-pdf-report-days"
    use-analytics-framework="/etc/cloudservices/sitecatalyst/ags-959-analytics/experience-aem-analytics-framework"
    get-analytics-for-last-days="{Long}-365"
    source="eaemReport:evar1"/>

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 365 days data

5) Create a service user mapping /apps/eaem-assets-pdf-insights/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-eaem-pdf-insights with write access to update PDF nodes, here we create the node performance and set property dam:impressionCount

<?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-assets-pdf-insights-bundle:eaem-update-pdf-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




List View Settings

1)  In the List view enable Impressions column



2) The impressions count imported by PDFViewsImporter into jcr:content/performance@dam:impressionCount is displayed using otb list view framework


No comments:

Post a Comment