Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add ga4 user registered handler #2281

Merged
merged 7 commits into from
Mar 1, 2023
Merged

Conversation

leogermani
Copy link
Contributor

@leogermani leogermani commented Feb 15, 2023

All Submissions:

Changes proposed in this Pull Request:

Closes # .

How to test the changes in this Pull Request:

Get the GA4 credentials. You will need your measurement ID and an API secret.

  • api_secret - Required. An API SECRET generated in the Google Analytics UI. To create a new secret, navigate to:
    Admin > Data Streams > choose your stream > Measurement Protocol > Create
  • measurement_id - Required. The measurement ID associated with a stream. Found in the Google Analytics UI under:
    Admin > Data Streams > choose your stream > Measurement ID

Store this info in the database:

wp option set ga4_measurement_id "G-XXXXXXXXXX"
wp option set ga4_api_secret YYYYYYYYYYYYYYYYYYY

Now, let's test:

  1. Make sure RAS is active and that you have a registered reader
  2. Make sure to add this constant to wp-config
define( 'NEWSPACK_LOG_LEVEL', 2 );
define( 'NEWSPACK_EXPERIMENTAL_GA4_EVENTS', true );
  1. Register the site (either via a registration prompt or from the my account page)
  2. Watch the reader_registered event being fired in the logs
[NEWSPACK-DATA-EVENTS][Newspack\Data_Events::dispatch]: Dispatching action "reader_registered" via Action Scheduler.
  1. Watch the handler being triggered and sending the reader_registered GA event:
[NEWSPACK-DATA-EVENTS][Newspack\Data_Events::handle]: Executing action handlers for "reader_registered".
[NEWSPACK-DATA-EVENTS-GA4][Newspack\Data_Events\Connectors\GA4::log]: Event sent - reader_registered
  1. Visit the Google Analytics Realtime dashboard
  2. See the reader_registered events coming in
  3. Inspect them and see the registration_method attribute set as registration-block for the times you registered via the registration block.
  4. See that the referer attribute is correct
  5. Register from inside a popup and see that, in those cases, you'll also get some campaign specific data:
(
    [logged_in] => yes
    [ga_session_id] => 1677185108
    [is_reader] => yes
    [registration_method] => registration-block-popup
    [campaign_id] => 139
    [campaign_title] => Registration Overlay
    [campaign_frequency] => always
    [campaign_placement] => center
    [campaign_has_registration_block] => 1
    [campaign_has_donation_block] => 
    [campaign_has_newsletter_block] => 
    [referer] => /2023/02/07/accumsan-nam-mauris-erat-ultrices-nascetur-porta-ornare-est-class-cursus-massa-lectus/
)

(you can also add a debug to the handle_reader_registered method to see these params being set).

Note that this PR also fixes kind of a bug. Something that was working by accident.

In the line:

$metadata['current_page_url']    = home_url( add_query_arg( array(), \wp_get_referer() ) );

