Skip to content

Commit 15d7aea

Browse files
hanslvikerman
authored andcommitted
feat(@angular-devkit/core): analytics interfaces and basic implementations
This is a preliminary analytics interface. It is meant to be used by every other subsystem where useful, but can be replaced by implementations.
1 parent 7eb4bab commit 15d7aea

File tree

7 files changed

+288
-0
lines changed

7 files changed

+288
-0
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
export interface CustomDimensionsAndMetricsOptions {
9+
dimensions?: (boolean | number | string)[];
10+
metrics?: (boolean | number | string)[];
11+
}
12+
13+
export interface EventOptions extends CustomDimensionsAndMetricsOptions {
14+
label?: string;
15+
value?: string;
16+
}
17+
18+
export interface ScreenviewOptions extends CustomDimensionsAndMetricsOptions {
19+
appVersion?: string;
20+
appId?: string;
21+
appInstallerId?: string;
22+
}
23+
24+
export interface PageviewOptions extends CustomDimensionsAndMetricsOptions {
25+
hostname?: string;
26+
title?: string;
27+
}
28+
29+
export interface TimingOptions extends CustomDimensionsAndMetricsOptions {
30+
label?: string;
31+
}
32+
33+
/**
34+
* Interface for managing analytics. This is highly platform dependent, and mostly matches
35+
* Google Analytics. The reason the interface is here is to remove the dependency to an
36+
* implementation from most other places.
37+
*
38+
* The methods exported from this interface more or less match those needed by us in the
39+
* universal analytics package, see https://unpkg.com/@types/universal-analytics@0.4.2/index.d.ts
40+
* for typings. We mostly named arguments to make it easier to follow, but didn't change or
41+
* add any semantics to those methods. They're mapping GA and u-a one for one.
42+
*
43+
* The Angular CLI (or any other kind of backend) should forward it to some compatible backend.
44+
*/
45+
export interface Analytics {
46+
event(category: string, action: string, options?: EventOptions): void;
47+
screenview(screenName: string, appName: string, options?: ScreenviewOptions): void;
48+
pageview(path: string, options?: PageviewOptions): void;
49+
timing(category: string, variable: string, time: string | number, options?: TimingOptions): void;
50+
51+
flush(): Promise<void>;
52+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import { JsonObject } from '../json';
9+
import { Analytics, EventOptions, PageviewOptions, ScreenviewOptions, TimingOptions } from './api';
10+
11+
12+
export enum AnalyticsReportKind {
13+
Event = 'event',
14+
Screenview = 'screenview',
15+
Pageview = 'pageview',
16+
Timing = 'timing',
17+
}
18+
19+
export interface AnalyticsReportBase extends JsonObject {
20+
kind: AnalyticsReportKind;
21+
}
22+
23+
export interface AnalyticsReportEvent extends AnalyticsReportBase {
24+
kind: AnalyticsReportKind.Event;
25+
options: JsonObject & EventOptions;
26+
category: string;
27+
action: string;
28+
}
29+
export interface AnalyticsReportScreenview extends AnalyticsReportBase {
30+
kind: AnalyticsReportKind.Screenview;
31+
options: JsonObject & ScreenviewOptions;
32+
screenName: string;
33+
appName: string;
34+
}
35+
export interface AnalyticsReportPageview extends AnalyticsReportBase {
36+
kind: AnalyticsReportKind.Pageview;
37+
options: JsonObject & PageviewOptions;
38+
path: string;
39+
}
40+
export interface AnalyticsReportTiming extends AnalyticsReportBase {
41+
kind: AnalyticsReportKind.Timing;
42+
options: JsonObject & TimingOptions;
43+
category: string;
44+
variable: string;
45+
time: string | number;
46+
}
47+
48+
export type AnalyticsReport =
49+
AnalyticsReportEvent
50+
| AnalyticsReportScreenview
51+
| AnalyticsReportPageview
52+
| AnalyticsReportTiming
53+
;
54+
55+
/**
56+
* A function that can forward analytics along some stream. AnalyticsReport is already a
57+
* JsonObject descendant, but we force it here so the user knows it's safe to serialize.
58+
*/
59+
export type AnalyticsForwarderFn = (report: JsonObject & AnalyticsReport) => void;
60+
61+
/**
62+
* A class that follows the Analytics interface and forwards analytic reports (JavaScript objects).
63+
* AnalyticsReporter is the counterpart which takes analytic reports and report them to another
64+
* Analytics interface.
65+
*/
66+
export class ForwardingAnalytics implements Analytics {
67+
constructor(protected _fn: AnalyticsForwarderFn) {}
68+
69+
event(category: string, action: string, options?: EventOptions) {
70+
this._fn({
71+
kind: AnalyticsReportKind.Event,
72+
category,
73+
action,
74+
options: { ...options } as JsonObject,
75+
});
76+
}
77+
screenview(screenName: string, appName: string, options?: ScreenviewOptions) {
78+
this._fn({
79+
kind: AnalyticsReportKind.Screenview,
80+
screenName,
81+
appName,
82+
options: { ...options } as JsonObject,
83+
});
84+
}
85+
pageview(path: string, options?: PageviewOptions) {
86+
this._fn({
87+
kind: AnalyticsReportKind.Pageview,
88+
path,
89+
options: { ...options } as JsonObject,
90+
});
91+
}
92+
timing(category: string, variable: string, time: string | number, options?: TimingOptions): void {
93+
this._fn({
94+
kind: AnalyticsReportKind.Timing,
95+
category,
96+
variable,
97+
time,
98+
options: { ...options } as JsonObject,
99+
});
100+
}
101+
102+
// We do not support flushing.
103+
flush() {
104+
return Promise.resolve();
105+
}
106+
}
107+
108+
109+
export class AnalyticsReporter {
110+
constructor(protected _analytics: Analytics) {}
111+
112+
report(report: AnalyticsReport) {
113+
switch (report.kind) {
114+
case AnalyticsReportKind.Event:
115+
this._analytics.event(report.category, report.action, report.options);
116+
break;
117+
case AnalyticsReportKind.Screenview:
118+
this._analytics.screenview(report.screenName, report.appName, report.options);
119+
break;
120+
case AnalyticsReportKind.Pageview:
121+
this._analytics.pageview(report.path, report.options);
122+
break;
123+
case AnalyticsReportKind.Timing:
124+
this._analytics.timing(report.category, report.variable, report.time, report.options);
125+
break;
126+
127+
default:
128+
throw new Error('Unexpected analytics report: ' + JSON.stringify(report));
129+
}
130+
}
131+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
export * from './api';
9+
export * from './forwarder';
10+
export * from './logging';
11+
export * from './multi';
12+
export * from './noop';
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import { Logger } from '../logger';
9+
import { Analytics, EventOptions, PageviewOptions, ScreenviewOptions, TimingOptions } from './api';
10+
11+
/**
12+
* Analytics implementation that logs analytics events to a logger. This should be used for
13+
* debugging mainly.
14+
*/
15+
export class LoggingAnalytics implements Analytics {
16+
constructor(protected _logger: Logger) {}
17+
18+
event(category: string, action: string, options?: EventOptions): void {
19+
this._logger.info('event ' + JSON.stringify({ category, action, ...options }));
20+
}
21+
screenview(screenName: string, appName: string, options?: ScreenviewOptions): void {
22+
this._logger.info('screenview ' + JSON.stringify({ screenName, appName, ...options }));
23+
}
24+
pageview(path: string, options?: PageviewOptions): void {
25+
this._logger.info('pageview ' + JSON.stringify({ path, ...options }));
26+
}
27+
timing(category: string, variable: string, time: string | number, options?: TimingOptions): void {
28+
this._logger.info('timing ' + JSON.stringify({ category, variable, time, ...options }));
29+
}
30+
31+
flush(): Promise<void> {
32+
return Promise.resolve();
33+
}
34+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import { Analytics, EventOptions, PageviewOptions, ScreenviewOptions, TimingOptions } from './api';
10+
11+
/**
12+
* Analytics implementation that reports to multiple analytics backend.
13+
*/
14+
export class MultiAnalytics implements Analytics {
15+
constructor(protected _backends: Analytics[] = []) {}
16+
17+
push(...backend: Analytics[]) {
18+
this._backends.push(...backend);
19+
}
20+
21+
event(category: string, action: string, options?: EventOptions): void {
22+
this._backends.forEach(be => be.event(category, action, options));
23+
}
24+
screenview(screenName: string, appName: string, options?: ScreenviewOptions): void {
25+
this._backends.forEach(be => be.screenview(screenName, appName, options));
26+
}
27+
pageview(path: string, options?: PageviewOptions): void {
28+
this._backends.forEach(be => be.pageview(path, options));
29+
}
30+
timing(category: string, variable: string, time: string | number, options?: TimingOptions): void {
31+
this._backends.forEach(be => be.timing(category, variable, time, options));
32+
}
33+
34+
35+
flush(): Promise<void> {
36+
return Promise.all(this._backends.map(x => x.flush())).then(() => {});
37+
}
38+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import { Analytics } from './api';
9+
10+
/**
11+
* Analytics implementation that does nothing.
12+
*/
13+
export class NoopAnalytics implements Analytics {
14+
event() {}
15+
screenview() {}
16+
pageview() {}
17+
timing() {}
18+
flush(): Promise<void> { return Promise.resolve(); }
19+
}

packages/angular_devkit/core/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8+
import * as analytics from './analytics';
89
import * as experimental from './experimental';
910
import * as json from './json/index';
1011
import * as logging from './logger/index';
@@ -16,6 +17,7 @@ export * from './utils/index';
1617
export * from './virtual-fs/index';
1718

1819
export {
20+
analytics,
1921
experimental,
2022
json,
2123
logging,

0 commit comments

Comments
 (0)