From b1a1e6d697ba1b9ae4a1f2463b7aadd95c8170bb Mon Sep 17 00:00:00 2001 From: kryalama <66494519+kryalama@users.noreply.github.com> Date: Tue, 12 Jan 2021 20:10:02 -0800 Subject: [PATCH 1/4] click plugin final iteration changes before public --- .../Tests/ClickEventTest.ts | 346 ++++++++++++++---- .../src/Interfaces/Datamodel.ts | 12 +- .../src/common/Utils.ts | 2 +- .../src/events/PageAction.ts | 51 ++- .../src/events/WebEvent.ts | 2 +- .../src/handlers/DomContentHandler.ts | 177 +++++++-- 6 files changed, 472 insertions(+), 118 deletions(-) diff --git a/extensions/applicationinsights-clickanalytics-js/Tests/ClickEventTest.ts b/extensions/applicationinsights-clickanalytics-js/Tests/ClickEventTest.ts index 9d6c1172a..c438617e7 100644 --- a/extensions/applicationinsights-clickanalytics-js/Tests/ClickEventTest.ts +++ b/extensions/applicationinsights-clickanalytics-js/Tests/ClickEventTest.ts @@ -39,7 +39,7 @@ export class ClickEventTest extends TestClass { pageActionPageTags: () => ({ key2: "value2" }) }, dataTags : { - useDefaultContentName : true, + useDefaultContentNameOrId : true, metaDataPrefix:'ha-', customDataPrefix: 'data-ha-', aiBlobAttributeTag: 'blob', @@ -66,12 +66,12 @@ export class ClickEventTest extends TestClass { pageAction.capturePageAction(element); Assert.equal(true, spy.called); let calledEvent: ITelemetryItem = spy.getCall(0).args[0]; - Assert.notEqual(-1, calledEvent.baseData["uri"].indexOf("Tests.html"),); - Assert.equal(undefined, calledEvent.baseData["behavior"],); - Assert.equal(undefined, calledEvent.baseData["actionType"],); - Assert.equal("[{}]", calledEvent.baseData["content"]); + Assert.notEqual(-1, calledEvent.data["uri"].indexOf("Tests.html"),); + Assert.equal(undefined, calledEvent.data["behavior"],); + Assert.equal(undefined, calledEvent.data["actionType"],); + Assert.equal("[{}]", calledEvent.data["content"]); Assert.equal(false, isNaN(calledEvent.data["timeToAction"] as number)); - Assert.equal("value2", calledEvent.baseData["properties"]["pageTags"].key2); + Assert.equal("value2", calledEvent.data["properties"]["pageTags"].key2); } }); @@ -108,9 +108,9 @@ export class ClickEventTest extends TestClass { pageAction.capturePageAction(element, overrides, { customProperty: { customNextedProperty: "test" } }); Assert.equal(true, spy.called); var calledEvent: ITelemetryItem = spy.getCall(0).args[0]; - Assert.equal("overrideActionType", calledEvent.baseData["actionType"]); - Assert.equal('[{"testTag":"testValue"}]', calledEvent.baseData["content"]); - Assert.equal(2, calledEvent.baseData["behavior"]); + Assert.equal("overrideActionType", calledEvent.data["actionType"]); + Assert.equal('[{"testTag":"testValue"}]', calledEvent.data["content"]); + Assert.equal(2, calledEvent.data["behavior"]); Assert.equal("overrideReferrerUri", calledEvent.data["refUri"]); Assert.equal("test", calledEvent.data["customProperty"]["customNextedProperty"]); } @@ -125,7 +125,7 @@ export class ClickEventTest extends TestClass { contentName: () => "testContentName" }, dataTags : { - useDefaultContentName : true, + useDefaultContentNameOrId : true, metaDataPrefix:'ha-', customDataPrefix: 'data-ha-', aiBlobAttributeTag: 'blob' @@ -156,25 +156,81 @@ export class ClickEventTest extends TestClass { ((element) as HTMLBaseElement).href = "testClickTarget"; var expectedContent = JSON.stringify([{ - id: "testId", + an: "testAn", + sn: "testsN", + cs: "testcS", + tn: "testtN", + pid: "testpid", + ct: "testcT", contentName: "testContentName", + }]); + // clickAnalyticsPlugin.capturePageAction(element); + pageAction.capturePageAction(element); + this.clock.tick(500); + Assert.equal(true, spy.called); + var calledEvent: ITelemetryItem = spy.getCall(0).args[0]; + Assert.equal("testId", calledEvent.baseData["name"]); + Assert.equal(expectedContent, calledEvent.data["content"]); + } + }); + + this.testCase({ + name: "PageAction properties are correctly assigned (Populated) with useDefaultContentNameOrId flag false", + test: () => { + const config = { + callback: { + contentName: () => "testContentName" + }, + dataTags : { + useDefaultContentNameOrId : false, + metaDataPrefix:'ha-', + customDataPrefix: 'data-ha-', + aiBlobAttributeTag: 'blob' + } + }; + const clickAnalyticsPlugin = new ClickAnalyticsPlugin(); + const core = new AppInsightsCore(); + const channel = new ChannelPlugin(); + const traceLogger = new DiagnosticLogger({ loggingLevelConsole: 1 } as any); + const contentHandler = new DomContentHandler(mergeConfig(config), traceLogger); + const pageAction = new PageAction(clickAnalyticsPlugin, mergeConfig(config), contentHandler, null, {}, traceLogger ); + core.initialize({ + instrumentationKey: 'testIkey', + extensionConfig : { + [clickAnalyticsPlugin.identifier] : config + } + } as IConfig & IConfiguration, [clickAnalyticsPlugin, channel]); + + let spy = this.sandbox.spy(clickAnalyticsPlugin.core, 'track'); + const element = document.createElement('a'); + element.setAttribute("id","testId") + element.setAttribute("data-ha-aN", "testAn"); + element.setAttribute("data-ha-sN", "testsN"); + element.setAttribute("data-ha-cS", "testcS"); + element.setAttribute("data-ha-tN", "testtN"); + element.setAttribute("data-ha-pid", "testpid"); + element.setAttribute("data-ha-cT", "testcT"); + ((element) as HTMLBaseElement).href = "testClickTarget"; + + var expectedContent = JSON.stringify([{ an: "testAn", sn: "testsN", cs: "testcS", tn: "testtN", pid: "testpid", - ct: "testcT" + ct: "testcT", }]); // clickAnalyticsPlugin.capturePageAction(element); pageAction.capturePageAction(element); this.clock.tick(500); Assert.equal(true, spy.called); var calledEvent: ITelemetryItem = spy.getCall(0).args[0]; - Assert.equal(expectedContent, calledEvent.baseData["content"]); + Assert.equal(undefined, calledEvent.baseData["name"]); + Assert.equal(expectedContent, calledEvent.data["content"]); } }); - // pageAction - clicked element has no tags, walk up the DOM to find the closest element with tags for tracking recognized as same element + // pageAction - clicked element has no tags, capture the id of the html element this.testCase({ name: "PageAction properties are correctly populated when parent element has data tags", test: () => { @@ -183,7 +239,7 @@ export class ClickEventTest extends TestClass { contentName: () => "testContentName" }, dataTags : { - useDefaultContentName : true, + useDefaultContentNameOrId : true, metaDataPrefix:'ha-', customDataPrefix: 'data-ha-', aiBlobAttributeTag: 'blob' @@ -211,20 +267,175 @@ export class ClickEventTest extends TestClass { parentElement.appendChild(element); ((element) as HTMLBaseElement).href = "testClickTarget"; var expectedContent = JSON.stringify([{ - id:"testParentId", contentName: "testContentName", - name: "testParentName", }]); // clickAnalyticsPlugin.capturePageAction(element); pageAction.capturePageAction(element); this.clock.tick(500); Assert.equal(true, spy.called); var calledEvent = spy.getCall(0).args[0]; - Assert.equal(expectedContent, calledEvent.baseData["content"]); + Assert.equal("testId", calledEvent.baseData["name"]); + Assert.equal(undefined, calledEvent.data["parentId"]); + Assert.equal(expectedContent, calledEvent.data["content"]); + } + }); + + + // pageAction - clicked element has no tags on current element, capture the id of the html element and parentId of the parent + this.testCase({ + name: "PageAction - clicked element has no tags on current element, capture the id of the html element and parentId of the parent", + test: () => { + const config = { + callback: { + contentName: () => "testContentName" + }, + dataTags : { + useDefaultContentNameOrId : true, + metaDataPrefix:'ha-', + customDataPrefix: 'data-ha-', + aiBlobAttributeTag: 'blob', + parentDataTag:'parentgroup' + } + }; + const clickAnalyticsPlugin = new ClickAnalyticsPlugin(); + const core = new AppInsightsCore(); + const channel = new ChannelPlugin(); + const traceLogger = new DiagnosticLogger({ loggingLevelConsole: 1 } as any); + const contentHandler = new DomContentHandler(mergeConfig(config), traceLogger); + const pageAction = new PageAction(clickAnalyticsPlugin, mergeConfig(config), contentHandler, null, {}, traceLogger ); + core.initialize({ + instrumentationKey: 'testIkey', + extensionConfig : { + [clickAnalyticsPlugin.identifier] : config + } + } as IConfig & IConfiguration, [clickAnalyticsPlugin, channel]); + + let spy = this.sandbox.spy(clickAnalyticsPlugin.core, 'track'); + const element = document.createElement('a'); + element.setAttribute("id", "testId") + const parentElement = document.createElement('div'); + parentElement.setAttribute("data-ha-id", "testParentId"); + parentElement.setAttribute("data-ha-name", "testParentName"); + parentElement.setAttribute("data-ha-parentgroup", "testParentGroup"); + parentElement.appendChild(element); + ((element) as HTMLBaseElement).href = "testClickTarget"; + var expectedContent = JSON.stringify([{ + contentName: "testContentName", + name: "testParentName" + }]); + // clickAnalyticsPlugin.capturePageAction(element); + pageAction.capturePageAction(element); + this.clock.tick(500); + Assert.equal(true, spy.called); + var calledEvent = spy.getCall(0).args[0]; + Assert.equal("testId", calledEvent.baseData["name"]); + Assert.equal("testParentId", calledEvent.data["parentId"]); + Assert.equal(expectedContent, calledEvent.data["content"]); + } + }); + + // pageAction - clicked element has no tags on current element, capture the id of the html element and html id of the parent + this.testCase({ + name: "PageAction - clicked element has no tags on current element, capture the id of the html element and html id of the parent", + test: () => { + const config = { + callback: { + contentName: () => "testContentName" + }, + dataTags : { + useDefaultContentNameOrId : true, + metaDataPrefix:'ha-', + customDataPrefix: 'data-ha-', + aiBlobAttributeTag: 'blob', + parentDataTag:'parentgroup' + } + }; + const clickAnalyticsPlugin = new ClickAnalyticsPlugin(); + const core = new AppInsightsCore(); + const channel = new ChannelPlugin(); + const traceLogger = new DiagnosticLogger({ loggingLevelConsole: 1 } as any); + const contentHandler = new DomContentHandler(mergeConfig(config), traceLogger); + const pageAction = new PageAction(clickAnalyticsPlugin, mergeConfig(config), contentHandler, null, {}, traceLogger ); + core.initialize({ + instrumentationKey: 'testIkey', + extensionConfig : { + [clickAnalyticsPlugin.identifier] : config + } + } as IConfig & IConfiguration, [clickAnalyticsPlugin, channel]); + + let spy = this.sandbox.spy(clickAnalyticsPlugin.core, 'track'); + const element = document.createElement('a'); + element.setAttribute("id", "testId") + const parentElement = document.createElement('div'); + parentElement.setAttribute("id","testParentId") + parentElement.setAttribute("data-ha-name", "testParentName"); + parentElement.setAttribute("data-ha-parentgroup", "testParentGroup"); + parentElement.appendChild(element); + ((element) as HTMLBaseElement).href = "testClickTarget"; + var expectedContent = JSON.stringify([{ + contentName: "testContentName", + name: "testParentName" + }]); + // clickAnalyticsPlugin.capturePageAction(element); + pageAction.capturePageAction(element); + this.clock.tick(500); + Assert.equal(true, spy.called); + var calledEvent = spy.getCall(0).args[0]; + Assert.equal("testId", calledEvent.baseData["name"]); + Assert.equal("testParentId", calledEvent.data["parentId"]); + Assert.equal(expectedContent, calledEvent.data["content"]); + } + }); + + // pageAction - clicked element has no tags, no html id capture the content name of html element should be event name + this.testCase({ + name: "PageAction name populated from contentname", + test: () => { + const config = { + callback: { + contentName: () => "testContentName" + }, + dataTags : { + useDefaultContentNameOrId : true, + metaDataPrefix:'ha-', + customDataPrefix: 'data-ha-', + aiBlobAttributeTag: 'blob' + } + }; + const clickAnalyticsPlugin = new ClickAnalyticsPlugin(); + const core = new AppInsightsCore(); + const channel = new ChannelPlugin(); + const traceLogger = new DiagnosticLogger({ loggingLevelConsole: 1 } as any); + const contentHandler = new DomContentHandler(mergeConfig(config), traceLogger); + const pageAction = new PageAction(clickAnalyticsPlugin, mergeConfig(config), contentHandler, null, {}, traceLogger ); + core.initialize({ + instrumentationKey: 'testIkey', + extensionConfig : { + [clickAnalyticsPlugin.identifier] : config + } + } as IConfig & IConfiguration, [clickAnalyticsPlugin, channel]); + + let spy = this.sandbox.spy(clickAnalyticsPlugin.core, 'track'); + const element = document.createElement('a'); + const parentElement = document.createElement('div'); + parentElement.setAttribute("data-ha-id", "testParentId"); + parentElement.setAttribute("data-ha-name", "testParentName"); + parentElement.appendChild(element); + ((element) as HTMLBaseElement).href = "testClickTarget"; + var expectedContent = JSON.stringify([{ + contentName: "testContentName", + }]); + // clickAnalyticsPlugin.capturePageAction(element); + pageAction.capturePageAction(element); + this.clock.tick(500); + Assert.equal(true, spy.called); + var calledEvent = spy.getCall(0).args[0]; + Assert.equal("testContentName", calledEvent.baseData["name"]); + Assert.equal(expectedContent, calledEvent.data["content"]); } }); - // upper element has no tags, continue walk up the DOM to find grand parent element's info + // Parent element has no tags, continue walk up the DOM to find grand parent element's info this.testCase({ name: "PageAction: element and its upper element does not have any tags, so grand parent element is recognize as under one area, no parent info is populated.", test: () => { @@ -233,8 +444,9 @@ export class ClickEventTest extends TestClass { contentName: () => "testContentName" }, dataTags : { - useDefaultContentName : true, + useDefaultContentNameOrId : true, customDataPrefix: 'data-ha-', + parentDataTag:'parentgroup' } }; const clickAnalyticsPlugin = new ClickAnalyticsPlugin(); @@ -257,13 +469,13 @@ export class ClickEventTest extends TestClass { parentElement.setAttribute("id", "testParentId"); parentElement.appendChild(element); const grandParentElement = document.createElement('div'); + grandParentElement.setAttribute("data-ha-parentgroup", "group1"); grandParentElement.setAttribute("data-ha-id", "testGrandParentId"); grandParentElement.setAttribute("data-ha-name", "testGrandParentName"); grandParentElement.appendChild(parentElement); ((element) as HTMLBaseElement).href = "testClickTarget"; var expectedContent = JSON.stringify([{ - id:"testGrandParentId", contentName: "testContentName", name: "testGrandParentName", }]); @@ -272,7 +484,9 @@ export class ClickEventTest extends TestClass { this.clock.tick(500); Assert.equal(true, spy.called); var calledEvent = spy.getCall(0).args[0]; - Assert.equal(expectedContent, calledEvent.baseData["content"]); + Assert.equal("testId", calledEvent.baseData["name"]); + Assert.equal("testGrandParentId", calledEvent.data["parentId"]); + Assert.equal(expectedContent, calledEvent.data["content"]); } }); @@ -282,7 +496,7 @@ export class ClickEventTest extends TestClass { test: () => { const config = { dataTags : { - useDefaultContentName : true, + useDefaultContentNameOrId : true, customDataPrefix: 'data-ha-', } }; @@ -312,14 +526,15 @@ export class ClickEventTest extends TestClass { ((element) as HTMLBaseElement).href = "testClickTarget"; var expectedContent = JSON.stringify([{ - id: "testId" }]); // clickAnalyticsPlugin.capturePageAction(element); pageAction.capturePageAction(element); this.clock.tick(500); Assert.equal(true, spy.called); var calledEvent = spy.getCall(0).args[0]; - Assert.equal(expectedContent, calledEvent.baseData["content"]); + Assert.equal("testId", calledEvent.baseData["name"]); + Assert.equal(undefined, calledEvent.data["parentId"]); + Assert.equal(expectedContent, calledEvent.data["content"]); } }); @@ -329,7 +544,7 @@ export class ClickEventTest extends TestClass { test: () => { const config = { dataTags : { - useDefaultContentName : true, + useDefaultContentNameOrId : true, metaDataPrefix:'ha-', customDataPrefix: 'data-ha-', aiBlobAttributeTag: 'blob' @@ -354,7 +569,6 @@ export class ClickEventTest extends TestClass { element.setAttribute("data-ha-blob", '{"id":"parentTestId","aN":"areaTestName","sN":"slotTestName","cN":"contentTestName", "pI":"parentIdSelfDefined", "pN":"parentNameSelfDefined"}'); ((element) as HTMLBaseElement).href = "testClickTarget"; var expectedContent = JSON.stringify([{ - id: "parentTestId", aN: "areaTestName", sN: "slotTestName", cN: "contentTestName", @@ -366,7 +580,8 @@ export class ClickEventTest extends TestClass { this.clock.tick(500); Assert.equal(true, spy.called); var calledEvent = spy.getCall(0).args[0]; - Assert.equal(expectedContent, calledEvent.baseData["content"]); + Assert.equal("parentTestId", calledEvent.baseData["name"]); + Assert.equal(expectedContent, calledEvent.data["content"]); } }); @@ -376,10 +591,11 @@ export class ClickEventTest extends TestClass { test: () => { const config = { dataTags : { - useDefaultContentName : true, + useDefaultContentNameOrId : true, metaDataPrefix:'ha-', customDataPrefix: 'data-ha-', - aiBlobAttributeTag: 'blob' + aiBlobAttributeTag: 'blob', + parentDataTag:'parentgroup' } }; const clickAnalyticsPlugin = new ClickAnalyticsPlugin(); @@ -399,23 +615,20 @@ export class ClickEventTest extends TestClass { const element = document.createElement('a'); const parentElement = document.createElement('div'); parentElement.setAttribute("id", "testParentId"); + parentElement.setAttribute("data-ha-parentgroup", "group1"); parentElement.setAttribute("data-ha-blob", '{"id":"parentTestId","aN":"areaTestName","sN":"slotTestName","cN":"contentTestName", "pI":"parentIdSelfDefined", "pN":"parentNameSelfDefined"}'); parentElement.appendChild(element); ((element) as HTMLBaseElement).href = "testClickTarget"; var expectedContent = JSON.stringify([{ - id: "parentTestId", - aN: "areaTestName", - sN: "slotTestName", - cN: "contentTestName", - pI: "parentIdSelfDefined", - pN: "parentNameSelfDefined" }]); // clickAnalyticsPlugin.capturePageAction(element); pageAction.capturePageAction(element); this.clock.tick(500); Assert.equal(true, spy.called); var calledEvent = spy.getCall(0).args[0]; - Assert.equal(expectedContent, calledEvent.baseData["content"]); + Assert.equal(undefined, calledEvent.baseData["name"]); + Assert.equal("parentTestId", calledEvent.data["parentId"]); + Assert.equal(expectedContent, calledEvent.data["content"]); } }); @@ -425,7 +638,7 @@ export class ClickEventTest extends TestClass { test: () => { const config = { dataTags : { - useDefaultContentName : true, + useDefaultContentNameOrId : true, customDataPrefix: 'data-ha-', parentDataTag: 'parent' } @@ -457,8 +670,6 @@ export class ClickEventTest extends TestClass { ((element) as HTMLBaseElement).href = "testClickTarget"; var expectedContent = JSON.stringify([{ - id:"testId", - parentid: "testParentId", name: "testParentName", }]); // clickAnalyticsPlugin.capturePageAction(element); @@ -466,7 +677,9 @@ export class ClickEventTest extends TestClass { this.clock.tick(500); Assert.equal(true, spy.called); var calledEvent = spy.getCall(0).args[0]; - Assert.equal(expectedContent, calledEvent.baseData["content"]); + Assert.equal("testId", calledEvent.baseData["name"]); + Assert.equal("testParentId", calledEvent.data["parentId"]); + Assert.equal(expectedContent, calledEvent.data["content"]); } }); @@ -478,7 +691,7 @@ export class ClickEventTest extends TestClass { contentName: () => "testContentName" }, dataTags : { - useDefaultContentName : true, + useDefaultContentNameOrId : true, metaDataPrefix:'ha-', customDataPrefix: 'data-ha-', aiBlobAttributeTag: 'blob' @@ -505,17 +718,16 @@ export class ClickEventTest extends TestClass { ((element) as HTMLBaseElement).href = "testClickTarget"; var expectedContent = JSON.stringify([{ - id: "testId", - contentName: "testContentName", - an: "testAn" + an: "testAn", + contentName: "testContentName" }]); // clickAnalyticsPlugin.capturePageAction(element); pageAction.capturePageAction(element); this.clock.tick(500); Assert.equal(true, spy.called); var calledEvent: ITelemetryItem = spy.getCall(0).args[0]; - Assert.equal(expectedContent, calledEvent.baseData["content"]); - Assert.equal("testBehavior",calledEvent.baseData["behavior"]); + Assert.equal(expectedContent, calledEvent.data["content"]); + Assert.equal("testBehavior",calledEvent.data["behavior"]); } }); @@ -527,7 +739,7 @@ export class ClickEventTest extends TestClass { contentName: () => "testContentName" }, dataTags : { - useDefaultContentName : true, + useDefaultContentNameOrId : true, metaDataPrefix:'ha-', customDataPrefix: 'data-ha-', aiBlobAttributeTag: 'blob' @@ -553,17 +765,16 @@ export class ClickEventTest extends TestClass { ((element) as HTMLBaseElement).href = "testClickTarget"; var expectedContent = JSON.stringify([{ - id: "testId", - contentName: "testContentName", - an: "testAn" + an: "testAn", + contentName: "testContentName" }]); // clickAnalyticsPlugin.capturePageAction(element); pageAction.capturePageAction(element); this.clock.tick(500); Assert.equal(true, spy.called); var calledEvent: ITelemetryItem = spy.getCall(0).args[0]; - Assert.equal(expectedContent, calledEvent.baseData["content"]); - Assert.equal(undefined, calledEvent.baseData["behavior"]); + Assert.equal(expectedContent, calledEvent.data["content"]); + Assert.equal(undefined, calledEvent.data["behavior"]); } }); @@ -579,7 +790,7 @@ export class ClickEventTest extends TestClass { contentName: () => "testContentName" }, dataTags : { - useDefaultContentName : true, + useDefaultContentNameOrId : true, metaDataPrefix:'ha-', customDataPrefix: 'data-ha-', aiBlobAttributeTag: 'blob' @@ -608,17 +819,16 @@ export class ClickEventTest extends TestClass { ((element) as HTMLBaseElement).href = "testClickTarget"; var expectedContent = JSON.stringify([{ - id: "testId", + an: "testAn", contentName: "testContentName", - an: "testAn" }]); // clickAnalyticsPlugin.capturePageAction(element); pageAction.capturePageAction(element); this.clock.tick(500); Assert.equal(true, spy.called); var calledEvent: ITelemetryItem = spy.getCall(0).args[0]; - Assert.equal(expectedContent, calledEvent.baseData["content"]); - Assert.equal("behavior1_Value",calledEvent.baseData["behavior"]); + Assert.equal(expectedContent, calledEvent.data["content"]); + Assert.equal("behavior1_Value",calledEvent.data["behavior"]); } }); @@ -634,7 +844,7 @@ export class ClickEventTest extends TestClass { contentName: () => "testContentName" }, dataTags : { - useDefaultContentName : true, + useDefaultContentNameOrId : true, metaDataPrefix:'ha-', customDataPrefix: 'data-ha-', aiBlobAttributeTag: 'blob' @@ -663,17 +873,16 @@ export class ClickEventTest extends TestClass { ((element) as HTMLBaseElement).href = "testClickTarget"; var expectedContent = JSON.stringify([{ - id: "testId", + an: "testAn", contentName: "testContentName", - an: "testAn" }]); // clickAnalyticsPlugin.capturePageAction(element); pageAction.capturePageAction(element); this.clock.tick(500); Assert.equal(true, spy.called); var calledEvent: ITelemetryItem = spy.getCall(0).args[0]; - Assert.equal(expectedContent, calledEvent.baseData["content"]); - Assert.equal("BEHAVIOR1",calledEvent.baseData["behavior"]); + Assert.equal(expectedContent, calledEvent.data["content"]); + Assert.equal("BEHAVIOR1",calledEvent.data["behavior"]); } }); @@ -690,7 +899,7 @@ export class ClickEventTest extends TestClass { contentName: () => "testContentName" }, dataTags : { - useDefaultContentName : true, + useDefaultContentNameOrId : true, metaDataPrefix:'ha-', customDataPrefix: 'data-ha-', aiBlobAttributeTag: 'blob' @@ -719,17 +928,16 @@ export class ClickEventTest extends TestClass { ((element) as HTMLBaseElement).href = "testClickTarget"; var expectedContent = JSON.stringify([{ - id: "testId", - contentName: "testContentName", - an: "testAn" + an: "testAn", + contentName: "testContentName" }]); // clickAnalyticsPlugin.capturePageAction(element); pageAction.capturePageAction(element); this.clock.tick(500); Assert.equal(true, spy.called); var calledEvent: ITelemetryItem = spy.getCall(0).args[0]; - Assert.equal(expectedContent, calledEvent.baseData["content"]); - Assert.equal(2,calledEvent.baseData["behavior"]); + Assert.equal(expectedContent, calledEvent.data["content"]); + Assert.equal(2,calledEvent.data["behavior"]); } }); } diff --git a/extensions/applicationinsights-clickanalytics-js/src/Interfaces/Datamodel.ts b/extensions/applicationinsights-clickanalytics-js/src/Interfaces/Datamodel.ts index aff9c76da..91689a251 100644 --- a/extensions/applicationinsights-clickanalytics-js/src/Interfaces/Datamodel.ts +++ b/extensions/applicationinsights-clickanalytics-js/src/Interfaces/Datamodel.ts @@ -45,9 +45,9 @@ export interface IClickAnalyticsConfiguration { */ export interface ICustomDataTags { /** - * When a particular element is not tagged with content name prefix or content name prefix is not provided by user, this flag is used to collect standard HTML attribute for contentName. + * When a particular element is not tagged with content name prefix or content name prefix is not provided by user, this flag is used to collect standard HTML attribute for contentName and id. */ - useDefaultContentName?: boolean; + useDefaultContentNameOrId?: boolean; /** * Automatic capture content name and value of elements which are tagged with provided prefix */ @@ -277,4 +277,12 @@ export interface IPageActionTelemetry extends IEventTelemetry { * Page type */ pageType?: string; + /** + * Title of the page + */ + pageName?: string; + /** + * Content Id (Parent Id) of the parent in which the content was located; + */ + parentId?: string; } \ No newline at end of file diff --git a/extensions/applicationinsights-clickanalytics-js/src/common/Utils.ts b/extensions/applicationinsights-clickanalytics-js/src/common/Utils.ts index cca0165ec..072a58d5a 100644 --- a/extensions/applicationinsights-clickanalytics-js/src/common/Utils.ts +++ b/extensions/applicationinsights-clickanalytics-js/src/common/Utils.ts @@ -310,7 +310,7 @@ export function mergeConfig(overrideConfig: IClickAnalyticsConfiguration): IClic pageType: '' }, dataTags: { - useDefaultContentName: false, + useDefaultContentNameOrId: false, aiBlobAttributeTag: DEFAULT_AI_BLOB_ATTRIBUTE_TAG, customDataPrefix: DEFAULT_DATA_PREFIX, captureAllMetaDataContent: false, diff --git a/extensions/applicationinsights-clickanalytics-js/src/events/PageAction.ts b/extensions/applicationinsights-clickanalytics-js/src/events/PageAction.ts index 2adcfecaf..43e735092 100644 --- a/extensions/applicationinsights-clickanalytics-js/src/events/PageAction.ts +++ b/extensions/applicationinsights-clickanalytics-js/src/events/PageAction.ts @@ -4,9 +4,9 @@ import { WebEvent } from './WebEvent'; import * as DataCollector from '../DataCollector'; -import { ITelemetryItem, getPerformance, ICustomProperties } from "@microsoft/applicationinsights-core-js" +import { ITelemetryItem, getPerformance, ICustomProperties, LoggingSeverity } from "@microsoft/applicationinsights-core-js" import { IPageActionOverrideValues, IPageActionTelemetry } from '../Interfaces/Datamodel'; -import { extractFieldFromObject, bracketIt, isValueAssigned, extend } from '../common/Utils'; +import { extractFieldFromObject, bracketIt, isValueAssigned, extend, _ExtendedInternalMessageId } from '../common/Utils'; export class PageAction extends WebEvent { @@ -21,23 +21,26 @@ export class PageAction extends WebEvent { ext['web'] = {}; let event: ITelemetryItem = { name: "Microsoft.ApplicationInsights.{0}.Event", - baseType: 'ClickEvent', + baseType: 'EventData', ext, data: {}, baseData: {} }; this._populateEventDataIfPresent(event.baseData, 'name', pageActionEvent.name); - this._populateEventDataIfPresent(event.baseData, 'uri', pageActionEvent.uri); - this._populateEventDataIfPresent(event.baseData, 'pageType', pageActionEvent.pageType); - this._populateEventDataIfPresent(event.baseData, 'properties', pageActionEvent.properties); - this._populateEventDataIfPresent(event.baseData, 'actionType', pageActionEvent.actionType); - this._populateEventDataIfPresent(event.baseData, 'behavior', pageActionEvent.behavior); - this._populateEventDataIfPresent(event.baseData, 'clickCoordinates', pageActionEvent.clickCoordinates); - this._populateEventDataIfPresent(event.baseData, 'content', pageActionEvent.content); - this._populateEventDataIfPresent(event.baseData, 'targetUri', pageActionEvent.targetUri); + this._populateEventDataIfPresent(event.data, 'baseTypeSource', 'ClickEvent'); + this._populateEventDataIfPresent(event.data, 'uri', pageActionEvent.uri); + this._populateEventDataIfPresent(event.data, 'pageType', pageActionEvent.pageType); + this._populateEventDataIfPresent(event.data, 'properties', pageActionEvent.properties); + this._populateEventDataIfPresent(event.data, 'actionType', pageActionEvent.actionType); + this._populateEventDataIfPresent(event.data, 'behavior', pageActionEvent.behavior); + this._populateEventDataIfPresent(event.data, 'clickCoordinates', pageActionEvent.clickCoordinates); + this._populateEventDataIfPresent(event.data, 'content', pageActionEvent.content); + this._populateEventDataIfPresent(event.data, 'targetUri', pageActionEvent.targetUri); this._populateEventDataIfPresent(event.data, 'timeToAction', pageActionEvent.timeToAction); this._populateEventDataIfPresent(event.data, 'refUri', pageActionEvent.refUri); + this._populateEventDataIfPresent(event.data, 'pageName', pageActionEvent.pageName); + this._populateEventDataIfPresent(event.data, 'parentId', pageActionEvent.parentId); for (let property in properties) { if (properties.hasOwnProperty(property)) { if (!event.data[property]) { @@ -80,13 +83,28 @@ export class PageAction extends WebEvent { let currentBehavior: string = extractFieldFromObject(elementContent, 'bhvr'); pageActionEvent.behavior = this._getValidBehavior(currentBehavior); } + + // Validate to ensure the minimum required field 'contentName' or 'id' is present. However, + // requiring these fields would result in majority of adopter's content from being collected. + // Just throw a warning and continue collection. + if (!isValueAssigned(elementContent.id) && !isValueAssigned(elementContent.contentName)) { + this._traceLogger.throwInternal( + LoggingSeverity.WARNING, + _ExtendedInternalMessageId.InvalidContentBlob, `Missing attributes id or contentName in click event. Click event information will still be collected!` + ) + } } + pageActionEvent.name = elementContent.id || elementContent.contentName || ''; + pageActionEvent.parentId = elementContent.parentid || elementContent.parentName || ''; + if (isValueAssigned(overrideValues.actionType)) { pageActionEvent.actionType = overrideValues.actionType; } if (isValueAssigned(overrideValues.clickCoordinateX) && isValueAssigned(overrideValues.clickCoordinateY)) { pageActionEvent.clickCoordinates = overrideValues.clickCoordinateX + 'X' + overrideValues.clickCoordinateY; } + + this._sanitizePageActionEventContent(elementContent); pageActionEvent.content = bracketIt(JSON.stringify(extend( elementContent, overrideValues && overrideValues.contentTags ? overrideValues.contentTags : {}))); @@ -115,4 +133,15 @@ export class PageAction extends WebEvent { } } + private _sanitizePageActionEventContent(pageActionContent: any) { + if(pageActionContent) { + delete pageActionContent.id; + delete pageActionContent.parentid; + delete pageActionContent.parentname; + if(isValueAssigned(this._config.dataTags.parentDataTag)) { + delete pageActionContent[this._config.dataTags.parentDataTag]; + } + } + } + } diff --git a/extensions/applicationinsights-clickanalytics-js/src/events/WebEvent.ts b/extensions/applicationinsights-clickanalytics-js/src/events/WebEvent.ts index 92d30e10b..72302105a 100644 --- a/extensions/applicationinsights-clickanalytics-js/src/events/WebEvent.ts +++ b/extensions/applicationinsights-clickanalytics-js/src/events/WebEvent.ts @@ -36,7 +36,7 @@ export class WebEvent { // Fill common PartB fields public setBasicProperties(event: IPageActionTelemetry, overrideValues: IOverrideValues) { if (!isValueAssigned(event.name)) { - event.name = DataCollector.getPageName(this._config, overrideValues); + event.pageName = DataCollector.getPageName(this._config, overrideValues); } if (!isValueAssigned(event.uri) && hasWindow) { event.uri = DataCollector.getUri(this._config, getLocation()); diff --git a/extensions/applicationinsights-clickanalytics-js/src/handlers/DomContentHandler.ts b/extensions/applicationinsights-clickanalytics-js/src/handlers/DomContentHandler.ts index e843c4579..cd76f8b09 100644 --- a/extensions/applicationinsights-clickanalytics-js/src/handlers/DomContentHandler.ts +++ b/extensions/applicationinsights-clickanalytics-js/src/handlers/DomContentHandler.ts @@ -50,7 +50,6 @@ export class DomContentHandler implements IContentHandler { let elementContent: any = {}; let biBlobElement; let biBlobValue; - let contentElement; let parentDataTagPrefix; const dataTagPrefix:string = this._config.dataTags.customDataPrefix; const aiBlobAttributeTag:string = dataTagPrefix + this._config.dataTags.aiBlobAttributeTag; @@ -60,10 +59,7 @@ export class DomContentHandler implements IContentHandler { if (!this._isTracked(element, dataTagPrefix, aiBlobAttributeTag)) { // capture blob from element or hierarchy - biBlobElement = findClosestByAttribute(element, aiBlobAttributeTag); - if (biBlobElement) { - biBlobValue = biBlobElement.getAttribute(aiBlobAttributeTag); - } + biBlobValue = element.getAttribute(aiBlobAttributeTag); if (biBlobValue) { try { elementContent = JSON.parse(biBlobValue); @@ -75,21 +71,24 @@ export class DomContentHandler implements IContentHandler { } } else { // traverse up the DOM to find the closest parent with data-* tag defined - contentElement = walkUpDomChainWithElementValidation(element, this._isTracked, dataTagPrefix); - elementContent = extend(elementContent, this._populateElementContentwithDataTag( contentElement, element, dataTagPrefix, parentDataTagPrefix)); + //contentElement = walkUpDomChainWithElementValidation(element, this._isTracked, dataTagPrefix); + elementContent = extend(elementContent, this._populateElementContent(element, dataTagPrefix, parentDataTagPrefix, aiBlobAttributeTag)); } } else { - contentElement = element; - elementContent = extend(elementContent, this._populateElementContentwithDataTag(contentElement, element, dataTagPrefix, parentDataTagPrefix)); + elementContent = extend(elementContent, this._populateElementContentwithDataTag(element, dataTagPrefix, parentDataTagPrefix, aiBlobAttributeTag)); } removeInvalidElements(elementContent); + if (parentDataTagPrefix) { + elementContent = extend(elementContent, this._getParentDetails(element, elementContent, dataTagPrefix, aiBlobAttributeTag )); + } + return elementContent; } /** * Capture current level Element content */ - private _captureElementContent(contentElement: Element, elementContent: any, dataTagPrefix: string) { + private _captureElementContentWithDataTag(contentElement: Element, elementContent: any, dataTagPrefix: string) { for (var i = 0, attrib; i < contentElement.attributes.length; i++) { attrib = contentElement.attributes[i]; @@ -106,9 +105,10 @@ export class DomContentHandler implements IContentHandler { /** * Walk Up the DOM to capture Element content */ - private _walkUpDomChainCaptureData(el: Element, elementContent: any, dataTagPrefix: string, parentDataTagPrefix: string ): void { + private _walkUpDomChainCaptureData(el: Element, elementContent: any, dataTagPrefix: string, parentDataTagPrefix: string, aiBlobAttributeTag: string ): void { let element = el; let parentDataTagFound: boolean = false; + let elementLevelFlag: boolean = false; // Use this flag to capture 'id' only at the incoming html element level. while(!CoreUtils.isNullOrUndefined(element) && !CoreUtils.isNullOrUndefined(element.attributes)) { let attributes=element.attributes; for (let i = 0; i < attributes.length; i++) { @@ -121,8 +121,13 @@ export class DomContentHandler implements IContentHandler { if( attrib.name.indexOf(parentDataTagPrefix) === 0) { parentDataTagFound = true; } - + + // Todo handle blob data + if( attrib.name.indexOf(aiBlobAttributeTag) === 0) { + continue; + } const attribName = attrib.name.replace(dataTagPrefix, ''); + if(elementLevelFlag && attribName ==='id') continue; // skip capturing id if not at the first level. if(!isValueAssigned(elementContent[attribName])) { elementContent[attribName] = attrib.value; } @@ -132,49 +137,80 @@ export class DomContentHandler implements IContentHandler { if(parentDataTagFound) { break; } - + elementLevelFlag = true; // after the initial level set this flag to true. element = (element.parentNode as Element); } } - /** + /** * Capture Element content along with Data Tag attributes and values */ - private _populateElementContentwithDataTag(contentElement: Element, element: Element, dataTagPrefix: string, parentDataTagPrefix: string) { + private _populateElementContent(element: Element, dataTagPrefix: string, parentDataTagPrefix: string, aiBlobAttributeTag: string) { let elementContent: any = {}; - if (!contentElement) { - // None of the element and its parent has any tags, collect standard HTML attribute for contentName when useDefaultContentName flag is true - if (this._config.dataTags.useDefaultContentName) { - contentElement = element; - } else { - return elementContent - } - } - - var customizedContentName = this._config.callback.contentName ? this._config.callback.contentName(contentElement, this._config.dataTags.useDefaultContentName) : ""; - var defaultContentName = this._getDefaultContentName(contentElement, this._config.dataTags.useDefaultContentName); + if(!element) return elementContent; + let htmlContent = this._getHtmlIdAndContentName(element); elementContent = { - id: contentElement.id || '', - contentName: customizedContentName || defaultContentName || contentElement.getAttribute('alt') || '', + id: htmlContent.id || '', + contentName: htmlContent.contentName || '' }; + + if(isValueAssigned(parentDataTagPrefix)) { + this._walkUpDomChainCaptureData(element, elementContent, dataTagPrefix, parentDataTagPrefix, aiBlobAttributeTag); + } - // Validate to ensure the minimum required field 'contentName' is present. + // Validate to ensure the minimum required field 'id' or 'contentName' is present. // The content schema defines id, aN and sN as required fields. However, // requiring these fields would result in majority of adopter's content from being collected. // Just throw a warning and continue collection. - if (!elementContent.id || !elementContent.contentName) { + if (!elementContent.id && !elementContent.contentName) { this._traceLogger.throwInternal( LoggingSeverity.WARNING, _ExtendedInternalMessageId.InvalidContentBlob, 'Invalid content blob. Missing required attributes (id, contentName. ' + ' Content information will still be collected!' ) } + + return elementContent; + } + + /** + * Capture Element content along with Data Tag attributes and values + */ + private _populateElementContentwithDataTag(element: Element, dataTagPrefix: string, parentDataTagPrefix: string, aiBlobAttributeTag: string) { + + let elementContent: any = {}; + if(!element) return elementContent; + + + let htmlContent = this._getHtmlIdAndContentName(element); - isValueAssigned(parentDataTagPrefix) ? - this._walkUpDomChainCaptureData(contentElement, elementContent, dataTagPrefix, parentDataTagPrefix) : - this._captureElementContent(contentElement, elementContent, dataTagPrefix); + if(isValueAssigned(parentDataTagPrefix)) { + this._walkUpDomChainCaptureData(element, elementContent, dataTagPrefix, parentDataTagPrefix, aiBlobAttributeTag); + } else { + this._captureElementContentWithDataTag(element, elementContent, dataTagPrefix); + } + + + if (this._config.dataTags.useDefaultContentNameOrId) { + if(!isValueAssigned(elementContent.id)) { + elementContent.id = htmlContent.id || ''; + } + elementContent.contentName = htmlContent.contentName || ''; + } + + // Validate to ensure the minimum required field 'id' or 'contentName' is present. + // The content schema defines id, aN and sN as required fields. However, + // requiring these fields would result in majority of adopter's content from being collected. + // Just throw a warning and continue collection. + if (!elementContent.id && !elementContent.contentName) { + this._traceLogger.throwInternal( + LoggingSeverity.WARNING, + _ExtendedInternalMessageId.InvalidContentBlob, 'Invalid content blob. Missing required attributes (id, contentName. ' + + ' Content information will still be collected!' + ) + } return elementContent; } @@ -209,7 +245,7 @@ export class DomContentHandler implements IContentHandler { /** * Gets the default content name. * @param element - An html element - * @param useDefaultContentName -Flag indicating if an element is market PII. + * @param useDefaultContentNameOrId -Flag indicating if an element is market PII. * @returns Content name */ private _getDefaultContentName(element: any, useDefaultContentName: boolean) { @@ -253,4 +289,77 @@ export class DomContentHandler implements IContentHandler { } return dataTagFound; } + + private _getHtmlIdAndContentName(element:Element) { + let htmlContent: any = {}; + if(!element) return htmlContent; + + if (this._config.dataTags.useDefaultContentNameOrId) { + const customizedContentName = this._config.callback.contentName ? this._config.callback.contentName(element, this._config.dataTags.useDefaultContentNameOrId) : ""; + const defaultContentName = this._getDefaultContentName(element, this._config.dataTags.useDefaultContentNameOrId); + + htmlContent = { + id: element.id, + contentName: customizedContentName || defaultContentName || element.getAttribute('alt'), + }; + } + + return htmlContent; + } + + /** + * Computes the parentId of a given element. + * @param element - An html element + * @returns An object containing the closest parentId , can be empty if nothing was found + */ + private _getParentDetails(element: Element, elementContent: any, dataTagPrefix: string, aiBlobAttributeTag: string): IContent { + const parentId = elementContent['parentid']; + const parentName = elementContent['parentname']; + let parentInfo = {}; + + if (parentId || parentName || !element) { + return parentInfo; + } + + return this._populateParentInfo(element, dataTagPrefix, aiBlobAttributeTag); + } + /** + * Check if parent info already set up, if so take and put into content, if not walk up the DOM to find correct info + * @param element - An html element that the user wants to track + * @returns An object containing the parent info, can be empty if nothing was found + */ + private _populateParentInfo(element: Element, dataTagPrefix: string, aiBlobAttributeTag: string): IContent { + let parentInfo: IContent = {}; + let parentId; + + // if the user does not set up parent info, walk to the DOM, find the closest parent element (with tags) and populate the info + const closestParentElement = walkUpDomChainWithElementValidation(element.parentElement, this._isTracked, dataTagPrefix); + if (closestParentElement) { + const dataAttr = closestParentElement.getAttribute(aiBlobAttributeTag) || element[aiBlobAttributeTag]; + if (dataAttr) { + try { + var telemetryObject = JSON.parse(dataAttr); + } catch (e) { + this._traceLogger.throwInternal( + LoggingSeverity.CRITICAL, + _ExtendedInternalMessageId.CannotParseAiBlobValue, "Can not parse " + dataAttr, + ); + } + if (telemetryObject) { + parentId = telemetryObject.id; + } + } else { + parentId = closestParentElement.getAttribute(dataTagPrefix+"id"); + } + } + if (parentId) { + parentInfo['parentid'] = parentId; + } + else { + let htmlContent= this._getHtmlIdAndContentName(element.parentElement); + parentInfo['parentid'] = htmlContent.id; + parentInfo['parentname'] = htmlContent.contentName; + } + return parentInfo; + } } From 4f80edb042586aa6b056149dab63dfaa77d84795 Mon Sep 17 00:00:00 2001 From: kryalama <66494519+kryalama@users.noreply.github.com> Date: Tue, 12 Jan 2021 20:39:42 -0800 Subject: [PATCH 2/4] Update shrinkwrap and bump version --- common/config/rush/npm-shrinkwrap.json | 101 ++++++++---------- .../package.json | 2 +- 2 files changed, 48 insertions(+), 55 deletions(-) diff --git a/common/config/rush/npm-shrinkwrap.json b/common/config/rush/npm-shrinkwrap.json index e10e0e1c1..bcf9b22f0 100644 --- a/common/config/rush/npm-shrinkwrap.json +++ b/common/config/rush/npm-shrinkwrap.json @@ -3,14 +3,14 @@ "version": "0.0.0", "dependencies": { "@babel/code-frame": { - "version": "7.10.4", + "version": "7.12.11", "from": "@babel/code-frame@^7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz" + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz" }, "@babel/helper-validator-identifier": { - "version": "7.10.4", + "version": "7.12.11", "from": "@babel/helper-validator-identifier@^7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz" + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz" }, "@babel/highlight": { "version": "7.10.4", @@ -40,19 +40,19 @@ "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.0.tgz" }, "@nodelib/fs.scandir": { - "version": "2.1.3", - "from": "@nodelib/fs.scandir@2.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz" + "version": "2.1.4", + "from": "@nodelib/fs.scandir@2.1.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz" }, "@nodelib/fs.stat": { - "version": "2.0.3", + "version": "2.0.4", "from": "@nodelib/fs.stat@^2.0.2", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz" + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz" }, "@nodelib/fs.walk": { - "version": "1.2.4", + "version": "1.2.6", "from": "@nodelib/fs.walk@^1.2.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz" + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz" }, "@rush-temp/applicationinsights-analytics-js": { "version": "0.0.0", @@ -155,14 +155,14 @@ "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz" }, "@types/node": { - "version": "14.14.7", + "version": "14.14.20", "from": "@types/node@*", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.7.tgz" + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.20.tgz" }, "@types/qunit": { - "version": "2.9.6", + "version": "2.11.1", "from": "@types/qunit@^2.5.3", - "resolved": "https://registry.npmjs.org/@types/qunit/-/qunit-2.9.6.tgz" + "resolved": "https://registry.npmjs.org/@types/qunit/-/qunit-2.11.1.tgz" }, "@types/sinon": { "version": "4.3.3", @@ -601,9 +601,9 @@ "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz" }, "debug": { - "version": "4.2.0", + "version": "4.3.1", "from": "debug@^4.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz" + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz" }, "decamelize": { "version": "1.2.0", @@ -890,9 +890,9 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" }, "fastq": { - "version": "1.9.0", + "version": "1.10.0", "from": "fastq@^1.6.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.9.0.tgz" + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.10.0.tgz" }, "fd-slicer": { "version": "1.1.0", @@ -1225,9 +1225,9 @@ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", "dependencies": { "debug": { - "version": "3.2.6", + "version": "3.2.7", "from": "debug@^3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz" + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" } } }, @@ -1257,9 +1257,9 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" }, "ip-regex": { - "version": "2.1.0", - "from": "ip-regex@>=2.1.0 <3.0.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz" + "version": "4.2.0", + "from": "ip-regex@^4.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.2.0.tgz" }, "is-accessor-descriptor": { "version": "0.1.6", @@ -1289,9 +1289,9 @@ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz" }, "is-core-module": { - "version": "2.1.0", + "version": "2.2.0", "from": "is-core-module@^2.1.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.1.0.tgz" + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz" }, "is-data-descriptor": { "version": "0.1.4", @@ -1374,7 +1374,7 @@ }, "is-url": { "version": "1.2.4", - "from": "is-url@>=1.2.2 <2.0.0", + "from": "is-url@>=1.2.4 <2.0.0", "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz" }, "is-utf8": { @@ -1388,9 +1388,9 @@ "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz" }, "is2": { - "version": "2.0.1", - "from": "is2@2.0.1", - "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.1.tgz" + "version": "2.0.6", + "from": "is2@^2.0.6", + "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.6.tgz" }, "isarray": { "version": "1.0.0", @@ -1560,19 +1560,19 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz" }, "mime": { - "version": "2.4.6", + "version": "2.4.7", "from": "mime@^2.0.3", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz" + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.7.tgz" }, "mime-db": { - "version": "1.44.0", - "from": "mime-db@1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz" + "version": "1.45.0", + "from": "mime-db@1.45.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz" }, "mime-types": { - "version": "2.1.27", + "version": "2.1.28", "from": "mime-types@~2.1.19", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz" + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz" }, "minimatch": { "version": "3.0.4", @@ -1860,9 +1860,9 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz" }, "qunit": { - "version": "2.12.0", + "version": "2.13.0", "from": "qunit@^2.9.1", - "resolved": "https://registry.npmjs.org/qunit/-/qunit-2.12.0.tgz", + "resolved": "https://registry.npmjs.org/qunit/-/qunit-2.13.0.tgz", "dependencies": { "commander": { "version": "6.2.0", @@ -2076,9 +2076,9 @@ "resolved": "https://registry.npmjs.org/rollup-plugin-uglify/-/rollup-plugin-uglify-6.0.4.tgz", "dependencies": { "uglify-js": { - "version": "3.11.6", + "version": "3.12.4", "from": "uglify-js@^3.4.9", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.11.6.tgz" + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.4.tgz" } } }, @@ -2319,9 +2319,9 @@ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz" }, "spdx-license-ids": { - "version": "3.0.6", + "version": "3.0.7", "from": "spdx-license-ids@^3.0.0", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz" + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz" }, "split-string": { "version": "3.1.0", @@ -2381,16 +2381,9 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" }, "tcp-port-used": { - "version": "1.0.1", + "version": "1.0.2", "from": "tcp-port-used@^1.0.1", - "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.1.tgz", - "dependencies": { - "debug": { - "version": "4.1.0", - "from": "debug@4.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz" - } - } + "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.2.tgz" }, "tiny-glob": { "version": "0.2.6", @@ -2475,9 +2468,9 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz" }, "js-yaml": { - "version": "3.14.0", + "version": "3.14.1", "from": "js-yaml@^3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz" + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" }, "supports-color": { "version": "5.5.0", diff --git a/extensions/applicationinsights-clickanalytics-js/package.json b/extensions/applicationinsights-clickanalytics-js/package.json index 56bba55cd..ec62997d9 100644 --- a/extensions/applicationinsights-clickanalytics-js/package.json +++ b/extensions/applicationinsights-clickanalytics-js/package.json @@ -1,6 +1,6 @@ { "name": "@microsoft/applicationinsights-clickanalytics-js", - "version": "2.5.10", + "version": "2.5.11", "description": "Microsoft Application Insights Click Analytics extension", "main": "dist/applicationinsights-clickanalytics-js.js", "module": "dist-esm/applicationinsights-clickanalytics-js.js", From bd4a389b9641ca633beae6b59461daec86fa1e6a Mon Sep 17 00:00:00 2001 From: kryalama <66494519+kryalama@users.noreply.github.com> Date: Wed, 13 Jan 2021 14:08:48 -0800 Subject: [PATCH 3/4] Add new flag to not sent undefined events. --- .../Tests/ClickEventTest.ts | 46 +++++++++++++++++-- .../package.json | 2 +- .../src/Interfaces/Datamodel.ts | 5 ++ .../src/common/Utils.ts | 3 +- .../src/events/PageAction.ts | 16 ++++++- .../src/handlers/DomContentHandler.ts | 3 +- 6 files changed, 65 insertions(+), 10 deletions(-) diff --git a/extensions/applicationinsights-clickanalytics-js/Tests/ClickEventTest.ts b/extensions/applicationinsights-clickanalytics-js/Tests/ClickEventTest.ts index c438617e7..f5b87f711 100644 --- a/extensions/applicationinsights-clickanalytics-js/Tests/ClickEventTest.ts +++ b/extensions/applicationinsights-clickanalytics-js/Tests/ClickEventTest.ts @@ -225,7 +225,7 @@ export class ClickEventTest extends TestClass { this.clock.tick(500); Assert.equal(true, spy.called); var calledEvent: ITelemetryItem = spy.getCall(0).args[0]; - Assert.equal(undefined, calledEvent.baseData["name"]); + Assert.equal("not_specified", calledEvent.baseData["name"]); Assert.equal(expectedContent, calledEvent.data["content"]); } }); @@ -275,7 +275,7 @@ export class ClickEventTest extends TestClass { Assert.equal(true, spy.called); var calledEvent = spy.getCall(0).args[0]; Assert.equal("testId", calledEvent.baseData["name"]); - Assert.equal(undefined, calledEvent.data["parentId"]); + Assert.equal("not_specified", calledEvent.data["parentId"]); Assert.equal(expectedContent, calledEvent.data["content"]); } }); @@ -533,7 +533,7 @@ export class ClickEventTest extends TestClass { Assert.equal(true, spy.called); var calledEvent = spy.getCall(0).args[0]; Assert.equal("testId", calledEvent.baseData["name"]); - Assert.equal(undefined, calledEvent.data["parentId"]); + Assert.equal("not_specified", calledEvent.data["parentId"]); Assert.equal(expectedContent, calledEvent.data["content"]); } }); @@ -626,7 +626,7 @@ export class ClickEventTest extends TestClass { this.clock.tick(500); Assert.equal(true, spy.called); var calledEvent = spy.getCall(0).args[0]; - Assert.equal(undefined, calledEvent.baseData["name"]); + Assert.equal("not_specified", calledEvent.baseData["name"]); Assert.equal("parentTestId", calledEvent.data["parentId"]); Assert.equal(expectedContent, calledEvent.data["content"]); } @@ -940,6 +940,44 @@ export class ClickEventTest extends TestClass { Assert.equal(2,calledEvent.data["behavior"]); } }); + + this.testCase({ + name: "PageAction not called on undefined event and disableUndefinedEventsTracking is set to true", + test: () => { + const config = { + callback: { + contentName: () => "testContentName" + }, + dataTags : { + useDefaultContentNameOrId : false, + metaDataPrefix:'ha-', + customDataPrefix: 'data-ha-', + aiBlobAttributeTag: 'blob' + }, + disableUndefinedEventsTracking:true + }; + const clickAnalyticsPlugin = new ClickAnalyticsPlugin(); + const core = new AppInsightsCore(); + const channel = new ChannelPlugin(); + const traceLogger = new DiagnosticLogger({ loggingLevelConsole: 1 } as any); + const contentHandler = new DomContentHandler(mergeConfig(config), traceLogger); + const pageAction = new PageAction(clickAnalyticsPlugin, mergeConfig(config), contentHandler, null, {}, traceLogger ); + core.initialize({ + instrumentationKey: 'testIkey', + extensionConfig : { + [clickAnalyticsPlugin.identifier] : config + } + } as IConfig & IConfiguration, [clickAnalyticsPlugin, channel]); + + let spy = this.sandbox.spy(clickAnalyticsPlugin.core, 'track'); + const element = document.createElement('a'); + element.setAttribute("id","testId"); + ((element) as HTMLBaseElement).href = "testClickTarget"; + pageAction.capturePageAction(element); + this.clock.tick(500); + Assert.equal(false, spy.called); + } + }); } } diff --git a/extensions/applicationinsights-clickanalytics-js/package.json b/extensions/applicationinsights-clickanalytics-js/package.json index ec62997d9..56bba55cd 100644 --- a/extensions/applicationinsights-clickanalytics-js/package.json +++ b/extensions/applicationinsights-clickanalytics-js/package.json @@ -1,6 +1,6 @@ { "name": "@microsoft/applicationinsights-clickanalytics-js", - "version": "2.5.11", + "version": "2.5.10", "description": "Microsoft Application Insights Click Analytics extension", "main": "dist/applicationinsights-clickanalytics-js.js", "module": "dist-esm/applicationinsights-clickanalytics-js.js", diff --git a/extensions/applicationinsights-clickanalytics-js/src/Interfaces/Datamodel.ts b/extensions/applicationinsights-clickanalytics-js/src/Interfaces/Datamodel.ts index 91689a251..196cb19e0 100644 --- a/extensions/applicationinsights-clickanalytics-js/src/Interfaces/Datamodel.ts +++ b/extensions/applicationinsights-clickanalytics-js/src/Interfaces/Datamodel.ts @@ -38,6 +38,11 @@ export interface IClickAnalyticsConfiguration { * value will be ovverriden if the element has the data-*-bhvr attribute present. */ defaultRightClickBhvr?: string | number; + /** + * Flag to drop events that donot have custom event names, no parentId and no data in content (basically no useful click data). + * Default will be false + */ + disableUndefinedEventsTracking?: boolean; } /** diff --git a/extensions/applicationinsights-clickanalytics-js/src/common/Utils.ts b/extensions/applicationinsights-clickanalytics-js/src/common/Utils.ts index 072a58d5a..77247bd3e 100644 --- a/extensions/applicationinsights-clickanalytics-js/src/common/Utils.ts +++ b/extensions/applicationinsights-clickanalytics-js/src/common/Utils.ts @@ -317,7 +317,8 @@ export function mergeConfig(overrideConfig: IClickAnalyticsConfiguration): IClic dntDataTag: DEFAULT_DONOT_TRACK_TAG, }, behaviorValidator: (key:string) => key || "", - defaultRightClickBhvr: "" + defaultRightClickBhvr: "", + disableUndefinedEventsTracking: false }; let attributesThatAreObjectsInConfig: any[] = []; diff --git a/extensions/applicationinsights-clickanalytics-js/src/events/PageAction.ts b/extensions/applicationinsights-clickanalytics-js/src/events/PageAction.ts index 43e735092..784e5cc3c 100644 --- a/extensions/applicationinsights-clickanalytics-js/src/events/PageAction.ts +++ b/extensions/applicationinsights-clickanalytics-js/src/events/PageAction.ts @@ -7,6 +7,7 @@ import * as DataCollector from '../DataCollector'; import { ITelemetryItem, getPerformance, ICustomProperties, LoggingSeverity } from "@microsoft/applicationinsights-core-js" import { IPageActionOverrideValues, IPageActionTelemetry } from '../Interfaces/Datamodel'; import { extractFieldFromObject, bracketIt, isValueAssigned, extend, _ExtendedInternalMessageId } from '../common/Utils'; +import { Util as CommonUtil } from '@microsoft/applicationinsights-common'; export class PageAction extends WebEvent { @@ -94,8 +95,8 @@ export class PageAction extends WebEvent { ) } } - pageActionEvent.name = elementContent.id || elementContent.contentName || ''; - pageActionEvent.parentId = elementContent.parentid || elementContent.parentName || ''; + pageActionEvent.name = elementContent.id || elementContent.contentName || CommonUtil.NotSpecified; + pageActionEvent.parentId = elementContent.parentid || elementContent.parentName || CommonUtil.NotSpecified; if (isValueAssigned(overrideValues.actionType)) { pageActionEvent.actionType = overrideValues.actionType; @@ -112,6 +113,7 @@ export class PageAction extends WebEvent { pageActionEvent.timeToAction = this._getTimeToClick(); pageActionEvent.refUri = isValueAssigned(overrideValues.refUri) ? overrideValues.refUri : this._config.coreData.referrerUri; + if(this._isUndefinedEvent(pageActionEvent)) return; this.trackPageAction(pageActionEvent, pageActionProperties); } @@ -144,4 +146,14 @@ export class PageAction extends WebEvent { } } + private _isUndefinedEvent(pageActionEvent: IPageActionTelemetry) { + if(this._config.disableUndefinedEventsTracking) { + if(pageActionEvent.name === CommonUtil.NotSpecified + && pageActionEvent.parentId === CommonUtil.NotSpecified + && pageActionEvent.content === "[{}]") + return true; + } + return false; + } + } diff --git a/extensions/applicationinsights-clickanalytics-js/src/handlers/DomContentHandler.ts b/extensions/applicationinsights-clickanalytics-js/src/handlers/DomContentHandler.ts index cd76f8b09..fc5866cab 100644 --- a/extensions/applicationinsights-clickanalytics-js/src/handlers/DomContentHandler.ts +++ b/extensions/applicationinsights-clickanalytics-js/src/handlers/DomContentHandler.ts @@ -2,7 +2,7 @@ * @copyright Microsoft 2020 */ import { - findClosestByAttribute, removeInvalidElements, + removeInvalidElements, walkUpDomChainWithElementValidation, extend, _ExtendedInternalMessageId, isValueAssigned } from '../common/Utils'; @@ -48,7 +48,6 @@ export class DomContentHandler implements IContentHandler { } let elementContent: any = {}; - let biBlobElement; let biBlobValue; let parentDataTagPrefix; const dataTagPrefix:string = this._config.dataTags.customDataPrefix; From f3e5d66be885469d6190e874ca44c2b328f51f41 Mon Sep 17 00:00:00 2001 From: kryalama <66494519+kryalama@users.noreply.github.com> Date: Thu, 14 Jan 2021 14:16:20 -0800 Subject: [PATCH 4/4] minor change in config --- .../Tests/ClickEventTest.ts | 2 +- .../src/Interfaces/Datamodel.ts | 4 ++-- .../src/common/Utils.ts | 2 +- .../src/events/PageAction.ts | 15 ++++++++------- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/extensions/applicationinsights-clickanalytics-js/Tests/ClickEventTest.ts b/extensions/applicationinsights-clickanalytics-js/Tests/ClickEventTest.ts index f5b87f711..32c91a08b 100644 --- a/extensions/applicationinsights-clickanalytics-js/Tests/ClickEventTest.ts +++ b/extensions/applicationinsights-clickanalytics-js/Tests/ClickEventTest.ts @@ -954,7 +954,7 @@ export class ClickEventTest extends TestClass { customDataPrefix: 'data-ha-', aiBlobAttributeTag: 'blob' }, - disableUndefinedEventsTracking:true + dropInvalidEvents:true }; const clickAnalyticsPlugin = new ClickAnalyticsPlugin(); const core = new AppInsightsCore(); diff --git a/extensions/applicationinsights-clickanalytics-js/src/Interfaces/Datamodel.ts b/extensions/applicationinsights-clickanalytics-js/src/Interfaces/Datamodel.ts index 196cb19e0..18603b7d5 100644 --- a/extensions/applicationinsights-clickanalytics-js/src/Interfaces/Datamodel.ts +++ b/extensions/applicationinsights-clickanalytics-js/src/Interfaces/Datamodel.ts @@ -39,10 +39,10 @@ export interface IClickAnalyticsConfiguration { */ defaultRightClickBhvr?: string | number; /** - * Flag to drop events that donot have custom event names, no parentId and no data in content (basically no useful click data). + * Flag to drop events that do not have custom event names, no parentId and no data in content (basically no useful click data). * Default will be false */ - disableUndefinedEventsTracking?: boolean; + dropInvalidEvents?: boolean; } /** diff --git a/extensions/applicationinsights-clickanalytics-js/src/common/Utils.ts b/extensions/applicationinsights-clickanalytics-js/src/common/Utils.ts index 77247bd3e..fe43ef2de 100644 --- a/extensions/applicationinsights-clickanalytics-js/src/common/Utils.ts +++ b/extensions/applicationinsights-clickanalytics-js/src/common/Utils.ts @@ -318,7 +318,7 @@ export function mergeConfig(overrideConfig: IClickAnalyticsConfiguration): IClic }, behaviorValidator: (key:string) => key || "", defaultRightClickBhvr: "", - disableUndefinedEventsTracking: false + dropInvalidEvents : false }; let attributesThatAreObjectsInConfig: any[] = []; diff --git a/extensions/applicationinsights-clickanalytics-js/src/events/PageAction.ts b/extensions/applicationinsights-clickanalytics-js/src/events/PageAction.ts index 784e5cc3c..e3247ee9e 100644 --- a/extensions/applicationinsights-clickanalytics-js/src/events/PageAction.ts +++ b/extensions/applicationinsights-clickanalytics-js/src/events/PageAction.ts @@ -4,7 +4,7 @@ import { WebEvent } from './WebEvent'; import * as DataCollector from '../DataCollector'; -import { ITelemetryItem, getPerformance, ICustomProperties, LoggingSeverity } from "@microsoft/applicationinsights-core-js" +import { ITelemetryItem, getPerformance, ICustomProperties, LoggingSeverity, objForEachKey } from "@microsoft/applicationinsights-core-js" import { IPageActionOverrideValues, IPageActionTelemetry } from '../Interfaces/Datamodel'; import { extractFieldFromObject, bracketIt, isValueAssigned, extend, _ExtendedInternalMessageId } from '../common/Utils'; import { Util as CommonUtil } from '@microsoft/applicationinsights-common'; @@ -42,12 +42,13 @@ export class PageAction extends WebEvent { this._populateEventDataIfPresent(event.data, 'refUri', pageActionEvent.refUri); this._populateEventDataIfPresent(event.data, 'pageName', pageActionEvent.pageName); this._populateEventDataIfPresent(event.data, 'parentId', pageActionEvent.parentId); - for (let property in properties) { - if (properties.hasOwnProperty(property)) { + + if (properties) { + objForEachKey(properties, (property, value) => { if (!event.data[property]) { - this._populateEventDataIfPresent(event.data, property, properties[property]); + this._populateEventDataIfPresent(event.data, property, value); } - } + }); } this._clickAnalyticsPlugin.core.track(event); } @@ -140,14 +141,14 @@ export class PageAction extends WebEvent { delete pageActionContent.id; delete pageActionContent.parentid; delete pageActionContent.parentname; - if(isValueAssigned(this._config.dataTags.parentDataTag)) { + if(this._config && this._config.dataTags && isValueAssigned(this._config.dataTags.parentDataTag)) { delete pageActionContent[this._config.dataTags.parentDataTag]; } } } private _isUndefinedEvent(pageActionEvent: IPageActionTelemetry) { - if(this._config.disableUndefinedEventsTracking) { + if(this._config.dropInvalidEvents) { if(pageActionEvent.name === CommonUtil.NotSpecified && pageActionEvent.parentId === CommonUtil.NotSpecified && pageActionEvent.content === "[{}]")