wp_get_referer() always returns false because the referer is the same page as the current page. (It's a post to itself). The accident is that if the second parameter to add_query_arg is false, it will use the current URL instead. And since this is a post to itself, the result is the same...

But my code fixes it and we now actually read the value from the _wp_http_referer in the form.

Other information:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your changes, as applicable?
  • Have you successfully ran tests with your changes locally?

@leogermani leogermani added the [Status] Needs Review The issue or pull request needs to be reviewed label Feb 15, 2023
@leogermani leogermani requested a review from a team as a code owner February 15, 2023 18:07
@leogermani leogermani self-assigned this Feb 15, 2023
Comment on lines 41 to 43
foreach ( self::$watched_events as $event_name ) {
Data_Events::register_handler( [ __CLASS__, 'handle_' . $event_name ], $event_name );
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not make it a global handler that watches every event? A global handler exists mostly for analytics integrations like this one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll explore it

I want to have each event handler in its own callback for the sake of organization, I didn't want to have one big callback with a switch case or anything like it. But let me give it a go

/**
* Initialize the class and registers the handlers
*
* @return void
*/
public static function init() {
Data_Events::register_handler( [ __CLASS__, 'handle_reader_logged_in' ], 'reader_logged_in' );
foreach ( self::$watched_events as $event_name ) {
Data_Events::register_handler( [ __CLASS__, 'handle_' . $event_name ], $event_name );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like a very fragile implementation. If the watched_events are modified it will call for an undefined method.

Comment on lines 122 to 125
$params['registration_method'] = $data['metadata']['registration_method'] ?? '';

$event = new Event( 'reader_registered', $params );
self::send_event( $event, $client_id, $timestamp, $user_id );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the context of a global handler for analytics, it can look like this:

Being a global hanler, this suggestion assumes the first argument of the method is the $action_name.

Suggested change
$params['registration_method'] = $data['metadata']['registration_method'] ?? '';
$event = new Event( 'reader_registered', $params );
self::send_event( $event, $client_id, $timestamp, $user_id );
$event = new Event( $action_name, $data );
self::send_event( $event, $client_id, $timestamp, $user_id );

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cant' simply pass whatever is in $data to GA4. Each event will have its own handler to add the appropriate parameters

Copy link
Member

@miguelpeixe miguelpeixe Feb 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish we could. The listeners' documentation would be the source of truth on what and how we collect and we avoid having another layer of support for each new trackable thing. The more we track the better, there's no downside to that IMO.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a limit of 25 parameters an event can have in GA, so I think we want to carefully choose what we will track.

Also, some events have arrays as parameters, that need to be treated...

@leogermani
Copy link
Contributor Author

leogermani commented Feb 23, 2023

@miguelpeixe This is ready for another review.

I slightly changed the implementation, using a global handler, but I kept one method for each event. Each event will have a very unique way of setting up its parameters. (In fact, if this gets too big, I might refactor it into having each event handling in one simple child class with only one method)

Please note that the handle_reader_logged_in doing nothing is also intentional. I want to be as explicit as possibe, and avoid situations where event tracking would silently fail without anyone noticing.

Note that I've updated the test instructions.

Copy link
Member

@miguelpeixe miguelpeixe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The registration metadata is being logged as described, except for the campaign popup metadata, which I was not able to test:

add_filter( 'newspack_data_events_dispatch_body', [ __CLASS__, 'filter_event_body' ], 10, 2 );
}

/**
* Handler for the reader_logged_in event.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is no longer a specific handler method

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment on lines +134 to 136
if ( ! empty( $data['metadata']['newspack_popup_id'] ) ) {
$params = array_merge( $params, Popups_Events::get_popup_metadata( $data['metadata']['newspack_popup_id'] ) );
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't know the reason, but the newspack_popup_id is not being populated to my registration through a campaigns popup. This is the resulting params from a center overlay campaign:

(
    [logged_in] => yes
    [is_reader] => yes
    [registration_method] => registration-block
    [referer] => /
)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check if you have blocks and popups plugins in the latest master and that they are built

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was the popups plugin that was outdated!

@miguelpeixe
Copy link
Member

After getting all params working from a campaigns' prompt the GA4\Event::validate_params() is not happy with it:

[28-Feb-2023 13:24:09 UTC] [57031][NEWSPACK-DATA-EVENTS][Newspack\Data_Events::handle]: Executing global action handlers for "reader_registered".
[28-Feb-2023 13:24:09 UTC] Array
(
    [logged_in] => yes
    [is_reader] => yes
    [registration_method] => registration-block-popup
    [campaign_id] => 394
    [campaign_title] => Reader Registration
    [campaign_frequency] => daily
    [campaign_placement] => center
    [campaign_has_registration_block] => 1
    [campaign_has_donation_block] =>
    [campaign_has_newsletter_block] =>
    [referer] => /
)

[28-Feb-2023 13:24:09 UTC] [57031][NEWSPACK-DATA-EVENTS][Newspack\Data_Events::handle][ERROR]: Invalid event parameters. Limit is 25 parameters, included the default parameters. Values must have a max of 100 characters.

This error message is not too helpful because it can be any of:

  • Max params count exceeded
  • Invalid param name on any of the params
  • Invalid param value on any of the params

Perhaps the validate_params() method could be the thrower and more verbose?

In this case, this is the culprit: [campaign_has_registration_block] => 1. The validation is not happy with its value because the method doesn't handle a boolean.

@miguelpeixe
Copy link
Member

miguelpeixe commented Feb 28, 2023

I also suggest making the GA4\Event parse the parameters to become valid while still logging the issue so it can be fixed. We don't want to lose the entire event due to bad parameter(s).

I suggest the parsing to:

  • transform invalid names to become valid by replacing/removing characters when possible (slugify?)
  • add an extra parameter containing a list of the names of parameters that were removed in case of an invalid value or exceeded the limit of 25

@leogermani
Copy link
Contributor Author

This error message is not too helpful

Yes, I know. I struggled a bit when I was doing this and decided to keep it simple for starters because I didn't want to over complicate.

But I agree with you, let's make it correct and sanitize the parameters and just throw a warning log in case something is invalid.

@leogermani
Copy link
Contributor Author

@miguelpeixe done!

I want to keep the Event class simple, as the source of truth of the limitations that GA4 has to events. And I didn't want to transform validation methods into validate & sanitize...

So I made the event values a little more flexible, accepting data types we can easily convert to strings, but still throwing error otherwise.

And then added the validating and error handling to our class. I think it looks ok now.

In the future we might want to surface these errors, and the events api errors in general, somehow....

@miguelpeixe miguelpeixe self-requested a review March 1, 2023 15:28
Copy link
Member

@miguelpeixe miguelpeixe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the tweaks and improved debugging! It's working great now 🎉:

image

Non-blocking NIT, by instantiating a new Event() with name and params, isn't non-static methods a better practice for this case?

$event = new Event( $name, $params );
if ( ! $event->has_valid_name() ) {
	throw new \Exception( 'Invalid event name' );
}

@github-actions github-actions bot added [Status] Approved The pull request has been reviewed and is ready to merge and removed [Status] Needs Review The issue or pull request needs to be reviewed labels Mar 1, 2023
@leogermani leogermani merged commit 5eb2336 into master Mar 1, 2023
@leogermani
Copy link
Contributor Author

by instantiating a new Event() with name and params, isn't non-static methods a better practice for this case?

Not necessarily. If the method does not interact or depend on anything from the instanced object, it could be static.

It could be non-static if it was, for example, writing the errors to a errors attribute in the object, or manipulating the object attributes... but then this would grow in complexity.

By keeping it static we are saying "If you provide me invalid params, I will throw exceptions, use my static methods to validate them before instantiating an object. It's also super easy to test those methods as well.

@miguelpeixe miguelpeixe deleted the add/ga4-registered-event branch March 1, 2023 19:08
matticbot pushed a commit that referenced this pull request Mar 3, 2023
# [1.106.0-alpha.1](v1.105.0...v1.106.0-alpha.1) (2023-03-03)

### Bug Fixes

* **ads:** gam api availability according to error type ([#2289](#2289)) ([024fe08](024fe08))

### Features

* add a Add new button to subscription lists ([#2314](#2314)) ([9543ad2](9543ad2))
* add ga4 user registered handler ([#2281](#2281)) ([5eb2336](5eb2336))
* add pid to Logger ([#2290](#2290)) ([fd3011c](fd3011c))
* Add popup info to donations ([#2300](#2300)) ([7ea800b](7ea800b))
* allow external links in dashboard via a filter ([#2279](#2279)) ([3943b1a](3943b1a))
* campaigns listeners for the data events api ([#2291](#2291)) ([ab407d4](ab407d4))
* disable save button for unchanged settings ([#2259](#2259)) ([e06d72f](e06d72f)), closes [#1531](#1531)
* **donate-block:** support modal checkout ([#2256](#2256)) ([34226dd](34226dd))
* Normalize donation events ([#2299](#2299)) ([2624d53](2624d53))
* **perfmatters:** improve config ([267306e](267306e))
* prevent homepage from being unpublished ([#2307](#2307)) ([a151d53](a151d53))
* Remove the campaign rendered event ([#2301](#2301)) ([23caa1d](23caa1d))
* Stripe Subscriptions to WC subscriptions migrator ([#2298](#2298)) ([6904356](6904356)), closes [#2251](#2251)
* **wc:** force allowing subscription switching ([#2305](#2305)) ([c13e741](c13e741))
@matticbot
Copy link
Contributor

🎉 This PR is included in version 1.106.0-alpha.1 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

matticbot pushed a commit that referenced this pull request Mar 14, 2023
# [1.106.0](v1.105.1...v1.106.0) (2023-03-14)

### Bug Fixes

* **ads:** gam api availability according to error type ([#2289](#2289)) ([024fe08](024fe08))
* show handoff to finish Newspack setup only if setup is incomplete ([#2343](#2343)) ([1173b5b](1173b5b))

### Features

* add a Add new button to subscription lists ([#2314](#2314)) ([9543ad2](9543ad2))
* add ga4 user registered handler ([#2281](#2281)) ([5eb2336](5eb2336))
* add pid to Logger ([#2290](#2290)) ([fd3011c](fd3011c))
* Add popup info to donations ([#2300](#2300)) ([7ea800b](7ea800b))
* allow external links in dashboard via a filter ([#2279](#2279)) ([3943b1a](3943b1a))
* campaigns listeners for the data events api ([#2291](#2291)) ([ab407d4](ab407d4))
* disable save button for unchanged settings ([#2259](#2259)) ([e06d72f](e06d72f)), closes [#1531](#1531)
* **donate-block:** support modal checkout ([#2256](#2256)) ([34226dd](34226dd))
* Normalize donation events ([#2299](#2299)) ([2624d53](2624d53))
* **perfmatters:** improve config ([267306e](267306e))
* prevent homepage from being unpublished ([#2307](#2307)) ([a151d53](a151d53))
* Remove the campaign rendered event ([#2301](#2301)) ([23caa1d](23caa1d))
* Stripe Subscriptions to WC subscriptions migrator ([#2298](#2298)) ([6904356](6904356)), closes [#2251](#2251)
* **wc:** force allowing subscription switching ([#2305](#2305)) ([c13e741](c13e741))
@matticbot
Copy link
Contributor

🎉 This PR is included in version 1.106.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
released on @alpha released [Status] Approved The pull request has been reviewed and is ready to merge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants