@@ -15,6 +15,7 @@ import (
15
15
"github.com/akitasoftware/akita-cli/rest"
16
16
"github.com/akitasoftware/akita-cli/version"
17
17
"github.com/akitasoftware/akita-libs/analytics"
18
+ "github.com/akitasoftware/go-utils/maps"
18
19
)
19
20
20
21
var (
@@ -27,14 +28,20 @@ var (
27
28
// Client key; set at link-time with -X flag
28
29
defaultAmplitudeKey = ""
29
30
30
- // Store the distinct ID; run through the process
31
+ // Store the user ID and team ID; run through the process
31
32
// of getting it only once.
32
- userDistinctID string
33
- userDistinctOnce sync. Once
33
+ userID string
34
+ teamID string
34
35
35
36
// Timeout talking to API.
36
37
// Shorter than normal because we don't want the CLI to be slow.
37
38
userAPITimeout = 2 * time .Second
39
+
40
+ // Sync.once to ensure that client is initialized before trying to use it.
41
+ initClientOnce sync.Once
42
+
43
+ // Whether to log client init logs to the console
44
+ isLoggingEnabled bool = true
38
45
)
39
46
40
47
type nullClient struct {}
@@ -52,8 +59,14 @@ func (_ nullClient) Close() error {
52
59
}
53
60
54
61
// Initialize the telemetry client.
55
- // This should be called once at startup either from the root command or from a subcommand that overrides the default PersistentPreRun.
56
- func Init (isLoggingEnabled bool ) {
62
+ // This should be called once at startup either from the root command
63
+ // or from a subcommand that overrides the default PersistentPreRun.
64
+ func Init (loggingEnabled bool ) {
65
+ isLoggingEnabled = loggingEnabled
66
+ initClientOnce .Do (doInit )
67
+ }
68
+
69
+ func doInit () {
57
70
// Opt-out mechanism
58
71
disableTelemetry := os .Getenv ("AKITA_DISABLE_TELEMETRY" ) + os .Getenv ("POSTMAN_INSIGHTS_AGENT_DISABLE_TELEMETRY" )
59
72
if disableTelemetry != "" {
@@ -107,39 +120,50 @@ func Init(isLoggingEnabled bool) {
107
120
printer .Infof ("Please send this log message to %s.\n " , consts .SupportEmail )
108
121
}
109
122
analyticsClient = nullClient {}
110
- } else {
111
- analyticsEnabled = true
123
+ return
124
+ }
125
+
126
+ analyticsEnabled = true
127
+
128
+ userID , teamID , err = getUserIdentity () // Initialize user ID and team ID
129
+ if err != nil {
130
+ if isLoggingEnabled {
131
+ printer .Infof ("Telemetry unavailable; error getting userID for given API key: %v\n " , err )
132
+ printer .Infof ("Postman support will not be able to see any errors you encounter.\n " )
133
+ printer .Infof ("Please send this log message to %s.\n " , consts .SupportEmail )
134
+ }
135
+ analyticsClient = nullClient {}
136
+ return
112
137
}
138
+
139
+ // Set up automatic reporting of all API errors
140
+ // (rest can't call telemetry directly because we call rest above!)
141
+ rest .SetAPIErrorHandler (APIError )
113
142
}
114
143
115
- func getDistinctID () string {
116
- // If we have a user email, use that!
144
+ func getUserIdentity () ( string , string , error ) {
145
+ // If we can get user details use userID and teamID
117
146
// Otherwise use the configured API Key.
118
- // Failing that, try to use the user name and host name?
147
+ // Failing that, try to use the user name and host name.
148
+ // In latter 2 cases teamID will be empty.
119
149
120
150
id := os .Getenv ("POSTMAN_ANALYTICS_DISTINCT_ID" )
121
151
if id != "" {
122
- return id
152
+ return id , "" , nil
123
153
}
124
154
125
155
// If there's no credentials configured, skip the API call and
126
156
// do not emit a log message.
127
157
// Similarly if telemetry is disabled.
128
158
if cfg .CredentialsPresent () && analyticsEnabled {
129
- // Call the REST API to get the user email associated with the configured
159
+ // Call the REST API to get the postman user associated with the configured
130
160
// API key.
131
161
ctx , cancel := context .WithTimeout (context .Background (), userAPITimeout )
132
162
defer cancel ()
133
163
frontClient := rest .NewFrontClient (rest .Domain , GetClientID ())
134
164
userResponse , err := frontClient .GetUser (ctx )
135
165
if err == nil {
136
- if userResponse .Email != "" {
137
- return userResponse .Email
138
- }
139
-
140
- // Use the user ID if no email is present;
141
- // this should be fixed in the current backend.
142
- return fmt .Sprint (userResponse .ID )
166
+ return fmt .Sprint (userResponse .ID ), fmt .Sprint (userResponse .TeamID ), nil
143
167
}
144
168
145
169
printer .Infof ("Telemetry using temporary ID; GetUser API call failed: %v\n " , err )
@@ -148,44 +172,31 @@ func getDistinctID() string {
148
172
}
149
173
150
174
// Try to derive a distinct ID from the credentials, if present, even
151
- // if the /v1/user call failed.
175
+ // if the getUser() call failed.
152
176
keyID := cfg .DistinctIDFromCredentials ()
153
177
if keyID != "" {
154
- return keyID
178
+ return keyID , "" , nil
155
179
}
156
180
157
181
localUser , err := user .Current ()
158
182
if err != nil {
159
- return "unknown"
183
+ return "" , "" , err
160
184
}
161
185
localHost , err := os .Hostname ()
162
186
if err != nil {
163
- return localUser .Username
187
+ return localUser .Username , "" , nil
164
188
}
165
- return localUser .Username + "@" + localHost
166
- }
167
-
168
- func distinctID () string {
169
- userDistinctOnce .Do (func () {
170
- userDistinctID = getDistinctID ()
171
-
172
- // Set up automatic reporting of all API errors
173
- // (rest can't call telemetry directly because we call rest above!)
174
- rest .SetAPIErrorHandler (APIError )
175
-
176
- printer .Debugf ("Using ID %q for telemetry\n " , userDistinctID )
177
- })
178
- return userDistinctID
189
+ return localUser .Username + "@" + localHost , "" , nil
179
190
}
180
191
181
192
// Report an error in a particular operation (inContext), including
182
193
// the text of the error.
183
194
func Error (inContext string , e error ) {
184
- analyticsClient . Track ( distinctID (),
185
- fmt . Sprintf ( "Error in %s" , inContext ) ,
195
+ tryTrackingEvent (
196
+ "Operation - Errored" ,
186
197
map [string ]any {
187
- "error " : e . Error () ,
188
- "type " : "error" ,
198
+ "operation " : inContext ,
199
+ "error " : e . Error () ,
189
200
},
190
201
)
191
202
}
@@ -235,80 +246,80 @@ func RateLimitError(inContext string, e error) {
235
246
rateLimitMap .Store (inContext , newRecord )
236
247
}
237
248
238
- analyticsClient . Track ( distinctID (),
239
- fmt . Sprintf ( "Error in %s" , inContext ) ,
249
+ tryTrackingEvent (
250
+ "Operation - Rate Limited" ,
240
251
map [string ]any {
241
- "error " : e . Error () ,
242
- "type " : "error" ,
243
- "count" : count ,
252
+ "operation " : inContext ,
253
+ "error " : e . Error () ,
254
+ "count" : count ,
244
255
},
245
256
)
246
257
}
247
258
248
259
// Report an error in a particular API, including the text of the error.
249
260
func APIError (method string , path string , e error ) {
250
- analyticsClient . Track ( distinctID (),
251
- "Error calling API " ,
261
+ tryTrackingEvent (
262
+ "API Call - Errored " ,
252
263
map [string ]any {
253
264
"method" : method ,
254
265
"path" : path ,
255
266
"error" : e .Error (),
256
- "type" : "error" ,
257
267
},
258
268
)
259
269
}
260
270
261
271
// Report a failure without a specific error object
262
272
func Failure (message string ) {
263
- analyticsClient . Track ( distinctID (),
264
- fmt . Sprintf ( "Unknown Error: %s" , message ) ,
273
+ tryTrackingEvent (
274
+ "Operation - Errored" ,
265
275
map [string ]any {
266
- "type " : "error" ,
276
+ "error " : message ,
267
277
},
268
278
)
269
279
}
270
280
271
281
// Report success of an operation
272
282
func Success (message string ) {
273
- analyticsClient . Track ( distinctID (),
274
- fmt . Sprintf ( "Success in %s" , message ) ,
283
+ tryTrackingEvent (
284
+ "Operation - Succeeded" ,
275
285
map [string ]any {
276
- "type " : "success" ,
286
+ "operation " : message ,
277
287
},
278
288
)
279
289
}
280
290
281
291
// Report a step in a multi-part workflow.
282
292
func WorkflowStep (workflow string , message string ) {
283
- analyticsClient . Track ( distinctID (),
284
- fmt . Sprintf ( "Executing Step: %s" , message ) ,
293
+ tryTrackingEvent (
294
+ "Workflow Step - Executed" ,
285
295
map [string ]any {
286
- "type " : "workflow" ,
296
+ "step " : message ,
287
297
"workflow" : workflow ,
288
298
},
289
299
)
290
300
}
291
301
292
302
// Report command line flags (before any error checking.)
293
303
func CommandLine (command string , commandLine []string ) {
294
- analyticsClient . Track ( distinctID (),
295
- fmt . Sprintf ( "Executed %s" , command ) ,
304
+ tryTrackingEvent (
305
+ "Command - Executed" ,
296
306
map [string ]any {
307
+ "command" : command ,
297
308
"command_line" : commandLine ,
298
309
},
299
310
)
300
311
}
301
312
302
313
// Report the platform and version of an attempted integration
303
314
func InstallIntegrationVersion (integration , arch , platform , version string ) {
304
- analyticsClient . Track ( distinctID (),
305
- fmt . Sprintf ( "Install %s" , integration ) ,
315
+ tryTrackingEvent (
316
+ "Integration - Installed" ,
306
317
map [string ]any {
318
+ "integration" : integration ,
307
319
"architecture" : arch ,
308
320
"version" : version ,
309
321
"platform" : platform ,
310
- },
311
- )
322
+ })
312
323
}
313
324
314
325
// Flush the telemetry to its endpoint
@@ -321,3 +332,22 @@ func Shutdown() {
321
332
printer .Infof ("Please send the CLI output to %s.\n " , consts .SupportEmail )
322
333
}
323
334
}
335
+
336
+ // Attempts to track an event using the provided event name and properties. It adds the user ID
337
+ // and team ID to the event properties, and then sends the event to the analytics client.
338
+ // If there is an error sending the event, a warning message is printed.
339
+ func tryTrackingEvent (eventName string , eventProperties maps.Map [string , any ]) {
340
+ // precondition: analyticsClient is initialized
341
+ initClientOnce .Do (doInit )
342
+
343
+ eventProperties .Upsert ("user_id" , userID , func (v , newV any ) any { return v })
344
+
345
+ if teamID != "" {
346
+ eventProperties .Upsert ("team_id" , teamID , func (v , newV any ) any { return v })
347
+ }
348
+
349
+ err := analyticsClient .Track (userID , eventName , eventProperties )
350
+ if err != nil {
351
+ printer .Warningf ("Error sending analytics event %q: %v\n " , eventName , err )
352
+ }
353
+ }
0 commit comments