diff --git a/examples/amp-story/analytics.html b/examples/amp-story/analytics.html index 0df23aad10cb..1e1d1396a2e0 100644 --- a/examples/amp-story/analytics.html +++ b/examples/amp-story/analytics.html @@ -43,7 +43,7 @@ "endpoint": "https://raw.githubusercontent.com/ampproject/amphtml/master/examples/img/ampicon.png", "base": "${endpoint}?${type|default:foo}&path=${canonicalPath}", "pageView": "${base}?&index=${storyPageIndex}&count=${storyPageCount}&id=${storyPageId}&muted=${storyIsMuted}&progress=${storyProgress}", - "click": "${base}?index=${storyPageIndex}&id=${storyPageId}&bookendTargetHref=${storyBookendTargetHref}&bookendCardType=${storyBookendComponentType}&bookendCardPosition=${storyBookendComponentPosition}" + "click": "${base}?&index=${storyPageIndex}&id=${storyPageId}&bookendTargetHref=${storyBookendTargetHref}&bookendCardType=${storyBookendComponentType}&bookendCardPosition=${storyBookendComponentPosition}" }, "triggers": { "trackAnchorClicks": { @@ -61,6 +61,16 @@ "eventId": "clickOnBookend" } }, + "trackFocusedState": { + "on": "story-focus", + "tagName": "a", + "request": "click" + }, + "trackClickThru": { + "on": "story-click-through", + "tagName": "a", + "request": "click" + }, "trackPageview": { "on": "story-page-visible", "request": "pageView", @@ -104,15 +114,43 @@

Page Two

Page Three

+ click me
click me
- + + +

Page Four

+ + amp.devamp.devamp.devamp.devamp.devamp.dev + + + amp.devamp.devamp.devamp.devamp.devamp.dev + + + amp.devamp.devamp.devamp.devamp.devamp.dev + + + amp.devamp.devamp.devamp.devamp.devamp.dev + + + amp.devamp.devamp.devamp.devamp.devamp.dev + + + amp.devamp.devamp.devamp.devamp.devamp.dev + + + amp.devamp.devamp.devamp.devamp.devamp.dev + +
+
+ + -

Page Four

+

Page Five

{ 'storyPageIndex': '0', 'storyPageId': 'p4', 'storyPageCount': '4', - 'pageDetails': {'repeated': false}, + 'eventDetails': {'repeated': false}, }; tracker.add( @@ -842,7 +842,7 @@ describes.realWin('Events', {amp: 1}, env => { 'storyPageIndex': '0', 'storyPageId': 'p4', 'storyPageCount': '4', - 'pageDetails': {'repeated': true}, + 'eventDetails': {'repeated': true}, }; tracker.add(target, 'story-page-visible', storyAnalyticsConfig, handler); @@ -859,7 +859,7 @@ describes.realWin('Events', {amp: 1}, env => { 'storyPageIndex': '0', 'storyPageId': 'p4', 'storyPageCount': '4', - 'pageDetails': {'repeated': true}, + 'eventDetails': {'repeated': true}, }; tracker.add(target, 'story-page-visible', storyAnalyticsConfig, handler); diff --git a/extensions/amp-story/1.0/amp-story-affiliate-link.js b/extensions/amp-story/1.0/amp-story-affiliate-link.js index e0a1155bb9fe..1da389f89dc8 100644 --- a/extensions/amp-story/1.0/amp-story-affiliate-link.js +++ b/extensions/amp-story/1.0/amp-story-affiliate-link.js @@ -20,6 +20,7 @@ import {Services} from '../../../src/services'; import {StateProperty, getStoreService} from './amp-story-store-service'; +import {StoryAnalyticsEvent, getAnalyticsService} from './story-analytics'; import {getAmpdoc} from '../../../src/service'; import {htmlFor} from '../../../src/static-template'; @@ -61,6 +62,9 @@ export class AmpStoryAffiliateLink { /** @private @const {!../../../src/service/resources-interface.ResourcesInterface} */ this.resources_ = Services.resourcesForDoc(getAmpdoc(this.win_.document)); + + /** @private @const {!./story-analytics.StoryAnalyticsService} */ + this.analyticsService_ = getAnalyticsService(this.win_, element); } /** @@ -80,15 +84,15 @@ export class AmpStoryAffiliateLink { this.addLaunchElement_(); }); - this.initializeListener_(); + this.initializeListeners_(); this.element_[AFFILIATE_LINK_BUILT] = true; } /** - * Initialize listener to toggle expanded state. + * Initializes listeners. * @private */ - initializeListener_() { + initializeListeners_() { this.storeService_.subscribe( StateProperty.AFFILIATE_LINK_STATE, elementToToggleExpand => { @@ -98,9 +102,23 @@ export class AmpStoryAffiliateLink { this.launchEl_.toggleAttribute('hidden', !expand); if (expand) { this.element_.toggleAttribute('pristine', false); + this.analyticsService_.triggerEvent( + StoryAnalyticsEvent.FOCUS, + this.element_ + ); } } ); + + this.element_.addEventListener('click', event => { + if (this.element_.hasAttribute('expanded')) { + event.stopPropagation(); + this.analyticsService_.triggerEvent( + StoryAnalyticsEvent.CLICK_THROUGH, + this.element_ + ); + } + }); } /** @@ -114,7 +132,7 @@ export class AmpStoryAffiliateLink {
-
+ `; this.element_.appendChild(iconEl); } diff --git a/extensions/amp-story/1.0/amp-story-embedded-component.js b/extensions/amp-story/1.0/amp-story-embedded-component.js index eccb62a6c8b4..b5cfb2239386 100644 --- a/extensions/amp-story/1.0/amp-story-embedded-component.js +++ b/extensions/amp-story/1.0/amp-story-embedded-component.js @@ -22,7 +22,11 @@ import { UIType, getStoreService, } from './amp-story-store-service'; -import {AdvancementMode} from './story-analytics'; +import { + AdvancementMode, + StoryAnalyticsEvent, + getAnalyticsService, +} from './story-analytics'; import {CSS} from '../../../build/amp-story-tooltip-1.0.css'; import {EventType, dispatch} from './events'; import {LocalizedStringId} from '../../../src/localized-strings'; @@ -286,6 +290,9 @@ export class AmpStoryEmbeddedComponent { /** @private @const {!../../../src/service/resources-interface.ResourcesInterface} */ this.resources_ = Services.resourcesForDoc(getAmpdoc(this.win_.document)); + /** @private @const {!./story-analytics.StoryAnalyticsService} */ + this.analyticsService_ = getAnalyticsService(this.win_, storyEl); + /** @private @const {!../../../src/service/owners-interface.OwnersInterface} */ this.owners_ = Services.ownersForDoc(getAmpdoc(this.win_.document)); @@ -384,6 +391,10 @@ export class AmpStoryEmbeddedComponent { case EmbeddedComponentState.FOCUSED: this.state_ = state; this.onFocusedStateUpdate_(component); + this.analyticsService_.triggerEvent( + StoryAnalyticsEvent.FOCUS, + this.triggeringTarget_ + ); break; case EmbeddedComponentState.HIDDEN: this.state_ = state; @@ -503,6 +514,18 @@ export class AmpStoryEmbeddedComponent { this.onOutsideTooltipClick_(event) ); + this.tooltip_.addEventListener( + 'click', + event => { + event.stopPropagation(); + this.analyticsService_.triggerEvent( + StoryAnalyticsEvent.CLICK_THROUGH, + this.triggeringTarget_ + ); + }, + true /** capture */ + ); + return this.shadowRoot_; } diff --git a/extensions/amp-story/1.0/page-advancement.js b/extensions/amp-story/1.0/page-advancement.js index f547971f7d12..2c05f0d1356d 100644 --- a/extensions/amp-story/1.0/page-advancement.js +++ b/extensions/amp-story/1.0/page-advancement.js @@ -552,6 +552,7 @@ class ManualAdvancement extends AdvancementConfig { const pageRect = this.element_.getLayoutBox(); if (this.isHandledByEmbeddedComponent_(event, pageRect)) { + event.stopPropagation(); event.preventDefault(); const embedComponent = /** @type {InteractiveComponentDef} */ (this.storeService_.get( StateProperty.INTERACTIVE_COMPONENT_STATE diff --git a/extensions/amp-story/1.0/story-analytics.js b/extensions/amp-story/1.0/story-analytics.js index 9e0486391995..59c78dfba082 100644 --- a/extensions/amp-story/1.0/story-analytics.js +++ b/extensions/amp-story/1.0/story-analytics.js @@ -25,6 +25,8 @@ export const StoryAnalyticsEvent = { BOOKEND_CLICK: 'story-bookend-click', BOOKEND_ENTER: 'story-bookend-enter', BOOKEND_EXIT: 'story-bookend-exit', + CLICK_THROUGH: 'story-click-through', + FOCUS: 'story-focus', LAST_PAGE_VISIBLE: 'story-last-page-visible', PAGE_ATTACHMENT_ENTER: 'story-page-attachment-enter', PAGE_ATTACHMENT_EXIT: 'story-page-attachment-exit', @@ -127,23 +129,25 @@ export class StoryAnalyticsService { /** * @param {!StoryAnalyticsEvent} eventType + * @param {Element=} element */ - triggerEvent(eventType) { + triggerEvent(eventType, element = null) { this.incrementPageEventCount_(eventType); triggerAnalyticsEvent( this.element_, eventType, - this.updateDetails(eventType) + this.updateDetails(eventType, element) ); } /** * Updates event details. * @param {!StoryAnalyticsEvent} eventType + * @param {Element=} element * @visibleForTesting * @return {!JsonObject}} */ - updateDetails(eventType) { + updateDetails(eventType, element = null) { const details = {}; const vars = this.variableService_.get(); const pageId = vars['storyPageId']; @@ -152,8 +156,12 @@ export class StoryAnalyticsService { details.repeated = true; } + if (element) { + details.tagName = element.tagName.toLowerCase(); + } + return /** @type {!JsonObject} */ (Object.assign( - {pageDetails: details}, + {eventDetails: details}, vars )); } diff --git a/extensions/amp-story/1.0/test/test-analytics.js b/extensions/amp-story/1.0/test/test-analytics.js index cca1a471863a..1d16861d8ad4 100644 --- a/extensions/amp-story/1.0/test/test-analytics.js +++ b/extensions/amp-story/1.0/test/test-analytics.js @@ -70,7 +70,7 @@ describes.fakeWin('amp-story analytics', {}, env => { expect(trigger).to.have.been.calledOnceWith('story-page-visible'); const details = analytics.updateDetails('story-page-visible'); - expect(details.pageDetails).to.deep.equal({}); + expect(details.eventDetails).to.deep.equal({}); }); it('should mark event as repeated when fired more than once', () => { @@ -94,7 +94,7 @@ describes.fakeWin('amp-story analytics', {}, env => { expect(trigger).to.have.been.calledWith('story-page-visible'); expect(trigger).to.have.been.calledThrice; expect( - analytics.updateDetails('story-page-visible').pageDetails + analytics.updateDetails('story-page-visible').eventDetails ).to.deep.include({ 'repeated': true, });