From bf7fdc1e4e19f45021e915ebb4091c4cc42c97d4 Mon Sep 17 00:00:00 2001 From: Jason Moon Date: Thu, 10 Oct 2019 16:04:55 -0600 Subject: [PATCH 1/8] Add TrainTracks analytics to Instant Search --- modules/search/class.jetpack-search.php | 19 +++++--- .../components/search-result-minimal.jsx | 45 +++++++++++++++---- .../components/search-results.jsx | 21 +++++++-- modules/search/instant-search/index.jsx | 9 ++++ .../instant-search/lib/array-overlap.js | 7 +++ modules/search/instant-search/lib/tracks.js | 34 ++++++++++++++ package.json | 1 + yarn.lock | 5 +++ 8 files changed, 123 insertions(+), 18 deletions(-) create mode 100644 modules/search/instant-search/lib/array-overlap.js create mode 100644 modules/search/instant-search/lib/tracks.js diff --git a/modules/search/class.jetpack-search.php b/modules/search/class.jetpack-search.php index f2b468d0aa652..7f204cee81ba5 100644 --- a/modules/search/class.jetpack-search.php +++ b/modules/search/class.jetpack-search.php @@ -204,6 +204,7 @@ public function load_assets() { $script_version = self::get_asset_version( $script_relative_path ); $script_path = plugins_url( $script_relative_path, JETPACK__PLUGIN_FILE ); wp_enqueue_script( 'jetpack-instant-search', $script_path, array(), $script_version, true ); + $this->load_and_initialize_tracks(); $widget_options = Jetpack_Search_Helpers::get_widgets_from_option(); if ( is_array( $widget_options ) ) { @@ -232,13 +233,14 @@ public function load_assets() { } // This is probably a temporary filter for testing the prototype. $options = array( - 'postTypes' => $post_type_labels, - 'siteId' => Jetpack::get_option( 'id' ), - 'widgets' => array_values( $widgets ), - 'sort' => $widget_options['sort'], - 'postTypeFilters' => $widget_options['post_types'], 'enableLoadOnScroll' => false, - 'locale' => str_replace( '_', '-', get_locale() ), + 'locale' => str_replace( '_', '-', get_locale() ), + 'postTypeFilters' => $widget_options['post_types'], + 'postTypes' => $post_type_labels, + 'siteId' => Jetpack::get_option( 'id' ), + 'sort' => $widget_options['sort'], + 'tracksUserData' => Jetpack_Tracks_Client::get_connected_user_tracks_identity(), + 'widgets' => array_values( $widgets ), ); /** * Customize Instant Search Options. @@ -265,6 +267,11 @@ public function load_assets() { } } + public function load_and_initialize_tracks() { + define( 'W_JS_VER', 60 ); // W_JS_VER + wp_enqueue_script( 'jetpack-instant-search-tracks', '//stats.wp.com/w.js', array(), W_JS_VER, true ); + } + /** * Get the version number to use when loading the file. Allows us to bypass cache when developing. * diff --git a/modules/search/instant-search/components/search-result-minimal.jsx b/modules/search/instant-search/components/search-result-minimal.jsx index 0c4f2283668cf..83dca9f20da75 100644 --- a/modules/search/instant-search/components/search-result-minimal.jsx +++ b/modules/search/instant-search/components/search-result-minimal.jsx @@ -9,6 +9,8 @@ import { h, Component } from 'preact'; * Internal dependencies */ import Gridicon from './gridicon'; +import arrayOverlap from '../lib/array-overlap'; +import { recordTrainTracksRender, recordTrainTracksInteract } from '../lib/tracks'; const ShortcodeTypes = { video: [ @@ -29,14 +31,42 @@ const ShortcodeTypes = { }; class SearchResultMinimal extends Component { - arrayOverlap( a1, a2 ) { - if ( ! Array.isArray( a1 ) ) { - a1 = [ a1 ]; - } - const intersection = a1.filter( value => a2.includes( value ) ); - return intersection.length !== 0; + componentDidMount() { + recordTrainTracksRender( this.getCommonTrainTracksProps() ); + } + + getCommonTrainTracksProps() { + return { + fetch_algo: 'jetpack-instant-search-api/v1', + fetch_position: this.props.index, + fetch_query: this.props.query, + railcar: this.props.railcarId, + rec_blog_id: this.props.result.fields.blog_id, + rec_post_id: this.props.result.fields.post_id, + ui_algo: 'jetpack-instant-search-ui/v1', + ui_position: this.props.index, + }; } + onClick = event => { + // User-triggered event + if ( event.isTrusted ) { + event.stopPropagation(); + event.preventDefault(); + // Send out analytics call + recordTrainTracksInteract( this.getCommonTrainTracksProps() ); + // Await next animation frame to ensure w.js processes the queue + requestAnimationFrame( () => { + // Re-dispatch click event + const clonedEvent = new event.constructor( event.type, event ); + event.target.dispatchEvent( clonedEvent ); + } ); + } else { + // Programmatically dispatched event from `dispatchEvent` + return true; + } + }; + render() { const { result_type, fields, highlight } = this.props.result; const { locale = 'en-US' } = this.props; @@ -114,11 +144,10 @@ class SearchResultMinimal extends Component { { postTypeIcon } diff --git a/modules/search/instant-search/components/search-results.jsx b/modules/search/instant-search/components/search-results.jsx index c581af638e26b..42274420f8f70 100644 --- a/modules/search/instant-search/components/search-results.jsx +++ b/modules/search/instant-search/components/search-results.jsx @@ -12,17 +12,30 @@ import { h, Component } from 'preact'; import SearchResultMinimal from './search-result-minimal'; import { hasFilter } from '../lib/query-string'; import ScrollButton from './scroll-button'; +import { getRailcarIdPrefix } from '../lib/tracks'; class SearchResults extends Component { - render_result( result ) { + constructor( props ) { + super( props ); + this.state = { railcarIdPrefix: getRailcarIdPrefix() }; + } + renderResult = ( result, index ) => { switch ( this.props.resultFormat ) { case 'engagement': case 'product': case 'minimal': default: - return ; + return ( + + ); } - } + }; render() { const { query } = this.props; @@ -72,7 +85,7 @@ class SearchResults extends Component { { sprintf( __( 'No results for "%s"', 'jetpack' ), query ) }

) } - { results.map( result => this.render_result( result ) ) } + { results.map( this.renderResult ) } { this.props.hasNextPage && ( { render( @@ -29,6 +30,14 @@ const injectSearchApp = grabFocus => { document.addEventListener( 'DOMContentLoaded', function() { if ( !! window[ SERVER_OBJECT_NAME ] && 'siteId' in window[ SERVER_OBJECT_NAME ] ) { + initializeTracks(); + + if ( 'userid' in window[ SERVER_OBJECT_NAME ] && 'username' in window[ SERVER_OBJECT_NAME ] ) { + const { siteId, userid, username } = window[ SERVER_OBJECT_NAME ]; + identifyUser( userid, username ); + identifySite( siteId ); + } + injectSearchApp(); } } ); diff --git a/modules/search/instant-search/lib/array-overlap.js b/modules/search/instant-search/lib/array-overlap.js new file mode 100644 index 0000000000000..ecbeb861a25d0 --- /dev/null +++ b/modules/search/instant-search/lib/array-overlap.js @@ -0,0 +1,7 @@ +export default function arrayOverlap( a1, a2 ) { + if ( ! Array.isArray( a1 ) ) { + a1 = [ a1 ]; + } + const intersection = a1.filter( value => a2.includes( value ) ); + return intersection.length !== 0; +} diff --git a/modules/search/instant-search/lib/tracks.js b/modules/search/instant-search/lib/tracks.js new file mode 100644 index 0000000000000..19fea5b9c1ba8 --- /dev/null +++ b/modules/search/instant-search/lib/tracks.js @@ -0,0 +1,34 @@ +/** + * External dependencies + */ +import { v4 as uuid } from 'uuid'; + +const globalProperties = {}; + +export function initializeTracks() { + window._tkq = window._tkq || []; +} + +export function identifyUser( userId, userName ) { + window._tkq.push( [ 'identifyUser', userId, userName ] ); +} + +export function identifySite( siteId ) { + globalProperties.blog_id = siteId; +} + +export function recordEvent( eventName, properties ) { + window._tkq.push( [ 'recordEvent', eventName, { ...globalProperties, ...properties } ] ); +} + +export function getRailcarIdPrefix() { + return `${ uuid().replace( /-/g, '' ) }`; +} + +export function recordTrainTracksRender( properties ) { + recordEvent( 'jetpack_instant_search_traintracks_render', properties ); +} + +export function recordTrainTracksInteract( properties ) { + recordEvent( 'jetpack_instant_search_traintracks_interact', properties ); +} diff --git a/package.json b/package.json index 6d34276ef3357..064e17b0ce7da 100644 --- a/package.json +++ b/package.json @@ -150,6 +150,7 @@ "swiper": "4.5.0", "uglify-save-license": "0.4.1", "unfetch": "4.1.0", + "uuid": "3.3.3", "webpack": "4.35.3", "webpack-cli": "3.3.6" }, diff --git a/yarn.lock b/yarn.lock index 634bd93776829..663871d888eeb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12526,6 +12526,11 @@ util@^0.11.0: dependencies: inherits "2.0.3" +uuid@3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866" + integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ== + uuid@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" From 69cb95ff7413346ce3c1f97b78de3320ca319d87 Mon Sep 17 00:00:00 2001 From: Jason Moon Date: Mon, 14 Oct 2019 13:18:50 -0600 Subject: [PATCH 2/8] Remove associating userIds and usernames --- modules/search/class.jetpack-search.php | 1 - modules/search/instant-search/index.jsx | 10 ++-------- modules/search/instant-search/lib/tracks.js | 4 ---- 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/modules/search/class.jetpack-search.php b/modules/search/class.jetpack-search.php index 7f204cee81ba5..2d95889c4d17f 100644 --- a/modules/search/class.jetpack-search.php +++ b/modules/search/class.jetpack-search.php @@ -239,7 +239,6 @@ public function load_assets() { 'postTypes' => $post_type_labels, 'siteId' => Jetpack::get_option( 'id' ), 'sort' => $widget_options['sort'], - 'tracksUserData' => Jetpack_Tracks_Client::get_connected_user_tracks_identity(), 'widgets' => array_values( $widgets ), ); /** diff --git a/modules/search/instant-search/index.jsx b/modules/search/instant-search/index.jsx index 085e9de4041d7..39e1ba35d2fcd 100644 --- a/modules/search/instant-search/index.jsx +++ b/modules/search/instant-search/index.jsx @@ -12,7 +12,7 @@ import SearchApp from './components/search-app'; import { getSearchQuery, getFilterQuery, determineDefaultSort } from './lib/query-string'; import { getThemeOptions } from './lib/dom'; import { SERVER_OBJECT_NAME } from './lib/constants'; -import { initializeTracks, identifyUser, identifySite } from './lib/tracks'; +import { initializeTracks, identifySite } from './lib/tracks'; const injectSearchApp = grabFocus => { render( @@ -31,13 +31,7 @@ const injectSearchApp = grabFocus => { document.addEventListener( 'DOMContentLoaded', function() { if ( !! window[ SERVER_OBJECT_NAME ] && 'siteId' in window[ SERVER_OBJECT_NAME ] ) { initializeTracks(); - - if ( 'userid' in window[ SERVER_OBJECT_NAME ] && 'username' in window[ SERVER_OBJECT_NAME ] ) { - const { siteId, userid, username } = window[ SERVER_OBJECT_NAME ]; - identifyUser( userid, username ); - identifySite( siteId ); - } - + identifySite( window[ SERVER_OBJECT_NAME ].siteId ); injectSearchApp(); } } ); diff --git a/modules/search/instant-search/lib/tracks.js b/modules/search/instant-search/lib/tracks.js index 19fea5b9c1ba8..3b0c367faf60e 100644 --- a/modules/search/instant-search/lib/tracks.js +++ b/modules/search/instant-search/lib/tracks.js @@ -9,10 +9,6 @@ export function initializeTracks() { window._tkq = window._tkq || []; } -export function identifyUser( userId, userName ) { - window._tkq.push( [ 'identifyUser', userId, userName ] ); -} - export function identifySite( siteId ) { globalProperties.blog_id = siteId; } From 122eb60c702bb1bea7d073717e14c733f01aa6f7 Mon Sep 17 00:00:00 2001 From: Jason Moon Date: Mon, 14 Oct 2019 13:20:11 -0600 Subject: [PATCH 3/8] =?UTF-8?q?Include=20=E2=80=98click=E2=80=99=20action?= =?UTF-8?q?=20property=20for=20TrainTracks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../search/instant-search/components/search-result-minimal.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/search/instant-search/components/search-result-minimal.jsx b/modules/search/instant-search/components/search-result-minimal.jsx index 83dca9f20da75..ae16c71eef413 100644 --- a/modules/search/instant-search/components/search-result-minimal.jsx +++ b/modules/search/instant-search/components/search-result-minimal.jsx @@ -54,7 +54,7 @@ class SearchResultMinimal extends Component { event.stopPropagation(); event.preventDefault(); // Send out analytics call - recordTrainTracksInteract( this.getCommonTrainTracksProps() ); + recordTrainTracksInteract( { ...this.getCommonTrainTracksProps(), action: 'click' } ); // Await next animation frame to ensure w.js processes the queue requestAnimationFrame( () => { // Re-dispatch click event From 20c8e72aa546900f2985316423577f0c76cffc32 Mon Sep 17 00:00:00 2001 From: Jason Moon Date: Mon, 14 Oct 2019 13:31:00 -0600 Subject: [PATCH 4/8] Use common slug for w.js --- modules/search/class.jetpack-search.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/search/class.jetpack-search.php b/modules/search/class.jetpack-search.php index 2d95889c4d17f..aeeb9111591be 100644 --- a/modules/search/class.jetpack-search.php +++ b/modules/search/class.jetpack-search.php @@ -268,7 +268,7 @@ public function load_assets() { public function load_and_initialize_tracks() { define( 'W_JS_VER', 60 ); // W_JS_VER - wp_enqueue_script( 'jetpack-instant-search-tracks', '//stats.wp.com/w.js', array(), W_JS_VER, true ); + wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), W_JS_VER, true ); } /** From b1e969385ea7e8172393f8ad07d7bbbf9f3fa907 Mon Sep 17 00:00:00 2001 From: Jason Moon Date: Mon, 14 Oct 2019 13:55:57 -0600 Subject: [PATCH 5/8] Fix rebase errors --- .../instant-search/components/search-result-minimal.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/search/instant-search/components/search-result-minimal.jsx b/modules/search/instant-search/components/search-result-minimal.jsx index ae16c71eef413..4a505f81d0812 100644 --- a/modules/search/instant-search/components/search-result-minimal.jsx +++ b/modules/search/instant-search/components/search-result-minimal.jsx @@ -95,9 +95,9 @@ class SearchResultMinimal extends Component { } const noTags = tags.length === 0 && cats.length === 0; - const hasVideo = this.arrayOverlap( fields.shortcode_types, ShortcodeTypes.video ); - const hasAudio = this.arrayOverlap( fields.shortcode_types, ShortcodeTypes.audio ); - const hasGallery = this.arrayOverlap( fields.shortcode_types, ShortcodeTypes.gallery ); + const hasVideo = arrayOverlap( fields.shortcode_types, ShortcodeTypes.video ); + const hasAudio = arrayOverlap( fields.shortcode_types, ShortcodeTypes.audio ); + const hasGallery = arrayOverlap( fields.shortcode_types, ShortcodeTypes.gallery ); let postTypeIcon = null; switch ( fields.post_type ) { From 1a74dbab568eee239d72165a49f90e85fe4a7b4e Mon Sep 17 00:00:00 2001 From: Jason Moon Date: Mon, 14 Oct 2019 14:00:13 -0600 Subject: [PATCH 6/8] Use railcar data from server response --- .../components/search-result-minimal.jsx | 12 ++++++------ .../instant-search/components/search-results.jsx | 8 +------- modules/search/instant-search/lib/tracks.js | 9 --------- package.json | 1 - yarn.lock | 5 ----- 5 files changed, 7 insertions(+), 28 deletions(-) diff --git a/modules/search/instant-search/components/search-result-minimal.jsx b/modules/search/instant-search/components/search-result-minimal.jsx index 4a505f81d0812..3e171846be0ec 100644 --- a/modules/search/instant-search/components/search-result-minimal.jsx +++ b/modules/search/instant-search/components/search-result-minimal.jsx @@ -37,12 +37,12 @@ class SearchResultMinimal extends Component { getCommonTrainTracksProps() { return { - fetch_algo: 'jetpack-instant-search-api/v1', - fetch_position: this.props.index, - fetch_query: this.props.query, - railcar: this.props.railcarId, - rec_blog_id: this.props.result.fields.blog_id, - rec_post_id: this.props.result.fields.post_id, + fetch_algo: this.props.result.railcar.fetch_algo, + fetch_position: this.props.result.railcar.fetch_position, + fetch_query: this.props.result.railcar.fetch_query, + railcar: this.props.result.railcar.railcar, + rec_blog_id: this.props.result.railcar.rec_blog_id, + rec_post_id: this.props.result.railcar.rec_post_id, ui_algo: 'jetpack-instant-search-ui/v1', ui_position: this.props.index, }; diff --git a/modules/search/instant-search/components/search-results.jsx b/modules/search/instant-search/components/search-results.jsx index 42274420f8f70..ae1514b0d3b82 100644 --- a/modules/search/instant-search/components/search-results.jsx +++ b/modules/search/instant-search/components/search-results.jsx @@ -12,13 +12,8 @@ import { h, Component } from 'preact'; import SearchResultMinimal from './search-result-minimal'; import { hasFilter } from '../lib/query-string'; import ScrollButton from './scroll-button'; -import { getRailcarIdPrefix } from '../lib/tracks'; class SearchResults extends Component { - constructor( props ) { - super( props ); - this.state = { railcarIdPrefix: getRailcarIdPrefix() }; - } renderResult = ( result, index ) => { switch ( this.props.resultFormat ) { case 'engagement': @@ -30,7 +25,6 @@ class SearchResults extends Component { index={ index } locale={ this.props.locale } query={ this.props.query } - railcarId={ `${ this.state.railcarIdPrefix }-${ index }` } result={ result } /> ); @@ -76,7 +70,7 @@ class SearchResults extends Component { return (

{ headerText }

diff --git a/modules/search/instant-search/lib/tracks.js b/modules/search/instant-search/lib/tracks.js index 3b0c367faf60e..c6ae157a2760f 100644 --- a/modules/search/instant-search/lib/tracks.js +++ b/modules/search/instant-search/lib/tracks.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { v4 as uuid } from 'uuid'; - const globalProperties = {}; export function initializeTracks() { @@ -17,10 +12,6 @@ export function recordEvent( eventName, properties ) { window._tkq.push( [ 'recordEvent', eventName, { ...globalProperties, ...properties } ] ); } -export function getRailcarIdPrefix() { - return `${ uuid().replace( /-/g, '' ) }`; -} - export function recordTrainTracksRender( properties ) { recordEvent( 'jetpack_instant_search_traintracks_render', properties ); } diff --git a/package.json b/package.json index 064e17b0ce7da..6d34276ef3357 100644 --- a/package.json +++ b/package.json @@ -150,7 +150,6 @@ "swiper": "4.5.0", "uglify-save-license": "0.4.1", "unfetch": "4.1.0", - "uuid": "3.3.3", "webpack": "4.35.3", "webpack-cli": "3.3.6" }, diff --git a/yarn.lock b/yarn.lock index 663871d888eeb..634bd93776829 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12526,11 +12526,6 @@ util@^0.11.0: dependencies: inherits "2.0.3" -uuid@3.3.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866" - integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ== - uuid@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" From a521b38eb96f0797ff6808216b69e5aa9af53e88 Mon Sep 17 00:00:00 2001 From: Jason Moon Date: Mon, 14 Oct 2019 14:02:00 -0600 Subject: [PATCH 7/8] Remove W_JS_VER --- modules/search/class.jetpack-search.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/search/class.jetpack-search.php b/modules/search/class.jetpack-search.php index aeeb9111591be..b4f701673f276 100644 --- a/modules/search/class.jetpack-search.php +++ b/modules/search/class.jetpack-search.php @@ -267,8 +267,7 @@ public function load_assets() { } public function load_and_initialize_tracks() { - define( 'W_JS_VER', 60 ); // W_JS_VER - wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), W_JS_VER, true ); + wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), null, true ); } /** From 4054712eea2b4eca9a37ad894d9c5f476ee266e7 Mon Sep 17 00:00:00 2001 From: Jason Moon Date: Mon, 14 Oct 2019 14:13:07 -0600 Subject: [PATCH 8/8] Simplify onClick handler --- .../components/search-result-minimal.jsx | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/modules/search/instant-search/components/search-result-minimal.jsx b/modules/search/instant-search/components/search-result-minimal.jsx index 3e171846be0ec..dc2f4007f2bf0 100644 --- a/modules/search/instant-search/components/search-result-minimal.jsx +++ b/modules/search/instant-search/components/search-result-minimal.jsx @@ -48,23 +48,9 @@ class SearchResultMinimal extends Component { }; } - onClick = event => { - // User-triggered event - if ( event.isTrusted ) { - event.stopPropagation(); - event.preventDefault(); - // Send out analytics call - recordTrainTracksInteract( { ...this.getCommonTrainTracksProps(), action: 'click' } ); - // Await next animation frame to ensure w.js processes the queue - requestAnimationFrame( () => { - // Re-dispatch click event - const clonedEvent = new event.constructor( event.type, event ); - event.target.dispatchEvent( clonedEvent ); - } ); - } else { - // Programmatically dispatched event from `dispatchEvent` - return true; - } + onClick = () => { + // Send out analytics call + recordTrainTracksInteract( { ...this.getCommonTrainTracksProps(), action: 'click' } ); }; render() {