Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

App Insights Provider #83

Merged
merged 9 commits into from
Dec 14, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ this.angulartics2.eventTrack.next({ action: 'myAction', properties: { category:
* Segment
* Baidu Analytics
* Facebook Pixel
* Application Insights

### For other providers

Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@
"Michael Cameron Delaney <michaelcamerondelaney@gmail.com> (https://github.com/NewMediaRoc)",
"Chris Soyars <ctso@ctso.me> (https://github.com/ctso)",
"Fil Maj <maj.fil@gmail.com> (https://github.com/filmaj)",
"Adam Yost <swimmadude66@users.noreply.github.com> (https://github.com/swimmadude66)"
"Adam Yost <swimmadude66@users.noreply.github.com> (https://github.com/swimmadude66)",
"Adam S. Kirschner <accounts@adamskirschner.com> (https://github.com/hikirsch)"
],
"dependencies": {},
"devDependencies": {
"@angular/common": "~2.2.0",
"@angular/compiler": "~2.2.0",
Expand All @@ -43,6 +43,7 @@
"@angular/platform-browser": "~2.2.0",
"@angular/platform-browser-dynamic": "~2.2.0",
"@angular/router": "~3.2.0",
"@types/applicationinsights-js": "^0.23.5",
"@types/core-js": "^0.9.32",
"@types/jasmine": "^2.2.29",
"@types/node": "6.0.38",
Expand Down
162 changes: 162 additions & 0 deletions src/providers/appinsights/angulartics2-appinsights.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { Location } from '@angular/common';
import { SpyLocation } from '@angular/common/testing';
import { TestBed, ComponentFixture, fakeAsync, inject } from '@angular/core/testing';

import { TestModule, RootCmp, advance, createRoot } from '../../test.mocks';

import { Angulartics2 } from '../../core/angulartics2';
import { Angulartics2AppInsights } from './angulartics2-appinsights';
import { RouterTestingModule } from '@angular/router/testing';
import { Title } from '@angular/platform-browser';

jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
declare let window: any;

describe('Angulartics2AppInsights', () => {
let appInsights: Microsoft.ApplicationInsights.IAppInsights;
let fixture: ComponentFixture<any>;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [
TestModule,
RouterTestingModule,
],
providers: [
{ provide: Location, useClass: SpyLocation },
Title,
Angulartics2AppInsights
]
});

window.appInsights = appInsights = jasmine.createSpyObj(
'appInsights',
[
'trackPageView',
'trackEvent',
'trackException',
'setAuthenticatedUserContext'
]
);
});

it('should track pages',
fakeAsync(inject([Location, Angulartics2, Angulartics2AppInsights, Title],
(location: Location, angulartics2: Angulartics2, angulartics2AppInsights: Angulartics2AppInsights, title: Title) => {
fixture = createRoot(RootCmp);
let metrics = {};
let dimensions = {};
let loadTime = 123;
spyOn(title, 'getTitle').and.returnValue('the title');
angulartics2AppInsights.metrics = metrics;
angulartics2AppInsights.dimensions = dimensions;
angulartics2AppInsights.loadTime = loadTime;
angulartics2.pageTrack.next({ path: '/abc', location: location });
advance(fixture);
expect(appInsights.trackPageView).toHaveBeenCalledWith('the title', '/abc', metrics, dimensions, loadTime);
}))
);

it('should track events',
fakeAsync(inject([Location, Angulartics2, Angulartics2AppInsights],
(location: Location, angulartics2: Angulartics2, angulartics2AppInsights: Angulartics2AppInsights) => {
fixture = createRoot(RootCmp);
let action = 'the action';
let properties = {};
let measurements = {};
angulartics2AppInsights.measurements = measurements;
angulartics2.eventTrack.next({
action, properties
});
advance(fixture);
expect(appInsights.trackEvent).toHaveBeenCalledWith(action, properties, measurements);
})));

it('should track exceptions (string)',
fakeAsync(inject([Location, Angulartics2, Angulartics2AppInsights],
(location: Location, angulartics2: Angulartics2, angulartics2AppInsights: Angulartics2AppInsights) => {
fixture = createRoot(RootCmp);
let str = 'test string';
angulartics2.exceptionTrack.next(str);
advance(fixture);
expect(appInsights.trackException).toHaveBeenCalledWith(str);
})));

it('should track exceptions (event)',
fakeAsync(inject([Location, Angulartics2, Angulartics2AppInsights],
(location: Location, angulartics2: Angulartics2, angulartics2AppInsights: Angulartics2AppInsights) => {
fixture = createRoot(RootCmp);
let event = { 'event': true };
angulartics2.exceptionTrack.next({ event });
advance(fixture);
expect(appInsights.trackException).toHaveBeenCalledWith(event);
})));

it('should track exceptions (description)',
fakeAsync(inject([Location, Angulartics2, Angulartics2AppInsights],
(location: Location, angulartics2: Angulartics2, angulartics2AppInsights: Angulartics2AppInsights) => {
fixture = createRoot(RootCmp);
let description = 'test description';
angulartics2.exceptionTrack.next({ description });
advance(fixture);
expect(appInsights.trackException).toHaveBeenCalledWith(description);
})));

it('should set userId in setAuthenticatedUserContext',
fakeAsync(inject([Location, Angulartics2, Angulartics2AppInsights],
(location: Location, angulartics2: Angulartics2, angulartics2AppInsights: Angulartics2AppInsights) => {
fixture = createRoot(RootCmp);
let userId = 'test_userId';
angulartics2AppInsights.setUsername(userId);
advance(fixture);
expect(angulartics2.settings.appInsights.userId).toBe(userId);
expect(appInsights.setAuthenticatedUserContext).toHaveBeenCalledWith(userId);
})));


it('should set userId and accountId in setAuthenticatedUserContext',
fakeAsync(inject([Location, Angulartics2, Angulartics2AppInsights],
(location: Location, angulartics2: Angulartics2, angulartics2AppInsights: Angulartics2AppInsights) => {
fixture = createRoot(RootCmp);
let userId = 'test_userId';
let accountId = 'test_accountId';
angulartics2AppInsights.setUserProperties({ userId, accountId });
advance(fixture);
expect(angulartics2.settings.appInsights.userId).toBe(userId);
expect(appInsights.setAuthenticatedUserContext).toHaveBeenCalledWith(userId, accountId);
})));


it('should user existing userId and set accountId in setAuthenticatedUserContext',
fakeAsync(inject([Location, Angulartics2, Angulartics2AppInsights],
(location: Location, angulartics2: Angulartics2, angulartics2AppInsights: Angulartics2AppInsights) => {
fixture = createRoot(RootCmp);
let userId = 'test_userId';
let accountId = 'test_accountId';
angulartics2AppInsights.setUsername(userId);
advance(fixture);
expect(angulartics2.settings.appInsights.userId).toBe(userId);
angulartics2AppInsights.setUserProperties({ accountId });
advance(fixture);
expect(appInsights.setAuthenticatedUserContext).toHaveBeenCalledWith(userId, accountId);
})));

it('should set the start time on start',
fakeAsync(inject([Location, Angulartics2, Angulartics2AppInsights],
(location: Location, angulartics2: Angulartics2, angulartics2AppInsights: Angulartics2AppInsights) => {
angulartics2AppInsights.startTimer();
expect(angulartics2AppInsights.loadStartTime).toBe(Date.now());
expect(angulartics2AppInsights.loadTime).toBe(null);
})));

it('should set the total time on stop',
fakeAsync(inject([Location, Angulartics2, Angulartics2AppInsights],
(location: Location, angulartics2: Angulartics2, angulartics2AppInsights: Angulartics2AppInsights) => {
angulartics2AppInsights.loadStartTime = Date.now() - 1000;
angulartics2AppInsights.stopTimer();
// 50ms time difference for testing to ensure timing is correct
expect(angulartics2AppInsights.loadTime).toBeLessThanOrEqual(1150);
expect(angulartics2AppInsights.loadTime).toBeGreaterThanOrEqual(1000);
expect(angulartics2AppInsights.loadStartTime).toBe(null);
})));
});
132 changes: 132 additions & 0 deletions src/providers/appinsights/angulartics2-appinsights.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { Injectable } from '@angular/core';

