diff --git a/extensions/applicationinsights-clickanalytics-js/Tests/Unit/src/ClickEventTest.ts b/extensions/applicationinsights-clickanalytics-js/Tests/Unit/src/ClickEventTest.ts index b857b6f95..445c9f749 100644 --- a/extensions/applicationinsights-clickanalytics-js/Tests/Unit/src/ClickEventTest.ts +++ b/extensions/applicationinsights-clickanalytics-js/Tests/Unit/src/ClickEventTest.ts @@ -49,6 +49,8 @@ export class ClickEventTest extends AITestClass { pageType: "" }; + const defaultElementTypes = "A,BUTTON,AREA,INPUT"; + const clickAnalyticsPlugin = new ClickAnalyticsPlugin(); const core = new AppInsightsCore(); const channel = new ChannelPlugin(); @@ -76,6 +78,7 @@ export class ClickEventTest extends AITestClass { Assert.equal(extConfig.urlCollectQuery, false, "urlCollectQuery should be false by default"); Assert.deepEqual(extConfig.dataTags, dataTagsDefault, "udataTags should be set by default"); Assert.deepEqual(extConfig.coreData, coreDataDefault, "udataTags should be set by default"); + Assert.deepEqual(extConfig.trackElementTypes, defaultElementTypes, "trackElementTypes should be set by default"); Assert.ok(extConfig.callback, "callback should be set by default"); let callbacks = extConfig.callback; @@ -420,11 +423,50 @@ export class ClickEventTest extends AITestClass { defaultRightClickBhvr: "", dropInvalidEvents : false, urlCollectHash: false, - urlCollectQuery: false + urlCollectQuery: false, + trackElementTypes: "A,BUTTON,AREA,INPUT" }, core.config.extensionConfig[clickAnalyticsPlugin.identifier]); } }); + this.testCase({ + name: "trackElementTypes: validate empty string, string with spaces, lowercase, and dynamic changes", + useFakeTimers: true, + test: () => { + const config = { + trackElementTypes: "A,BUTTON,AREA,INPUT" + }; + const clickAnalyticsPlugin = new ClickAnalyticsPlugin(); + const core = new AppInsightsCore(); + const channel = new ChannelPlugin(); + + core.initialize({ + instrumentationKey: 'testIkey', + extensionConfig: { + [clickAnalyticsPlugin.identifier]: config + } + } as IConfig & IConfiguration, [clickAnalyticsPlugin, channel]); + this.onDone(() => { + core.unload(false); + }); + + let currentConfig = core.config["extensionConfig"][clickAnalyticsPlugin.identifier].trackElementTypes; + // Validate default value + Assert.equal("A,BUTTON,AREA,INPUT", currentConfig, "Default trackElementTypes should be 'A,BUTTON,AREA,INPUT'"); + + // Test empty string + core.config["extensionConfig"][clickAnalyticsPlugin.identifier].trackElementTypes = null; + this.clock.tick(1); + currentConfig = core.config["extensionConfig"][clickAnalyticsPlugin.identifier].trackElementTypes; + Assert.equal("A,BUTTON,AREA,INPUT", currentConfig, "default value would be applied"); + + // Test dynamic change + core.config["extensionConfig"][clickAnalyticsPlugin.identifier].trackElementTypes = "A,BUTTON,AREA,INPUT,TEST"; + this.clock.tick(1); + currentConfig = core.config["extensionConfig"][clickAnalyticsPlugin.identifier].trackElementTypes; + Assert.equal("A,BUTTON,AREA,INPUT,TEST", currentConfig, "spaces and lowercase string will be converted to uppercase and trimmed"); + } + }); this.testCase({ name: "PageAction properties are correctly assigned (Populated) with useDefaultContentNameOrId flag false", diff --git a/extensions/applicationinsights-clickanalytics-js/src/ClickAnalyticsPlugin.ts b/extensions/applicationinsights-clickanalytics-js/src/ClickAnalyticsPlugin.ts index 704938be2..5cd0502ad 100644 --- a/extensions/applicationinsights-clickanalytics-js/src/ClickAnalyticsPlugin.ts +++ b/extensions/applicationinsights-clickanalytics-js/src/ClickAnalyticsPlugin.ts @@ -52,7 +52,8 @@ const defaultValues: IConfigDefaults = objDeepFree defaultRightClickBhvr: cfgDfString(), dropInvalidEvents : false, urlCollectHash: false, - urlCollectQuery: false + urlCollectQuery: false, + trackElementTypes: cfgDfString("A,BUTTON,AREA,INPUT") }); function _dataPrefixChk(val: any) { @@ -147,7 +148,7 @@ export class ClickAnalyticsPlugin extends BaseTelemetryPlugin { _contentHandler = new DomContentHandler(_config, logger); let metaTags = _contentHandler.getMetadata(); _pageAction = new PageAction(_self, _config, _contentHandler, _config.callback.pageActionPageTags, metaTags, logger); - + // Default to DOM autoCapture handler if (_autoCaptureHandler) { _autoCaptureHandler._doUnload(); diff --git a/extensions/applicationinsights-clickanalytics-js/src/Interfaces/Datamodel.ts b/extensions/applicationinsights-clickanalytics-js/src/Interfaces/Datamodel.ts index bd38d0851..d57aba03d 100644 --- a/extensions/applicationinsights-clickanalytics-js/src/Interfaces/Datamodel.ts +++ b/extensions/applicationinsights-clickanalytics-js/src/Interfaces/Datamodel.ts @@ -51,6 +51,12 @@ export interface IClickAnalyticsConfiguration { * Enables the logging of the query string of the URL. Default is "false." */ urlCollectQuery?: boolean; + + /** + * A list of element types to track. Default is "undefined" which means default elements ["a", "button", "area", "input"] are tracked. + * If set, it will combine with the default element types. + */ + trackElementTypes?: string; } /** diff --git a/extensions/applicationinsights-clickanalytics-js/src/handlers/AutoCaptureHandler.ts b/extensions/applicationinsights-clickanalytics-js/src/handlers/AutoCaptureHandler.ts index 5371381ef..0683ec43d 100644 --- a/extensions/applicationinsights-clickanalytics-js/src/handlers/AutoCaptureHandler.ts +++ b/extensions/applicationinsights-clickanalytics-js/src/handlers/AutoCaptureHandler.ts @@ -4,9 +4,10 @@ import dynamicProto from "@microsoft/dynamicproto-js"; import { - IDiagnosticLogger, IProcessTelemetryUnloadContext, ITelemetryUnloadState, createUniqueNamespace, eventOff, eventOn, getDocument, - getWindow, isNullOrUndefined, mergeEvtNamespace + IDiagnosticLogger, IProcessTelemetryUnloadContext, ITelemetryUnloadState, IUnloadHook, createUniqueNamespace, eventOff, eventOn, + getDocument, getWindow, isNullOrUndefined, mergeEvtNamespace, onConfigChange } from "@microsoft/applicationinsights-core-js"; +import { arrMap, strTrim } from "@nevware21/ts-utils"; import { ClickAnalyticsPlugin } from "../ClickAnalyticsPlugin"; import { ActionType } from "../Enums"; import { IAutoCaptureHandler, IClickAnalyticsConfiguration, IPageActionOverrideValues } from "../Interfaces/Datamodel"; @@ -23,11 +24,12 @@ export class AutoCaptureHandler implements IAutoCaptureHandler { */ constructor(protected _analyticsPlugin: ClickAnalyticsPlugin, protected _config: IClickAnalyticsConfiguration, protected _pageAction: PageAction, protected _traceLogger: IDiagnosticLogger) { - let _evtNamespace = mergeEvtNamespace(createUniqueNamespace("AutoCaptureHandler"), (_analyticsPlugin as any)._evtNamespace); - + let unloadHandler: IUnloadHook = onConfigChange(_config, () => { + _clickCaptureElements = arrMap(_config.trackElementTypes.toUpperCase().split(","), tag => strTrim(tag)); + }); + let _clickCaptureElements: string[]; dynamicProto(AutoCaptureHandler, this, (_self) => { - _self.click = () => { let win = getWindow(); let doc = getDocument(); @@ -47,6 +49,8 @@ export class AutoCaptureHandler implements IAutoCaptureHandler { _self._doUnload = (unloadCtx?: IProcessTelemetryUnloadContext, unloadState?: ITelemetryUnloadState, asyncCallback?: () => void): void | boolean => { eventOff(getWindow(), null, null, _evtNamespace); eventOff(getDocument(), null, null, _evtNamespace); + unloadHandler && unloadHandler.rm(); + unloadHandler = null; }; function _capturePageAction(element: Element, overrideValues?: IPageActionOverrideValues, customProperties?: { [name: string]: string | number | boolean | string[] | number[] | boolean[] | object }, isRightClick?: boolean): void { @@ -58,7 +62,6 @@ export class AutoCaptureHandler implements IAutoCaptureHandler { // Process click event function _processClick(clickEvent: any) { - var clickCaptureElements = { A: true, BUTTON: true, AREA: true, INPUT: true }; let win = getWindow(); if (isNullOrUndefined(clickEvent) && win) { clickEvent = win.event; // IE 8 does not pass the event @@ -89,11 +92,11 @@ export class AutoCaptureHandler implements IAutoCaptureHandler { while (element && element.tagName) { // control property will be available for