Feature: add SPA auto route change tracking (#947)
* add spa auto route change tracking * use this.config isntead of config * readme: remove newly unneeded documenation * analyics: add pushState undefined tests * types: add properties plugin types * tests: add any type to tests * send pv duration of 0, refresh operation name * analytics: use self ref to this inside callbacks
This commit is contained in:
Родитель
3d92a13481
Коммит
c2e413decf
11
README.md
11
README.md
|
@ -154,16 +154,13 @@ Most configuration fields are named such that they can be defaulted to falsey. A
|
|||
| appId | null | AppId is used for the correlation between AJAX dependencies happening on the client-side with the server-side requests. When Beacon API is enabled, it cannot be used automatically, but can be set manually in the configuration. Default is null |
|
||||
| enableCorsCorrelation | false | If true, the SDK will add two headers ('Request-Id' and 'Request-Context') to all CORS requests tocorrelate outgoing AJAX dependencies with corresponding requests on the server side. Default is false |
|
||||
| namePrefix | undefined | An optional value that will be used as name postfix for localStorage and cookie name.
|
||||
<!-- | enableAutoRouteTracking | false | Automatically track route changes in Single Page Applications (SPA). If true, each route change will send a new Pageview to Application Insights. Hash route changes changes (`example.com/foo#bar`) are also recorded as new page views. -->
|
||||
|
||||
## Single Page Applications
|
||||
|
||||
By default, this SDK will **not** handle state based route changing that occurs in single page applications unless you use a plugin designed for your frontend framework (Angular, React, Vue, etc). Currently, we support a separate [React plugin](#available-extensions-for-the-sdk) which you can initialize with this SDK and it will accomplish this for you in your React application. Otherwise, you must manually trigger pageviews on each route change. An example of accomplishing this for a React application is located [here](https://github.com/Azure-Samples/appinsights-guestbook/blob/6555933e19d737b2ff4f9f339cc1b928f0c08cdb/client/src/AppContainer.js#L17-L20). Note that you must refresh the current operation's id **as well as** trigger an additional pageview:
|
||||
**React Router history listener example**
|
||||
```js
|
||||
this.unlisten = this.props.history.listen((location, action) => {
|
||||
ai.properties.context.telemetryTrace.traceID = Util.newId();
|
||||
ai.trackPageView({name: window.location.pathname});
|
||||
});
|
||||
By default, this SDK will **not** handle state based route changing that occurs in single page applications unless you use a plugin designed for your frontend framework (Angular, React, Vue, etc). <!-- To enable enable automatic route change tracking for your single page application, you can add `enableAutoRouteTracking: true` to the setup configuration. -->
|
||||
|
||||
Currently, we support a separate [React plugin](#available-extensions-for-the-sdk) which you can initialize with this SDK. It will also accomplish route change tracking for you, as well as collect [other React specific telemetry](./vNext/extensions/applicationinsights-react-js).
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/// <reference path="./TestFramework/Common.ts" />
|
||||
|
||||
import { Util, Exception, SeverityLevel, Trace, PageViewPerformance, PageView } from "@microsoft/applicationinsights-common";
|
||||
import { Util, Exception, SeverityLevel, Trace, PageViewPerformance, PageView, IConfig } from "@microsoft/applicationinsights-common";
|
||||
import {
|
||||
ITelemetryItem, AppInsightsCore,
|
||||
IPlugin, IConfiguration
|
||||
|
@ -26,6 +26,88 @@ export class ApplicationInsightsTests extends TestClass {
|
|||
}
|
||||
|
||||
public registerTests() {
|
||||
this.testCase({
|
||||
name: 'enableAutoRouteTracking: event listener is added to the popstate event',
|
||||
test: () => {
|
||||
// Setup
|
||||
var appInsights = new ApplicationInsights();
|
||||
var core = new AppInsightsCore();
|
||||
var channel = new ChannelPlugin();
|
||||
var eventListenerStub = this.sandbox.stub(window, 'addEventListener');
|
||||
|
||||
// Act
|
||||
core.initialize(<IConfig & IConfiguration>{
|
||||
instrumentationKey: '',
|
||||
enableAutoRouteTracking: true
|
||||
}, [appInsights, channel]);
|
||||
|
||||
// Assert
|
||||
Assert.ok(eventListenerStub.calledTwice);
|
||||
Assert.equal(eventListenerStub.args[0][0], "popstate");
|
||||
Assert.equal(eventListenerStub.args[1][0], "locationchange");
|
||||
}
|
||||
});
|
||||
|
||||
this.testCase({
|
||||
name: 'enableAutoRouteTracking: route changes trigger a new pageview',
|
||||
test: () => {
|
||||
// Setup
|
||||
var appInsights = new ApplicationInsights();
|
||||
var core = new AppInsightsCore();
|
||||
var channel = new ChannelPlugin();
|
||||
appInsights['_properties'] = <any>{
|
||||
context: { telemetryTrace: { traceID: 'not set', name: 'name not set' } }
|
||||
}
|
||||
const trackPageViewStub = this.sandbox.stub(appInsights, 'trackPageView');
|
||||
|
||||
// Act
|
||||
core.initialize(<IConfig & IConfiguration>{
|
||||
instrumentationKey: '',
|
||||
enableAutoRouteTracking: true
|
||||
}, [appInsights, channel]);
|
||||
window.dispatchEvent(new Event('locationchange'));
|
||||
|
||||
// Assert
|
||||
Assert.ok(trackPageViewStub.calledOnce);
|
||||
Assert.ok(appInsights['_properties'].context.telemetryTrace.traceID);
|
||||
Assert.ok(appInsights['_properties'].context.telemetryTrace.name);
|
||||
Assert.notEqual(appInsights['_properties'].context.telemetryTrace.traceID, 'not set', 'current operation id is updated after route change');
|
||||
Assert.notEqual(appInsights['_properties'].context.telemetryTrace.name, 'name not set', 'current operation name is updated after route change');
|
||||
}
|
||||
});
|
||||
|
||||
this.testCase({
|
||||
name: 'enableAutoRouteTracking: (IE9) app does not crash if history.pushState does not exist',
|
||||
test: () => {
|
||||
// Setup
|
||||
const originalPushState = history.pushState;
|
||||
const originalReplaceState = history.replaceState;
|
||||
history.pushState = null;
|
||||
history.replaceState = null;
|
||||
var appInsights = new ApplicationInsights();
|
||||
var core = new AppInsightsCore();
|
||||
var channel = new ChannelPlugin();
|
||||
appInsights['_properties'] = <any>{
|
||||
context: { telemetryTrace: { traceID: 'not set'}}
|
||||
}
|
||||
this.sandbox.stub(appInsights, 'trackPageView');
|
||||
|
||||
// Act
|
||||
core.initialize(<IConfig & IConfiguration>{
|
||||
instrumentationKey: '',
|
||||
enableAutoRouteTracking: true
|
||||
}, [appInsights, channel]);
|
||||
window.dispatchEvent(new Event('locationchange'));
|
||||
|
||||
// Assert
|
||||
Assert.ok(true, 'App does not crash when history object is incomplete');
|
||||
|
||||
// Cleanup
|
||||
history.pushState = originalPushState;
|
||||
history.replaceState = originalReplaceState;
|
||||
}
|
||||
});
|
||||
|
||||
this.testCase({
|
||||
name: 'AppInsightsTests: PageVisitTimeManager is constructed when analytics plugin is initialized',
|
||||
test: () => {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
"test": "grunt aitests"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/applicationinsights-properties-js": "2.0.1",
|
||||
"typescript": "2.5.3",
|
||||
"rollup-plugin-node-resolve": "^3.4.0",
|
||||
"rollup-plugin-replace": "^2.1.0",
|
||||
|
|
|
@ -4,12 +4,13 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
IConfig, Util, PageViewPerformance, IAppInsights, PageView, RemoteDependencyData, Event, IEventTelemetry,
|
||||
IConfig, Util, PageViewPerformance, IAppInsights, PageView, RemoteDependencyData, Event as EventTelemetry, IEventTelemetry,
|
||||
TelemetryItemCreator, Metric, Exception, SeverityLevel, Trace, IDependencyTelemetry,
|
||||
IExceptionTelemetry, ITraceTelemetry, IMetricTelemetry, IAutoExceptionTelemetry,
|
||||
IPageViewTelemetryInternal, IPageViewTelemetry, IPageViewPerformanceTelemetry, IPageViewPerformanceTelemetryInternal,
|
||||
ConfigurationManager, DateTimeUtils,
|
||||
IExceptionInternal
|
||||
IExceptionInternal,
|
||||
PropertiesPluginIdentifier
|
||||
} from "@microsoft/applicationinsights-common";
|
||||
|
||||
import {
|
||||
|
@ -22,6 +23,9 @@ import { PageVisitTimeManager } from "./Telemetry/PageVisitTimeManager";
|
|||
import { PageViewPerformanceManager } from './Telemetry/PageViewPerformanceManager';
|
||||
import { ITelemetryConfig } from "../JavaScriptSDK.Interfaces/ITelemetryConfig";
|
||||
|
||||
// For types only
|
||||
import * as properties from "@microsoft/applicationinsights-properties-js";
|
||||
|
||||
"use strict";
|
||||
|
||||
const durationProperty: string = "duration";
|
||||
|
@ -39,6 +43,7 @@ export class ApplicationInsights implements IAppInsights, ITelemetryPlugin, IApp
|
|||
private _globalconfig: IConfiguration;
|
||||
private _eventTracking: Timing;
|
||||
private _pageTracking: Timing;
|
||||
private _properties: properties.PropertiesPlugin;
|
||||
protected _nextPlugin: ITelemetryPlugin;
|
||||
protected _logger: IDiagnosticLogger; // Initialized by Core
|
||||
protected _telemetryInitializers: { (envelope: ITelemetryItem): boolean | void; }[]; // Internal telemetry initializers.
|
||||
|
@ -75,6 +80,8 @@ export class ApplicationInsights implements IAppInsights, ITelemetryPlugin, IApp
|
|||
config.isCookieUseDisabled = Util.stringToBoolOrDefault(config.isCookieUseDisabled);
|
||||
config.isStorageUseDisabled = Util.stringToBoolOrDefault(config.isStorageUseDisabled);
|
||||
config.isBrowserLinkTrackingEnabled = Util.stringToBoolOrDefault(config.isBrowserLinkTrackingEnabled);
|
||||
config.enableAutoRouteTracking = Util.stringToBoolOrDefault(config.enableAutoRouteTracking);
|
||||
config.namePrefix = config.namePrefix || "";
|
||||
|
||||
return config;
|
||||
}
|
||||
|
@ -113,8 +120,8 @@ export class ApplicationInsights implements IAppInsights, ITelemetryPlugin, IApp
|
|||
try {
|
||||
let telemetryItem = TelemetryItemCreator.create<IEventTelemetry>(
|
||||
event,
|
||||
Event.dataType,
|
||||
Event.envelopeType,
|
||||
EventTelemetry.dataType,
|
||||
EventTelemetry.envelopeType,
|
||||
this._logger,
|
||||
customProperties
|
||||
);
|
||||
|
@ -223,7 +230,7 @@ export class ApplicationInsights implements IAppInsights, ITelemetryPlugin, IApp
|
|||
public trackPageView(pageView?: IPageViewTelemetry, customProperties?: ICustomProperties) {
|
||||
try {
|
||||
const inPv = pageView || {};
|
||||
this._pageViewManager.trackPageView(inPv, customProperties);
|
||||
this._pageViewManager.trackPageView(inPv, {...inPv.properties, ...inPv.measurements, ...customProperties});
|
||||
|
||||
if (this.config.autoTrackPageVisitTime) {
|
||||
this._pageVisitTimeManager.trackPreviousPageVisit(inPv.name, inPv.uri);
|
||||
|
@ -540,12 +547,12 @@ export class ApplicationInsights implements IAppInsights, ITelemetryPlugin, IApp
|
|||
this.sendPageViewInternal(pageViewItem);
|
||||
}
|
||||
|
||||
const instance: IAppInsights = this;
|
||||
if (this.config.disableExceptionTracking === false &&
|
||||
!this.config.autoExceptionInstrumented) {
|
||||
// We want to enable exception auto collection and it has not been done so yet
|
||||
const onerror = "onerror";
|
||||
const originalOnError = window[onerror];
|
||||
const instance: IAppInsights = this;
|
||||
window.onerror = function (message, url, lineNumber, columnNumber, error) {
|
||||
const handled = originalOnError && <any>originalOnError(message, url, lineNumber, columnNumber, error);
|
||||
if (handled !== true) { // handled could be typeof function
|
||||
|
@ -563,6 +570,47 @@ export class ApplicationInsights implements IAppInsights, ITelemetryPlugin, IApp
|
|||
this.config.autoExceptionInstrumented = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a custom "locationchange" event which is triggered each time the history object is changed
|
||||
*/
|
||||
if (this.config.enableAutoRouteTracking === true
|
||||
&& typeof history === "object" && typeof history.pushState === "function" && typeof history.replaceState === "function"
|
||||
&& typeof window === "object") {
|
||||
const _self = this;
|
||||
// Find the properties plugin
|
||||
extensions.forEach(extension => {
|
||||
if (extension.identifier === PropertiesPluginIdentifier) {
|
||||
this._properties = extension as properties.PropertiesPlugin;
|
||||
}
|
||||
});
|
||||
|
||||
history.pushState = ( f => function pushState() {
|
||||
var ret = f.apply(this, arguments);
|
||||
window.dispatchEvent(new Event(_self.config.namePrefix + "pushState"));
|
||||
window.dispatchEvent(new Event(_self.config.namePrefix + "locationchange"));
|
||||
return ret;
|
||||
})(history.pushState);
|
||||
|
||||
history.replaceState = ( f => function replaceState(){
|
||||
var ret = f.apply(this, arguments);
|
||||
window.dispatchEvent(new Event(_self.config.namePrefix + "replaceState"));
|
||||
window.dispatchEvent(new Event(_self.config.namePrefix + "locationchange"));
|
||||
return ret;
|
||||
})(history.replaceState);
|
||||
|
||||
window.addEventListener(_self.config.namePrefix + "popstate",()=>{
|
||||
window.dispatchEvent(new Event(_self.config.namePrefix + "locationchange"));
|
||||
});
|
||||
|
||||
window.addEventListener(_self.config.namePrefix + "locationchange", () => {
|
||||
if (_self._properties && _self._properties.context && _self._properties.context.telemetryTrace) {
|
||||
_self._properties.context.telemetryTrace.traceID = Util.newId();
|
||||
_self._properties.context.telemetryTrace.name = window.location.pathname;
|
||||
}
|
||||
_self.trackPageView({ properties: { duration: 0 } }); // SPA route change loading durations are undefined, so send 0
|
||||
});
|
||||
}
|
||||
|
||||
this._isInitialized = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -94,6 +94,13 @@ export interface IConfig {
|
|||
*/
|
||||
autoTrackPageVisitTime?: boolean;
|
||||
|
||||
/**
|
||||
* @description Automatically track route changes in Single Page Applications (SPA). If true, each route change will send a new Pageview to Application Insights.
|
||||
* @type {boolean}
|
||||
* @memberof IConfig
|
||||
*/
|
||||
enableAutoRouteTracking?: boolean;
|
||||
|
||||
/**
|
||||
* @description If true, Ajax calls are not autocollected. Default is false
|
||||
* @type {boolean}
|
||||
|
|
Загрузка…
Ссылка в новой задаче