site-kit-wp icon indicating copy to clipboard operation
site-kit-wp copied to clipboard

Add Google Analytics page views count to WP Admin's Posts list view.

Open jamesozzie opened this issue 3 years ago β€’ 36 comments

Feature Description

Considering adding a total Google Analytics view count on both the pages and posts tab, as per the screenshot below, once Site Kit is set up with Analytics connected. This could be a sortable column, as some of the other columns are within the same list views.

image

This is a feature request on behalf of one user in the support forums.


Do not alter or remove anything below. The following sections will be managed by moderators only.

Acceptance criteria

  • Given GA4 is connected, when an admin visits the Posts list view, then they should see a new column as follows:
    • Column title: GA Page Views (translatable string)
    • Column data: Metric should be screenPageViews and dimension should be pagePathPlusQueryString for the Post's URL. (Use existing GA4 datastore infrastructure to fetch this data).
    • The column should appear after the "Author" column (if it exists), otherwise it can appear as the column before the "Date".
    • The column should not be sortable, see: https://github.com/google/site-kit-wp/issues/5418#issuecomment-1420564334

Implementation Brief

For overview and clarity

The approach will use transient data to store post IDs, which maps to associative array holding it’s permalinks and views. ID will be used to match the data. Transient should be stored for 24 hours to minimize calls to generate the report only when necessary - otherwise it will be updated earlier in case when new post is added, or usage of pagination/filter. Visible posts permalinks on admin list view will be used with inListFilter dimension filter to make 1 batch request to pull the data. Transient will be allways replaced with new data, so at most it will hold 100 posts if user set 100 rows per table in the view which in most cases will be default 20. This data will be matched with $post_id in new post column to display views.

New class to handle transient data and post columns

  • Create new class, e.g. includes/Modules/Analytics_4/Post_List_View_Analytics_Column that will handle the report data and analytics column modification
    • The class constructor will accept a $transients instance.
    • Add methods to interact with transient data, you can see example in is_data_available and set_data_available methods of Custom_Dimensions_Data_Available class. You can name them get_page_views_column_data and set_page_views_column_data for example, and include method for deleting the transient delete_page_views_column_data. And transient can be named ga4_page_views.

Manage columns

  • Create a method, say manage_columns
    • Hook into manage_posts_columns, this hook passes$columns param. Define new variable new_columns as an empty array, which should hold the columns.
    • Iterate through $columns to find the comments key. Once found, add a new column, represented by the key ga4_page_views, to the array. Use translatable string GA Page Views as value.
    • If no column with comments key is not found, check for date to push ga4_page_views before it
    • After the checks just push original column to maintain the original array like $new_columns[$column_key] = $column_value;.
    • In case no comments and date columns were found, add ga4_page_views at the end as last to ensure column is present
  • Hook into manage_posts_custom_column which will populate the data, it accepts 2 params $column_name and, $post_id
    • Check if current $column_name is not ga4_page_views to return early
    • Otherwise collect transient data from get_page_views_column_data method and using $post_id extract the current post data (as post IDs are the keys of stored data arrays)
    • If data is found, $post_id exists in stored data array, output the views value
    • In case analytics is in gathering data state, return N/A

Analytics_4 class

  • Define new $post_list_view_analytics_column protected variable, that will hold the instance of Post_List_View_Analytics_Column
  • In __construct method set $post_list_view_analytics_column, by assigning it a new instance of Post_List_View_Analytics_Column, you can follow this example
  • Create a new protected method, say generate_posts_report, which should accept $paths array param (which will hold flat list of posts permalinks)
    • Re-use part of the logic from this block
      • Instead of returning error return null for example to exit early
      • Replicate the reportOptions in php array, like this one. With dimensionFilter for pagePathPlusQueryString, and keep same inListFilter, for value it will accept passed $paths arrays consisting of posts links.
      • Handle potential errors or failed API requests

