wp-rocket icon indicating copy to clipboard operation
wp-rocket copied to clipboard

As a Product Manager, I want to track clicks on educational/help content

Open MathieuLamiot opened this issue 6 months ago • 1 comments

Context

Track user interactions (clicks) with all available help resources and educational materials within the plugin UI under main sections:

  • Getting Started

  • Frequently Asked Questions

  • Documentation (differentiate between the one on the homepage and the sidebar)

  • Beacon directly (Click on the Help bzacon button)

  • "Need help?" right side of sections title

  • How to correctly measure your website’s loading time

Dependencies

MixPanel SDK must be installed & configured (done by Remy in a previous PR): https://github.com/wp-media/wp-rocket/issues/7436

Expected behavior

When a user clicks on one of the aforementioned button, if data tracking is activated (opted-in), then, a MixPanel event "WPM Button Clicked" must be fired with the following properties:

  • button (string): identifier of which button was clicked.
  • brand (string): WP Media
  • Product (string): WP Rocket
  • Context (string): "wp_plugin"
  • path (string): current path the user is on (wp-admin/options-general.php?page=wprocket most likely?)

The events must be identified to the email (hashed) of the WP Rocket licence.

Acceptance Criteria

  • Install WP Rocket. keep the data tracking opted-out (by default)
  • Click on the tracked buttons. Nothing must be sent to MixPanel.
  • Activate the data tracking.
  • Click on the tracked buttons. Events must be sent to MixPanel.
    • The event must be linked to the MixPanel user with the email on the WP Rocket licence.
    • The properties of the events must be as expected. In particular, one must be able to precisely identify which button was clicked.

Additional Information

Recommendation from Remy is to use the JS SDK for this, as it's mostly front-end tracking. The alternative could be to use AJAX calls but the performance drawbacks are too big.

MathieuLamiot avatar Jun 04 '25 12:06 MathieuLamiot

@DahmaniAdame Could you confirm this expectation: "The events must be identified to the email (hashed) of the WP Rocket licence."? If we do this, then we won't track each user on the website as different users (if there are 2 admins on the website for instance).

On the other hand, if we track based on the user email on the WordPress website, it might not match the email of the user who purchased WP Rocket, so the tracking on MixPanel will be much more complex.

MathieuLamiot avatar Jun 04 '25 12:06 MathieuLamiot

Scope a solution

Technical Solution

Implementation Approach

Leverage the existing MixPanel JavaScript SDK that's already loaded in the admin area to track help resource interactions directly from the frontend using the "WPM Button Clicked" event with consistent properties across all touchpoints.

Development Tasks

1. Frontend: Enhanced Beacon Click Handler File: beacon.js:

var $ = jQuery;
$(document).ready(function(){
    if ('Beacon' in window) {
        var $help = $('.wpr-infoAction--help');
        $help.on('click', function(e){
            var ids = $(this).data('beacon-id');
            var button = $(this).data('track-button') || 'Beacon Help';
            var context = $(this).data('track-context') || 'Settings';
            
            // Track with MixPanel JS SDK
            wprTrackHelpButton(button, context);
            
            // Continue with existing beacon functionality
            wprCallBeacon(ids);
            return false;
        });

        function wprCallBeacon(aID){
            aID = aID.split(',');
            if ( aID.length === 0 ) {
                return;
            }

            if ( aID.length > 1 ) {
                window.Beacon("suggest", aID);

                setTimeout(function() {
                    window.Beacon("open");
                }, 200);
            } else {
                window.Beacon("article", aID.toString());
            }
        }
    }
    
    // MixPanel tracking function
    function wprTrackHelpButton(button, context) {
        if (typeof mixpanel !== 'undefined' && mixpanel.track) {
            // Check if user has opted in
            if (typeof rocket_mixpanel_optin !== 'undefined' && !rocket_mixpanel_optin) {
                return;
            }
            
            mixpanel.track('WPM Button Clicked', {
                'button': button,
                'brand': 'WP Rocket',
                'product': 'WP Rocket',
                'context': context,
                'path': window.location.pathname + window.location.search
            });
        }
    }
    
    // Make function globally available
    window.wprTrackHelpButton = wprTrackHelpButton;
});

2. Frontend: Generic Help Button Tracking File main.js

Add the following js:

$(document).ready(function() {
    // Track clicks on various help elements
    $(document).on('click', '[data-track-help]', function(e) {
        if (typeof window.wprTrackHelpButton === 'function') {
            var $el = $(this);
            var button = $el.data('track-help');
            var context = $el.data('track-context') || '';
            
            window.wprTrackHelpButton(button, context);
        }
    });

    // Track specific help resource clicks with explicit selectors
    $(document).on('click', '.wpr-getting-started-link', function() {
        if (typeof window.wprTrackHelpButton === 'function') {
            window.wprTrackHelpButton('Getting Started', 'Dashboard');
        }
    });

    $(document).on('click', '.wpr-faq-link', function() {
        if (typeof window.wprTrackHelpButton === 'function') {
            window.wprTrackHelpButton('FAQ', 'Sidebar');
        }
    });

    $(document).on('click', '.wpr-documentation-link', function() {
        if (typeof window.wprTrackHelpButton === 'function') {
            window.wprTrackHelpButton('Documentation', 'Sidebar');
        }
    });
    
    // Track "How to measure loading time" link
    $(document).on('click', 'a[href*="correctly-measure-website-loading-time"]', function() {
        if (typeof window.wprTrackHelpButton === 'function') {
            window.wprTrackHelpButton('Loading Time Guide', 'Performance');
        }
    });

    // Track "Need help?" links
    $(document).on('click', '.wpr-need-help-link', function() {
        if (typeof window.wprTrackHelpButton === 'function') {
            window.wprTrackHelpButton('Need Help', 'General');
        }
    });
});

