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.
This commit is contained in:
Hans Larsen 2019-03-06 10:16:47 -08:00 committed by vikerman
parent 7eb4bab00f
commit 15d7aea2b7
7 changed files with 288 additions and 0 deletions

View File

@ -0,0 +1,52 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export interface CustomDimensionsAndMetricsOptions {
dimensions?: (boolean | number | string)[];
metrics?: (boolean | number | string)[];
}
export interface EventOptions extends CustomDimensionsAndMetricsOptions {
label?: string;
value?: string;
}
export interface ScreenviewOptions extends CustomDimensionsAndMetricsOptions {
appVersion?: string;
appId?: string;
appInstallerId?: string;
}
export interface PageviewOptions extends CustomDimensionsAndMetricsOptions {
hostname?: string;
title?: string;
}
export interface TimingOptions extends CustomDimensionsAndMetricsOptions {
label?: string;
}
/**
* Interface for managing analytics. This is highly platform dependent, and mostly matches
* Google Analytics. The reason the interface is here is to remove the dependency to an
* implementation from most other places.
*
* The methods exported from this interface more or less match those needed by us in the
* universal analytics package, see https://unpkg.com/@types/universal-analytics@0.4.2/index.d.ts
* for typings. We mostly named arguments to make it easier to follow, but didn't change or
* add any semantics to those methods. They're mapping GA and u-a one for one.
*
* The Angular CLI (or any other kind of backend) should forward it to some compatible backend.
*/
export interface Analytics {
event(category: string, action: string, options?: EventOptions): void;
screenview(screenName: string, appName: string, options?: ScreenviewOptions): void;
pageview(path: string, options?: PageviewOptions): void;
timing(category: string, variable: string, time: string | number, options?: TimingOptions): void;
flush(): Promise<void>;
}

View File

@ -0,0 +1,131 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { JsonObject } from '../json';
import { Analytics, EventOptions, PageviewOptions, ScreenviewOptions, TimingOptions } from './api';
export enum AnalyticsReportKind {
Event = 'event',
Screenview = 'screenview',
Pageview = 'pageview',
Timing = 'timing',
}
export interface AnalyticsReportBase extends JsonObject {
kind: AnalyticsReportKind;
}
export interface AnalyticsReportEvent extends AnalyticsReportBase {
kind: AnalyticsReportKind.Event;
options: JsonObject & EventOptions;
category: string;
action: string;
}
export interface AnalyticsReportScreenview extends AnalyticsReportBase {
kind: AnalyticsReportKind.Screenview;
options: JsonObject & ScreenviewOptions;
screenName: string;
appName: string;
}
export interface AnalyticsReportPageview extends AnalyticsReportBase {
kind: AnalyticsReportKind.Pageview;
options: JsonObject & PageviewOptions;
path: string;
}
export interface AnalyticsReportTiming extends AnalyticsReportBase {
kind: AnalyticsReportKind.Timing;
options: JsonObject & TimingOptions;
category: string;
variable: string;
time: string | number;
}
export type AnalyticsReport =
AnalyticsReportEvent
| AnalyticsReportScreenview
| AnalyticsReportPageview
| AnalyticsReportTiming
;
/**
* A function that can forward analytics along some stream. AnalyticsReport is already a
* JsonObject descendant, but we force it here so the user knows it's safe to serialize.
*/
export type AnalyticsForwarderFn = (report: JsonObject & AnalyticsReport) => void;
/**
* A class that follows the Analytics interface and forwards analytic reports (JavaScript objects).
* AnalyticsReporter is the counterpart which takes analytic reports and report them to another
* Analytics interface.
*/
export class ForwardingAnalytics implements Analytics {
constructor(protected _fn: AnalyticsForwarderFn) {}
event(category: string, action: string, options?: EventOptions) {
this._fn({
kind: AnalyticsReportKind.Event,
category,
action,
options: { ...options } as JsonObject,
});
}
screenview(screenName: string, appName: string, options?: ScreenviewOptions) {
this._fn({
kind: AnalyticsReportKind.Screenview,
screenName,
appName,
options: { ...options } as JsonObject,
});
}
pageview(path: string, options?: PageviewOptions) {
this._fn({
kind: AnalyticsReportKind.Pageview,
path,
options: { ...options } as JsonObject,
});
}
timing(category: string, variable: string, time: string | number, options?: TimingOptions): void {
this._fn({
kind: AnalyticsReportKind.Timing,
category,
variable,
time,
options: { ...options } as JsonObject,
});
}
// We do not support flushing.
flush() {
return Promise.resolve();
}
}
export class AnalyticsReporter {
constructor(protected _analytics: Analytics) {}
report(report: AnalyticsReport) {
switch (report.kind) {
case AnalyticsReportKind.Event:
this._analytics.event(report.category, report.action, report.options);
break;
case AnalyticsReportKind.Screenview:
this._analytics.screenview(report.screenName, report.appName, report.options);
break;
case AnalyticsReportKind.Pageview:
this._analytics.pageview(report.path, report.options);
break;
case AnalyticsReportKind.Timing:
this._analytics.timing(report.category, report.variable, report.time, report.options);
break;
default:
throw new Error('Unexpected analytics report: ' + JSON.stringify(report));
}
}
}