Register method updates

  • Call register method on $post_list_view_analytics_column
  • Hook into pre_get_posts and utilize the $query parameter passed to it
  • Check if ! is_admin(), to return early
  • Check for $query->is_main_query and get_current_screen()->base !== 'edit-post' to return early
    • Then check if analytics module is connected, e.q $this->is_connected(), and if not, return early
    • Otherwise means it is on the All Posts page in the main query, with analytics 4 module being connected and it can be proceed
    • Continue by hooking into posts_selection (when query will be setup), and pass the received $query param from previous hook
      • Define new variable $current_posts_data with empty array as value.
      • Then do a loop on $query->posts to get the posts permalinks with get_permalink function. Add these to the $current_posts_data array, using the post ID as the key. Each post ID will map to an associative array with two keys: path, holding the permalink, and views, initially set to 0 to represent page views.
      • After the loop check if $current_posts_data is empty to return early
      • Check also for gathering data state, to return early, using $this->is_data_available()
      • Retrieve transient data using get_page_views_column_data method of $post_list_view_analytics_column and assign it to the variable, say $stored_data
      • Define $make_new_request variable with value false, which will be used to determine if analytics request should be made or not
      • If there is no $stored_data, $make_new_request should be true.
      • Compare $current_posts_data keys, with $stored_data keys (as permalinks can change), and in case of mismatch, set $make_new_request to true
      • If $make_new_request is true
        • Extract flat array of path from $current_posts_data and use it as param in generate_posts_report method to get report data
        • Update views value in $current_posts_data from report response to hold the metric value (screenPageViews) for all posts, by matching their path with received dimension pagePathPlusQueryString value
        • Save data into transient (for 24h) using set_page_views_column_data method of $post_list_view_analytics_column
        • If there is no data returned from report, assign value N/A to the page views
  • When propertyID changes, transient should be emptied, follow the example from this filter
    • Add it without feature flag
    • In case values mismatch, call delete_page_views_column_data method of $post_list_view_analytics_column

Test Coverage

  • Add PHP unit test case for the Analytics_4->generate_posts_report method
  • Add tests for new Post_List_View_Analytics_Column class

QA Brief

Changelog entry

jamesozzie avatar Jun 23 '22 09:06 jamesozzie

following

iamkingsleyf avatar Jun 23 '22 12:06 iamkingsleyf

Thank you and following!

shah-ahmadyusof avatar Jun 25 '22 14:06 shah-ahmadyusof

please implement this. This feature would be very helpful both for bloggers and SEOs.

StochasticLi avatar Sep 02 '22 11:09 StochasticLi

This would be a VERY useful thing for WordPress.

EmilioCodes avatar Sep 02 '22 11:09 EmilioCodes

That's exactly on point! I hope Google implements it soon, it would make our lives so much easier ;)

AveCeasar avatar Sep 02 '22 12:09 AveCeasar

+1

An option/setting/filter to save these values as post_meta would be equally as important as showing those values in the admin.

JiveDig avatar Sep 02 '22 13:09 JiveDig

I'd love to see this implemented as well. Keenly following!

taronavagyan avatar Sep 02 '22 16:09 taronavagyan

+1 Please!

Explorergt92 avatar Sep 02 '22 18:09 Explorergt92

+1 This would be incredibly helpful.

bscreative avatar Sep 02 '22 20:09 bscreative

This would be really nice

candland avatar Sep 12 '22 23:09 candland

@aaemnnosttv @marrrmarrr Have added some preliminary ACs. It makes sense to only implement this for GA4. Just need to add a bit more clarity on the "position" of this column.

I have also split this issue and created #6503 to implement the same column for the Pages list view separately.

jimmymadon avatar Jan 31 '23 15:01 jimmymadon

The above will require Site Kit's assets to be loaded on the Posts wp-admin page similar to how we load them for the WP Dashboard.

In this case, wouldn't it make more sense to load the data in PHP and then serve it directly in the UI? I suppose that means a request on every page load since we don't cache data in PHP, but it seems very excessive to load an entire React app for every table row and then make a getReport call for each.

I know this is venturing into implementation, but I wonder why this needs to be part of the ACs here, as it's more a "how" than a "what" for this issue. πŸ€”

tofumatt avatar Feb 01 '23 22:02 tofumatt

