Skip to content

Commit fbc03fb

Browse files
jamosidharthachatterjee
authored andcommitted
chore(telemetry): add ability to inject tags from env and track websocket updates (#14711)
* chore(telemetry) add ability to inject tags from env Add tracking for websocket pushed live data updates * Try to get activePath from the websocket handshake. * Add missing nullcheck * Make the 3rd parameter to be an opts object for named parameter experience and plan ahead for future extension needs * Remove debug print
1 parent f3beda1 commit fbc03fb

File tree

4 files changed

+98
-8
lines changed

4 files changed

+98
-8
lines changed

packages/gatsby-telemetry/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"resolve-cwd": "^2.0.0",
2121
"source-map": "^0.5.7",
2222
"stack-trace": "^0.0.10",
23+
"lodash": "^4.17.10",
2324
"stack-utils": "1.0.2",
2425
"uuid": "3.3.2"
2526
},

packages/gatsby-telemetry/src/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const tick = _ => {
1616
}
1717

1818
module.exports = {
19-
trackCli: (input, tags) => instance.captureEvent(input, tags),
19+
trackCli: (input, tags, opts) => instance.captureEvent(input, tags, opts),
2020
trackError: (input, tags) => instance.captureError(input, tags),
2121
trackBuildError: (input, tags) => instance.captureBuildError(input, tags),
2222
setDefaultTags: tags => instance.decorateAll(tags),

packages/gatsby-telemetry/src/telemetry.js

+32-7
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const { basename, join, sep } = require(`path`)
88
const { execSync } = require(`child_process`)
99
const isDocker = require(`is-docker`)
1010
const showAnalyticsNotification = require(`./showAnalyticsNotification`)
11+
const lodash = require(`lodash`)
1112

1213
module.exports = class AnalyticsTracker {
1314
store = new EventStorage()
@@ -18,16 +19,29 @@ module.exports = class AnalyticsTracker {
1819
trackingEnabled // lazy
1920
componentVersion
2021
sessionId = uuid()
22+
2123
constructor() {
2224
try {
2325
this.componentVersion = require(`../package.json`).version
2426
this.installedGatsbyVersion = this.getGatsbyVersion()
2527
this.gatsbyCliVersion = this.getGatsbyCliVersion()
28+
this.defaultTags = this.getTagsFromEnv()
2629
} catch (e) {
2730
// ignore
2831
}
2932
}
3033

34+
getTagsFromEnv() {
35+
if (process.env.GATSBY_TELEMETRY_TAGS) {
36+
try {
37+
return JSON.parse(process.env.GATSBY_TELEMETRY_TAGS)
38+
} catch (_) {
39+
// ignore
40+
}
41+
}
42+
return {}
43+
}
44+
3145
getGatsbyVersion() {
3246
const packageInfo = require(join(
3347
process.cwd(),
@@ -60,7 +74,7 @@ module.exports = class AnalyticsTracker {
6074
}
6175
return undefined
6276
}
63-
captureEvent(type = ``, tags = {}) {
77+
captureEvent(type = ``, tags = {}, opts = { debounce: false }) {
6478
if (!this.isTrackingEnabled()) {
6579
return
6680
}
@@ -71,9 +85,21 @@ module.exports = class AnalyticsTracker {
7185
}
7286

7387
const decoration = this.metadataCache[type]
74-
delete this.metadataCache[type]
7588
const eventType = `${baseEventType}_${type}`
76-
this.buildAndStoreEvent(eventType, Object.assign(tags, decoration))
89+
90+
if (opts.debounce) {
91+
const debounceTime = 5 * 1000
92+
const now = Date.now()
93+
const debounceKey = JSON.stringify({ type, decoration })
94+
const last = this.debouncer[debounceKey] || 0
95+
if (now - last < debounceTime) {
96+
return
97+
}
98+
this.debouncer[debounceKey] = now
99+
}
100+
101+
delete this.metadataCache[type]
102+
this.buildAndStoreEvent(eventType, lodash.merge({}, tags, decoration))
77103
}
78104

79105
captureError(type, tags = {}) {
@@ -89,7 +115,7 @@ module.exports = class AnalyticsTracker {
89115
tags.error = sanitizeErrors(tags.error)
90116
}
91117

92-
this.buildAndStoreEvent(eventType, Object.assign(tags, decoration))
118+
this.buildAndStoreEvent(eventType, lodash.merge({}, tags, decoration))
93119
}
94120

95121
captureBuildError(type, tags = {}) {
@@ -105,15 +131,14 @@ module.exports = class AnalyticsTracker {
105131
tags.error = sanitizeErrors(tags.error)
106132
}
107133

108-
this.buildAndStoreEvent(eventType, Object.assign(tags, decoration))
134+
this.buildAndStoreEvent(eventType, lodash.merge({}, tags, decoration))
109135
}
110136

111137
buildAndStoreEvent(eventType, tags) {
112138
const event = {
113139
installedGatsbyVersion: this.installedGatsbyVersion,
114140
gatsbyCliVersion: this.gatsbyCliVersion,
115-
...this.defaultTags,
116-
...tags, // The schema must include these
141+
...lodash.merge({}, this.defaultTags, tags), // The schema must include these
117142
eventType,
118143
sessionId: this.sessionId,
119144
time: new Date(),

packages/gatsby/src/utils/websocket-manager.js

+64
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ const path = require(`path`)
44
const { store } = require(`../redux`)
55
const fs = require(`fs`)
66
const pageDataUtil = require(`../utils/page-data`)
7+
const telemetry = require(`gatsby-telemetry`)
8+
const url = require(`url`)
9+
const { createHash } = require(`crypto`)
710

811
type QueryResult = {
912
id: string,
@@ -37,6 +40,20 @@ const getCachedPageData = async (
3740
}
3841
}
3942

43+
const hashPaths = paths => {
44+
if (!paths) {
45+
return undefined
46+
}
47+
return paths.map(path => {
48+
if (!path) {
49+
return undefined
50+
}
51+
return createHash(`sha256`)
52+
.update(path)
53+
.digest(`hex`)
54+
})
55+
}
56+
4057
/**
4158
* Get cached StaticQuery results for components that Gatsby didn't run query yet.
4259
* @param {QueryResultsMap} resultsMap Already stored results for queries that don't need to be read from files.
@@ -99,6 +116,7 @@ class WebsocketManager {
99116
this.emitPageData = this.emitPageData.bind(this)
100117
this.emitStaticQueryData = this.emitStaticQueryData.bind(this)
101118
this.emitError = this.emitError.bind(this)
119+
this.connectedClients = 0
102120
}
103121

104122
init({ server, directory }) {
@@ -117,6 +135,20 @@ class WebsocketManager {
117135

118136
this.websocket.on(`connection`, s => {
119137
let activePath = null
138+
if (
139+
s &&
140+
s.handshake &&
141+
s.handshake.headers &&
142+
s.handshake.headers.referer
143+
) {
144+
const path = url.parse(s.handshake.headers.referer).path
145+
if (path) {
146+
activePath = path
147+
this.activePaths.add(path)
148+
}
149+
}
150+
151+
this.connectedClients += 1
120152
// Send already existing static query results
121153
this.staticQueryResults.forEach(result => {
122154
this.websocket.send({
@@ -160,6 +192,17 @@ class WebsocketManager {
160192
why: `getDataForPath`,
161193
payload: this.pageResults.get(path),
162194
})
195+
196+
telemetry.trackCli(
197+
`WEBSOCKET_PAGE_DATA_UPDATE`,
198+
{
199+
siteMeasurements: {
200+
clientsCount: this.connectedClients,
201+
paths: hashPaths(Array.from(this.activePaths)),
202+
},
203+
},
204+
{ debounce: true }
205+
)
163206
}
164207

165208
s.on(`getDataForPath`, getDataForPath)
@@ -172,6 +215,7 @@ class WebsocketManager {
172215

173216
s.on(`disconnect`, s => {
174217
leaveRoom(activePath)
218+
this.connectedClients -= 1
175219
})
176220

177221
s.on(`unregisterPath`, path => {
@@ -190,13 +234,33 @@ class WebsocketManager {
190234
this.staticQueryResults.set(data.id, data)
191235
if (this.isInitialised) {
192236
this.websocket.send({ type: `staticQueryResult`, payload: data })
237+
telemetry.trackCli(
238+
`WEBSOCKET_EMIT_STATIC_PAGE_DATA_UPDATE`,
239+
{
240+
siteMeasurements: {
241+
clientsCount: this.connectedClients,
242+
paths: hashPaths(Array.from(this.activePaths)),
243+
},
244+
},
245+
{ debounce: true }
246+
)
193247
}
194248
}
195249

196250
emitPageData(data: QueryResult) {
197251
this.pageResults.set(data.id, data)
198252
if (this.isInitialised) {
199253
this.websocket.send({ type: `pageQueryResult`, payload: data })
254+
telemetry.trackCli(
255+
`WEBSOCKET_EMIT_PAGE_DATA_UPDATE`,
256+
{
257+
siteMeasurements: {
258+
clientsCount: this.connectedClients,
259+
paths: hashPaths(Array.from(this.activePaths)),
260+
},
261+
},
262+
{ debounce: true }
263+
)
200264
}
201265
}
202266
emitError(id: string, message?: string) {

0 commit comments

Comments
 (0)