View File

@ -0,0 +1,12 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export * from './api';
export * from './forwarder';
export * from './logging';
export * from './multi';
export * from './noop';

View File

@ -0,0 +1,34 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { Logger } from '../logger';
import { Analytics, EventOptions, PageviewOptions, ScreenviewOptions, TimingOptions } from './api';
/**
* Analytics implementation that logs analytics events to a logger. This should be used for
* debugging mainly.
*/
export class LoggingAnalytics implements Analytics {
constructor(protected _logger: Logger) {}
event(category: string, action: string, options?: EventOptions): void {
this._logger.info('event ' + JSON.stringify({ category, action, ...options }));
}
screenview(screenName: string, appName: string, options?: ScreenviewOptions): void {
this._logger.info('screenview ' + JSON.stringify({ screenName, appName, ...options }));
}
pageview(path: string, options?: PageviewOptions): void {
this._logger.info('pageview ' + JSON.stringify({ path, ...options }));
}
timing(category: string, variable: string, time: string | number, options?: TimingOptions): void {
this._logger.info('timing ' + JSON.stringify({ category, variable, time, ...options }));
}
flush(): Promise<void> {
return Promise.resolve();
}
}

View File

@ -0,0 +1,38 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { Analytics, EventOptions, PageviewOptions, ScreenviewOptions, TimingOptions } from './api';
/**
* Analytics implementation that reports to multiple analytics backend.
*/
export class MultiAnalytics implements Analytics {
constructor(protected _backends: Analytics[] = []) {}
push(...backend: Analytics[]) {
this._backends.push(...backend);
}
event(category: string, action: string, options?: EventOptions): void {
this._backends.forEach(be => be.event(category, action, options));
}
screenview(screenName: string, appName: string, options?: ScreenviewOptions): void {
this._backends.forEach(be => be.screenview(screenName, appName, options));
}
pageview(path: string, options?: PageviewOptions): void {
this._backends.forEach(be => be.pageview(path, options));
}
timing(category: string, variable: string, time: string | number, options?: TimingOptions): void {
this._backends.forEach(be => be.timing(category, variable, time, options));
}
flush(): Promise<void> {
return Promise.all(this._backends.map(x => x.flush())).then(() => {});
}
}

View File

@ -0,0 +1,19 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { Analytics } from './api';
/**
* Analytics implementation that does nothing.
*/
export class NoopAnalytics implements Analytics {
event() {}
screenview() {}
pageview() {}
timing() {}
flush(): Promise<void> { return Promise.resolve(); }
}

View File

@ -5,6 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as analytics from './analytics';
import * as experimental from './experimental';
import * as json from './json/index';
import * as logging from './logger/index';
@ -16,6 +17,7 @@ export * from './utils/index';
export * from './virtual-fs/index';
export {
analytics,
experimental,
json,
logging,