@tofumatt I completely agree - we can discuss this at the IB level and happy to remove this point from the ACs.

jimmymadon avatar Feb 02 '23 01:02 jimmymadon

Cool, in that case ACs here are good; moving to IB πŸ‘πŸ»

tofumatt avatar Feb 06 '23 22:02 tofumatt

@jimmymadon althought I think I'm not able to write the IB, as I'm not sure about how the PHP classes should be structured, some thoughts from my side:

I don't think the sorting functionality should be included in this issue. It's one thing load the data for the shown posts on every pageload, but its something completely different to add this sorting.

The sorting is done by the database query. The post lists are paginated to 10 posts / page (can be increased to 100 posts/page), so client side sorting won't work. To implement sorting, we would need to have the post views already in the database. Therefore we would need to somehow continuously sync the page view data from Analytics to our database and save the page views as a postmeta.

We currently don't store/sync analytics data to the WordPress database. If we want to have this sorting function, we should think in depth about how such a sync could look like and create a separate issue for it.

Just for the sake of completeness: instead of keeping the postview data in the WordPress database and synchronising it continuously, we could solve the pageview sorting "on the fly" with the help of "ORDER BY CASE". But that would still mean that we would have to load the pageviews of all posts. For WordPress installations with a large number of posts, this would mean quite a performance loss. Also, this would probably lead to compatibility problems with other plugins as we would have to edit the sql query directly instead of going via WP_Query. So this approach does not make sense.

derweili avatar Feb 07 '23 10:02 derweili

@aaemnnosttv @tofumatt Any thoughts on how to implement sorting here based on what @derweili has raised?

jimmymadon avatar Feb 07 '23 17:02 jimmymadon

Oh, actually, that's a great point and sometimes I didn't fully consider πŸ˜…

The filed issue says:

This could be a sortable column, as some of the other columns are within the same list views.

But really only "Title" and "Date" are sortable. Sorting by page views might be useful but adds a lot of complexity and overhead for minimal value, especially as you can view top-performing pages elsewhere in Analytics views.

It's not the primary purpose of this screen to view pages by page views, so I've updated the ACs and omitted the sortable requirement from it.

tofumatt avatar Feb 11 '23 23:02 tofumatt

Can one make an input? I think it should display the pageviews (all time or if there is a way change it between days like the sitekit page) then the sorting should be HIGHEST and LOWEST.

Currently our theme has this, views shows all time pageviews to that particular URL.

iamkingsleyf avatar Feb 12 '23 06:02 iamkingsleyf

@nfmohit As @derweili pointed out above, sorting will be hard here unless you figure out something completely different. If it is too cumbersome like we predict, I can remove sorting by page views from the AC and we can continue without it for now.

jimmymadon avatar Feb 17 '23 11:02 jimmymadon

Apologies, @tofumatt already made that change! πŸ˜„

jimmymadon avatar Feb 17 '23 11:02 jimmymadon

Putting in a vote for some API to (optionally?) use CRUD functions via PHP to get/set view counts in post_meta and/or options so we can use WP_Query to get the most popular or currently trending posts according to GA.

JiveDig avatar Feb 17 '23 14:02 JiveDig

Hi @nfmohit, thanks for drafting this IB. I have a minor point, and a more substantial aspect that we need to consider.

The minor point is that we need to ensure the column is placed in the right order as per the AC, and can refer to this comment on the linked blog post for an example on how this can be achieved.

More substantially though, while the IB as is stands would work in terms of functionality, I see a problem with the approach.

By following this approach we'd end up with a synchronous series of GA4 report requests being made on the backend before the page is even rendered. This could be any number of requests - the default number of posts per page is 20.

This could introduce an unacceptably high delay to the page loading, which I am sure our users would not appreciate.

We need to batch the Analytics metric retrieval so that only one GA4 report request is made. However, having spent some time looking into it, I don't see this being realistically achievable on the PHP side, due to the way the code is structured for the post list page (for reference, here's the entry point to the loop where the table rows are rendered). Plus - even if we were to find a way, just adding one GA4 report request can introduce a significant delay, with a variety of factors in play.

