-
-
Notifications
You must be signed in to change notification settings - Fork 160
/
metrics.js
219 lines (190 loc) · 5.75 KB
/
metrics.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
import Service from '@ember/service';
import { assert } from '@ember/debug';
import { dasherize } from '@ember/string';
import { getOwner } from '@ember/application';
export default class Metrics extends Service {
/**
* Cached adapters to reduce multiple expensive lookups.
*
* @property _adapters
* @private
* @type Object
* @default null
*/
_adapters = {};
/**
* Contextual information attached to each call to an adapter. Often you'll
* want to include things like `currentUser.name` with every event or page
* view that's tracked. Any properties that you bind to `metrics.context`
* will be merged into the options for every service call.
*
* @property context
* @type Object
* @default null
*/
context = {};
/**
* Indicates whether calls to the service will be forwarded to the adapters.
* This is determined by investigating the user's doNotTrack settings.
*
* Note that the doNotTrack specification is deprecated, and could stop
* working at any minute. As such should this feature not be detected we
* presume tracking is permitted.
*
* @property enabled
* @type Boolean
*/
enabled = typeof navigator !== 'undefined' && navigator.doNotTrack !== '1';
/**
* Environment the host application is running in (e.g. development or production).
*/
appEnvironment = null;
/**
* When the Service is created, activate adapters that were specified in the
* configuration. This config is injected into the Service as `options`.
*/
constructor() {
super(...arguments);
const owner = getOwner(this);
owner.registerOptionsForType('ember-metrics@metrics-adapter', {
instantiate: false,
});
owner.registerOptionsForType('metrics-adapter', { instantiate: false });
const config = owner.factoryFor('config:environment').class;
const { metricsAdapters = [] } = config;
const { environment = 'development' } = config;
this._options = { metricsAdapters, environment };
this.appEnvironment = environment;
this.activateAdapters(metricsAdapters);
}
/**
* Instantiates adapters from passed adapter options and caches them for future retrieval.
*
* @method activateAdapters
* @param {Array} adapterOptions
* @return {Object} instantiated adapters
*/
activateAdapters(adapterOptions = []) {
if (!this.enabled) {
return;
}
const adaptersForEnv = this._adaptersForEnv(adapterOptions);
const activeAdapters = {};
for (let { name, config } of adaptersForEnv) {
let adapterClass = this._lookupAdapter(name);
if (typeof FastBoot === 'undefined' || adapterClass.supportsFastBoot) {
activeAdapters[name] =
this._adapters[name] ||
this._activateAdapter({ adapterClass, config });
}
}
this._adapters = activeAdapters;
return this._adapters;
}
/**
* Returns all adapterOptions that should be activated in the current application environment.
* Defaults to all environments if the option is `all` or undefined.
*
* @method adaptersForEnv
* @param {Array} adapterOptions
* @private
* @return {Array} - adapter options in the current environment
*/
_adaptersForEnv(adapterOptions = []) {
return adapterOptions.filter(({ environments = ['all'] }) => {
return (
environments.includes('all') ||
environments.includes(this.appEnvironment)
);
});
}
/**
* Looks up the adapter from the container. Prioritizes the consuming app's
* adapters over the addon's adapters.
*
* @method _lookupAdapter
* @param {String} adapterName
* @private
* @return {Adapter} a local adapter or an adapter from the addon
*/
_lookupAdapter(adapterName) {
assert(
'[ember-metrics] Could not find metrics adapter without a name.',
adapterName
);
const availableAdapter = getOwner(this).lookup(
`ember-metrics@metrics-adapter:${dasherize(adapterName)}`
);
const localAdapter = getOwner(this).lookup(
`metrics-adapter:${dasherize(adapterName)}`
);
const adapter = localAdapter || availableAdapter;
assert(
`[ember-metrics] Could not find metrics adapter ${adapterName}.`,
adapter
);
return adapter;
}
/**
* Instantiates an adapter.
*
* @method _activateAdapter
* @param {Object}
* @private
* @return {Adapter}
*/
_activateAdapter({ adapterClass, config }) {
const adapter = new adapterClass(config);
adapter.install();
return adapter;
}
identify() {
this.invoke('identify', ...arguments);
}
alias() {
this.invoke('alias', ...arguments);
}
trackEvent() {
this.invoke('trackEvent', ...arguments);
}
trackPage() {
this.invoke('trackPage', ...arguments);
}
/**
* Invokes a method on the passed adapter, or across all activated adapters if not passed.
*
* @method invoke
* @param {String} methodName
* @param {Rest} args
* @return {Void}
*/
invoke(methodName, ...args) {
if (!this.enabled) {
return;
}
let selectedAdapterNames, options;
if (args.length > 1) {
selectedAdapterNames = makeArray(args[0]);
options = args[1];
} else {
selectedAdapterNames = Object.keys(this._adapters);
options = args[0];
}
for (let adapterName of selectedAdapterNames) {
let adapter = this._adapters[adapterName];
adapter && adapter[methodName]({ ...this.context, ...options });
}
}
/**
* On teardown, destroy cached adapters together with the Service.
*
* @method willDestroy
* @return {void}
*/
willDestroy() {
Object.values(this._adapters).forEach((adapter) => adapter.uninstall());
}
}
function makeArray(maybeArray) {
return Array.isArray(maybeArray) ? Array.from(maybeArray) : Array(maybeArray);
}