UNPKG

9.66 kBJavaScriptView Raw
1"use strict";
2
3var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
5var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
6
7var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
8
9var _temp;
10
11const uuidv4 = require(`uuid/v4`);
12
13const EventStorage = require(`./event-storage`);
14
15const {
16 cleanPaths
17} = require(`./error-helpers`);
18
19const {
20 isCI,
21 getCIName
22} = require(`gatsby-core-utils`);
23
24const os = require(`os`);
25
26const {
27 join,
28 sep
29} = require(`path`);
30
31const isDocker = require(`is-docker`);
32
33const showAnalyticsNotification = require(`./showAnalyticsNotification`);
34
35const lodash = require(`lodash`);
36
37const {
38 getRepositoryId
39} = require(`./repository-id`);
40
41module.exports = (_temp = class AnalyticsTracker {
42 // lazy
43 // lazy
44 constructor() {
45 (0, _defineProperty2.default)(this, "store", new EventStorage());
46 (0, _defineProperty2.default)(this, "debouncer", {});
47 (0, _defineProperty2.default)(this, "metadataCache", {});
48 (0, _defineProperty2.default)(this, "defaultTags", {});
49 (0, _defineProperty2.default)(this, "osInfo", void 0);
50 (0, _defineProperty2.default)(this, "trackingEnabled", void 0);
51 (0, _defineProperty2.default)(this, "componentVersion", void 0);
52 (0, _defineProperty2.default)(this, "sessionId", this.getSessionId());
53
54 try {
55 if (this.store.isTrackingDisabled()) {
56 this.trackingEnabled = false;
57 }
58
59 this.defaultTags = this.getTagsFromEnv(); // These may throw and should be last
60
61 this.componentVersion = require(`../package.json`).version;
62 this.gatsbyCliVersion = this.getGatsbyCliVersion();
63 this.installedGatsbyVersion = this.getGatsbyVersion();
64 } catch (e) {// ignore
65 }
66 } // We might have two instances of this lib loaded, one from globally installed gatsby-cli and one from local gatsby.
67 // Hence we need to use process level globals that are not scoped to this module
68
69
70 getSessionId() {
71 return process.gatsbyTelemetrySessionId || (process.gatsbyTelemetrySessionId = uuidv4());
72 }
73
74 getTagsFromEnv() {
75 if (process.env.GATSBY_TELEMETRY_TAGS) {
76 try {
77 return JSON.parse(process.env.GATSBY_TELEMETRY_TAGS);
78 } catch (_) {// ignore
79 }
80 }
81
82 return {};
83 }
84
85 getGatsbyVersion() {
86 const packageInfo = require(join(process.cwd(), `node_modules`, `gatsby`, `package.json`));
87
88 try {
89 return packageInfo.version;
90 } catch (e) {// ignore
91 }
92
93 return undefined;
94 }
95
96 getGatsbyCliVersion() {
97 try {
98 const jsonfile = join(require.resolve(`gatsby-cli`) // Resolve where current gatsby-cli would be loaded from.
99 .split(sep).slice(0, -2) // drop lib/index.js
100 .join(sep), `package.json`);
101
102 const {
103 version
104 } = require(jsonfile).version;
105
106 return version;
107 } catch (e) {// ignore
108 }
109
110 return undefined;
111 }
112
113 captureEvent(type = ``, tags = {}, opts = {
114 debounce: false
115 }) {
116 if (!this.isTrackingEnabled()) {
117 return;
118 }
119
120 let baseEventType = `CLI_COMMAND`;
121
122 if (Array.isArray(type)) {
123 type = type.length > 2 ? type[2].toUpperCase() : ``;
124 baseEventType = `CLI_RAW_COMMAND`;
125 }
126
127 const decoration = this.metadataCache[type];
128 const eventType = `${baseEventType}_${type}`;
129
130 if (opts.debounce) {
131 const debounceTime = 5 * 1000;
132 const now = Date.now();
133 const debounceKey = JSON.stringify({
134 type,
135 decoration,
136 tags
137 });
138 const last = this.debouncer[debounceKey] || 0;
139
140 if (now - last < debounceTime) {
141 return;
142 }
143
144 this.debouncer[debounceKey] = now;
145 }
146
147 delete this.metadataCache[type];
148 this.buildAndStoreEvent(eventType, lodash.merge({}, tags, decoration));
149 }
150
151 captureError(type, tags = {}) {
152 if (!this.isTrackingEnabled()) {
153 return;
154 }
155
156 const decoration = this.metadataCache[type];
157 delete this.metadataCache[type];
158 const eventType = `CLI_ERROR_${type}`;
159 this.formatErrorAndStoreEvent(eventType, lodash.merge({}, tags, decoration));
160 }
161
162 captureBuildError(type, tags = {}) {
163 if (!this.isTrackingEnabled()) {
164 return;
165 }
166
167 const decoration = this.metadataCache[type];
168 delete this.metadataCache[type];
169 const eventType = `BUILD_ERROR_${type}`;
170 this.formatErrorAndStoreEvent(eventType, lodash.merge({}, tags, decoration));
171 }
172
173 formatErrorAndStoreEvent(eventType, tags) {
174 if (tags.error) {
175 var _tags$error, _tags$error2, _tags$error2$error, _tags$error3;
176
177 // `error` ought to have been `errors` but is `error` in the database
178 if (Array.isArray(tags.error)) {
179 const {
180 error
181 } = tags,
182 restOfTags = (0, _objectWithoutPropertiesLoose2.default)(tags, ["error"]);
183 error.forEach(err => {
184 this.formatErrorAndStoreEvent(eventType, Object.assign({
185 error: err
186 }, restOfTags));
187 });
188 return;
189 }
190
191 tags.errorV2 = {
192 // errorCode field was changed from `id` to `code`
193 id: tags.error.code || tags.error.id,
194 text: cleanPaths(tags.error.text),
195 level: tags.error.level,
196 type: (_tags$error = tags.error) === null || _tags$error === void 0 ? void 0 : _tags$error.type,
197 // see if we need empty string or can just use NULL
198 stack: cleanPaths(((_tags$error2 = tags.error) === null || _tags$error2 === void 0 ? void 0 : (_tags$error2$error = _tags$error2.error) === null || _tags$error2$error === void 0 ? void 0 : _tags$error2$error.stack) || ``),
199 context: cleanPaths(JSON.stringify((_tags$error3 = tags.error) === null || _tags$error3 === void 0 ? void 0 : _tags$error3.context))
200 };
201 delete tags.error;
202 }
203
204 this.buildAndStoreEvent(eventType, tags);
205 }
206
207 buildAndStoreEvent(eventType, tags) {
208 const event = Object.assign({
209 installedGatsbyVersion: this.installedGatsbyVersion,
210 gatsbyCliVersion: this.gatsbyCliVersion
211 }, lodash.merge({}, this.defaultTags, tags), {
212 // The schema must include these
213 eventType,
214 sessionId: this.sessionId,
215 time: new Date(),
216 machineId: this.getMachineId(),
217 componentId: `gatsby-cli`,
218 osInformation: this.getOsInfo(),
219 componentVersion: this.componentVersion
220 }, getRepositoryId());
221 this.store.addEvent(event);
222 }
223
224 getMachineId() {
225 // Cache the result
226 if (this.machineId) {
227 return this.machineId;
228 }
229
230 let machineId = this.store.getConfig(`telemetry.machineId`);
231
232 if (!machineId) {
233 machineId = uuidv4();
234 this.store.updateConfig(`telemetry.machineId`, machineId);
235 }
236
237 this.machineId = machineId;
238 return machineId;
239 }
240
241 isTrackingEnabled() {
242 // Cache the result
243 if (this.trackingEnabled !== undefined) {
244 return this.trackingEnabled;
245 }
246
247 let enabled = this.store.getConfig(`telemetry.enabled`);
248
249 if (enabled === undefined || enabled === null) {
250 if (!isCI()) {
251 showAnalyticsNotification();
252 }
253
254 enabled = true;
255 this.store.updateConfig(`telemetry.enabled`, enabled);
256 }
257
258 this.trackingEnabled = enabled;
259 return enabled;
260 }
261
262 getOsInfo() {
263 if (this.osInfo) {
264 return this.osInfo;
265 }
266
267 const cpus = os.cpus();
268 const osInfo = {
269 nodeVersion: process.version,
270 platform: os.platform(),
271 release: os.release(),
272 cpus: cpus && cpus.length > 0 && cpus[0].model || undefined,
273 arch: os.arch(),
274 ci: isCI(),
275 ciName: getCIName(),
276 docker: isDocker()
277 };
278 this.osInfo = osInfo;
279 return osInfo;
280 }
281
282 trackActivity(source) {
283 if (!this.isTrackingEnabled()) {
284 return;
285 } // debounce by sending only the first event whithin a rolling window
286
287
288 const now = Date.now();
289 const last = this.debouncer[source] || 0;
290 const debounceTime = 5 * 1000; // 5 sec
291
292 if (now - last > debounceTime) {
293 this.captureEvent(source);
294 }
295
296 this.debouncer[source] = now;
297 }
298
299 decorateNextEvent(event, obj) {
300 const cached = this.metadataCache[event] || {};
301 this.metadataCache[event] = Object.assign(cached, obj);
302 }
303
304 addSiteMeasurement(event, obj) {
305 const cachedEvent = this.metadataCache[event] || {};
306 const cachedMeasurements = cachedEvent.siteMeasurements || {};
307 this.metadataCache[event] = Object.assign(cachedEvent, {
308 siteMeasurements: Object.assign(cachedMeasurements, obj)
309 });
310 }
311
312 decorateAll(tags) {
313 this.defaultTags = Object.assign(this.defaultTags, tags);
314 }
315
316 setTelemetryEnabled(enabled) {
317 this.trackingEnabled = enabled;
318 this.store.updateConfig(`telemetry.enabled`, enabled);
319 }
320
321 aggregateStats(data) {
322 const sum = data.reduce((acc, x) => acc + x, 0);
323 const mean = sum / data.length || 0;
324 const median = data.sort()[Math.floor((data.length - 1) / 2)] || 0;
325 const stdDev = Math.sqrt(data.reduce((acc, x) => acc + Math.pow(x - mean, 2), 0) / (data.length - 1)) || 0;
326 const skewness = data.reduce((acc, x) => acc + Math.pow(x - mean, 3), 0) / data.length / Math.pow(stdDev, 3);
327 return {
328 count: data.length,
329 min: data.reduce((acc, x) => x < acc ? x : acc, data[0] || 0),
330 max: data.reduce((acc, x) => x > acc ? x : acc, 0),
331 sum: sum,
332 mean: mean,
333 median: median,
334 stdDev: stdDev,
335 skewness: !Number.isNaN(skewness) ? skewness : 0
336 };
337 }
338
339 async sendEvents() {
340 if (!this.isTrackingEnabled()) {
341 return Promise.resolve();
342 }
343
344 return this.store.sendEvents();
345 }
346
347}, _temp);
\No newline at end of file