I think that a better approach here would be to add the column with a placeholder for the data in PHP, and then use JS to make the GA4 report request and populate the data on the client-side. I'm not sure exactly how we'd do this without looking into it, but I'm sure it's achievable. What do you think?

techanvil avatar Mar 01 '23 12:03 techanvil

Hi

Nice work guys, but how is this coming up? its been over a year now.

iamkingsleyf avatar Jul 31 '23 20:07 iamkingsleyf

Nice work guys, but how is this coming up? its been over a year now.

Hi, @iamkingsleyf. Thank you for checking in. Unfortunately, this had to fall back a little since we had some other priorities that came up. We acknowledge this feature has received quite a good number of +1s, and we will work on putting this back in the pipeline again.

nfmohit avatar Aug 02 '23 06:08 nfmohit

Nice work guys, but how is this coming up? its been over a year now.

Hi, @iamkingsleyf. Thank you for checking in. Unfortunately, this had to fall back a little since we had some other priorities that came up. We acknowledge this feature has received quite a good number of +1s, and we will work on putting this back in the pipeline again.

Alright thanks.

iamkingsleyf avatar Aug 02 '23 07:08 iamkingsleyf

Hi @nfmohit, thanks for drafting this IB. I have a minor point, and a more substantial aspect that we need to consider.

Thank you for the kind IB review, @techanvil! Unfortunately, I had to leave this one hanging and unassigned myself as I had to adhere to other priorities. Apologies for not leaving a comment earlier, I remember to have left one but don't see it now, so it might have not gone through.

Since the approach needs some more thought, I'll unassign myself from here so that someone who has the capacity can pick up the IB review feedback. Thank you!

CC: @bethanylang

nfmohit avatar Aug 02 '23 07:08 nfmohit

Thanks, @zutigrm. This is a good start, but we need to make some improvements to IB:

  • Create new class, e.g. Post_List_View_Analytics_Column that will handle the report data and analytics column modification

Where should it be created? Which namespace should it use? I believe it should live in the Google\Site_Kit\Modules\Analytics_4 namespace, right?

  • The class constructor will accept a $modules instance. This will be injected to provide access to the analytics module using it’s get_module( $module_slug ) method
  • Then using module instance check if analytics 4 module is available, e.q $this->modules->get_module(Analytics_4::MODULE_SLUG), and if not, return early
  • ... and use it as param in $this->modules->get_module(Analytics_4::MODULE_SLUG)->generate_posts_report() method to get report data
  • In includes/Plugin.php on init hook, instantiate Post_List_View_Analytics_Column and pass $modules instance
    • Call register method

This is not how it should work. The new class is part of the Analytics_4 functionality. Thus it should be instantiated and registered in the Analytics_4 class. In this case, we don't need to check whether the Analytics 4 module is available. The only thing that we will need to check is that the module is connected.

Also this will let us pass anything that we need from the module class to the column class. For example, we will need to pass the reference to the transients class to be able to read and manage transients. Use the Custom_Dimensions_Data_Available class as an example.

eugene-manuilov avatar Nov 04 '23 14:11 eugene-manuilov

@eugene-manuilov Thanks for the feedback. Originally I though it might be more handy to make this class more decoupled for flexibility as it would have access to the $modules which would be easier to extend in the future for some other module, hence the proposal to instantiate it that way. But on the second thought, it does make more sense to group it with Analytics module for instantiation as you suggested, as this will only rely on analytics data.

Updated IB per suggestions. I also included reseting of transient upon property change (something I missed originally), and showing N/A as a value if there is no report data, and check for gathering data state.

zutigrm avatar Nov 06 '23 10:11 zutigrm

@eugene-manuilov Reminder on this one please. :)

mxbclang avatar Jan 08 '24 14:01 mxbclang

@bethanylang this one i want to discuss on our round table call that is scheduled for the next week. Planned to discuss it in Dec on the same call but we skipped those calls.

eugene-manuilov avatar Jan 08 '24 14:01 eugene-manuilov