3. Backend: Expose Opt-in Status to JavaScript We need proper way to know if the user opt-in for tracking: File: Tracking.php

/**
 * Add opt-in status to admin scripts
 */
public function localize_optin_status() {
    if (!current_user_can('rocket_manage_options')) {
        return;
    }
    
    wp_localize_script('rocket-admin', 'rocket_mixpanel_optin', $this->optin->is_optin_enabled());
}

File: Subscriber.php: Modify the get_subscribed_events() to add the localize optin status function.

public static function get_subscribed_events() {
    return [
        'admin_init'                                             => 'migrate_optin',
        'update_option_wp_rocket_settings'                       => 'track_option_change',
        'wp_ajax_rocket_toggle_optin'                           => 'ajax_toggle_optin',
        'admin_enqueue_scripts'                                 => ['localize_optin_status', 15], // After scripts are enqueued
    ];
}

public function localize_optin_status() {
    $this->tracking->localize_optin_status();
}

4. Backend: Update Templates with Tracking Attributes File: sidebar.php: Update existing button to include more info

<?php
<button class="wpr-infoAction wpr-infoAction--help" 
        data-beacon-id="existing-beacon-id"
        data-track-button="Beacon Help" 
        data-track-context="Settings">
    <i class="wpr-icon wpr-icon--help"></i>
</button>

Results

Events properties Schema:

{
  'button': 'Beacon Help' | 'FAQ' | 'Documentation' | 'Tutorial Video Title' | 'Loading Time Guide' | 'Ask Support',
  'brand': 'WP Rocket',
  'product': 'WP Rocket', 
  'context': 'Settings' | 'Sidebar' | 'Dashboard' | 'FAQ' | 'Getting Started' | 'Documentation',
  'path': '/wp-admin/options-general.php?page=wprocket' // Current admin page path
}

Efforts

S

Miraeld avatar Jun 30 '25 01:06 Miraeld

@DahmaniAdame Could you confirm this expectation: "The events must be identified to the email (hashed) of the WP Rocket licence."?

Yes. No need to over complicate things. There will generally be one person managing performance and interacting with the plugin. Worst case scenario, we will be collecting the general usage patterns of that website. But my bet is it's going to be diluted into the most common scenario: one person doing it all.

DahmaniAdame avatar Jun 30 '25 08:06 DahmaniAdame

@Miraeld I like the idea of having data attributes, just a small note, plz use wpr_ as a prefix to make sure that it won't conflict with any other js.

Looks good to me but didn't test it.

wordpressfan avatar Jul 01 '25 12:07 wordpressfan

@Miraeld @DahmaniAdame Can you check on MixPanel the first events we received? (on the WP Media MixPanel account for now). I am not sure we can identify which button was clicked (see the AC "In particular, one must be able to precisely identify which button was clicked."). We have the info about "button" and "context" being "Documentation" and "Sidebar". I'm not sure this is precise enough for you @DahmaniAdame ? SHouldn't we have a unique identifier per button?

Image

MathieuLamiot avatar Jul 09 '25 06:07 MathieuLamiot

@MathieuLamiot it's the documentation button on the sidebar. It's identifiable.

But I would rather stick with actual buttons labels. It makes it easy to spot.

SHouldn't we have a unique identifier per button?

Could be helpful to have a static identifier to avoid mixups, but not sure how friendly it will be on Mixpanel?

DahmaniAdame avatar Jul 09 '25 07:07 DahmaniAdame

You can send whatever string you want to MixPanel, so that would be fine if we have a string ID per button. As long as you have enough granularity for you @DahmaniAdame, all good from my perspective 👌

MathieuLamiot avatar Jul 09 '25 08:07 MathieuLamiot

Sounds good to me. Having a unique ID will reduce confusion for sure. Now that I think of it, it should also help test wording changes as well without loosing the tracker item data. The ID will need to be a predefined static string and not dynamically generated (from the label content... etc.).

DahmaniAdame avatar Jul 09 '25 09:07 DahmaniAdame

If we go with the ID solution, could we get a list of IDs defined by Product, so we won't be going back and forth ?

Miraeld avatar Jul 10 '25 00:07 Miraeld

Created this issue as a follow up: https://github.com/wp-media/wp-rocket/issues/7494

wordpressfan avatar Jul 10 '25 03:07 wordpressfan

Sent to ready for QA based on this discussion: https://group-onecom.slack.com/archives/C08EFCYRQ2X/p1752117316919649?thread_ts=1752053118.937739&cid=C08EFCYRQ2X

wordpressfan avatar Jul 10 '25 03:07 wordpressfan