import { Angulartics2 } from '../../core/angulartics2';

declare const appInsights: Microsoft.ApplicationInsights.IAppInsights;

import { Title } from '@angular/platform-browser';
import { Router, NavigationEnd, NavigationStart, NavigationError } from '@angular/router';

@Injectable()
export class Angulartics2AppInsights {
loadStartTime: number = null;
loadTime: number = null;

metrics: any = null;
dimensions: any = null;
measurements: any = null;

constructor(private angulartics2: Angulartics2,
private title: Title,
private router: Router) {
if (typeof (appInsights) === 'undefined') {
console.warn('appInsights not found');
}

this.angulartics2.settings.appInsights = {
userId: null
};

this.angulartics2.pageTrack.subscribe((x: any) => this.pageTrack(x.path));

this.angulartics2.eventTrack.subscribe((x: any) => this.eventTrack(x.action, x.properties));

this.angulartics2.exceptionTrack.subscribe((x: any) => this.exceptionTrack(x));

this.angulartics2.setUsername.subscribe((x: string) => this.setUsername(x));

this.angulartics2.setUserProperties.subscribe((x: any) => this.setUserProperties(x));

this.router.events
.filter(event => event instanceof NavigationStart)
.subscribe(event => this.startTimer());

this.router.events
.filter(event =>
(event instanceof NavigationError) ||
(event instanceof NavigationEnd)
)
.subscribe(error => this.stopTimer());
}

startTimer() {
this.loadStartTime = Date.now();
this.loadTime = null;
}

stopTimer() {
this.loadTime = Date.now() - this.loadStartTime;
this.loadStartTime = null;
}

/**
* Page Track in Baidu Analytics
* @name pageTrack
*
* @param {string} path Required 'path' (string)
*
* @link https://github.com/Microsoft/ApplicationInsights-JS/blob/master/API-reference.md#trackpageview
*
*/
pageTrack(path: string) {
appInsights.trackPageView(
this.title.getTitle(),
path,
this.dimensions,
this.metrics,
this.loadTime
);
}

/**
* Log a user action or other occurrence.
* @param name A string to identify this event in the portal.
*
* @param properties map[string, string] - additional data used to filter events and metrics in the portal. Defaults to empty.
*
* @https://github.com/Microsoft/ApplicationInsights-JS/blob/master/API-reference.md#trackevent
*/
eventTrack(name: string, properties: any) {
appInsights.trackEvent(name, properties, this.measurements);
}

/**
* Exception Track Event in GA
* @name exceptionTrack
*
* @param {any} properties Comprised of the mandatory fields 'appId' (string), 'appName' (string) and 'appVersion' (string) and
* optional fields 'fatal' (boolean) and 'description' (string), error
*
* @link https://github.com/Microsoft/ApplicationInsights-JS/blob/master/API-reference.md#trackexception
*/
exceptionTrack(properties: any) {
let description = properties.event || properties.description || properties;

appInsights.trackException(description);
}

/**
*
* @param userId
*
* @link https://github.com/Microsoft/ApplicationInsights-JS/blob/master/API-reference.md#setauthenticatedusercontext
*/

setUsername(userId: string) {
this.angulartics2.settings.appInsights.userId = userId;
appInsights.setAuthenticatedUserContext(userId);
}

setUserProperties(properties: any) {
if (properties.userId) {
this.angulartics2.settings.appInsights.userId = properties.userId;
}

if (properties.accountId) {
appInsights.setAuthenticatedUserContext(
this.angulartics2.settings.appInsights.userId,
properties.accountId
);
}
}
}
1 change: 1 addition & 0 deletions src/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * from './mixpanel/angulartics2-mixpanel';
export * from './piwik/angulartics2-piwik';
export * from './segment/angulartics2-segment';
export * from './facebook/angulartics2-facebook';
export * from './appinsights/angulartics2-appinsights';
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"moduleResolution": "node",
"types": [
"jasmine",
"node"
"node",
"applicationinsights-js"
],
"lib": ["es2015", "dom"],
"outDir": "dist"
Expand Down