Skip to content

Commit dd93e63

Browse files
authored
Q standalone telemetry events (#4399)
1 parent 80ab35c commit dd93e63

File tree

11 files changed

+390
-9
lines changed

11 files changed

+390
-9
lines changed

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QLoginWebview.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import software.aws.toolkits.jetbrains.services.amazonq.util.createBrowser
4040
import software.aws.toolkits.jetbrains.utils.isQConnected
4141
import software.aws.toolkits.jetbrains.utils.isQExpired
4242
import software.aws.toolkits.telemetry.FeatureId
43+
import software.aws.toolkits.telemetry.WebviewTelemetry
4344
import java.awt.event.ActionListener
4445
import java.util.function.Function
4546
import javax.swing.JButton
@@ -113,6 +114,10 @@ class QWebviewBrowser(val project: Project, private val parentDisposable: Dispos
113114
when (command) {
114115
"prepareUi" -> {
115116
this.prepareBrowser(BrowserState(FeatureId.Q, false))
117+
WebviewTelemetry.amazonqSignInOpened(
118+
project,
119+
reAuth = isQExpired(project)
120+
)
116121
}
117122

118123
"selectConnection" -> {

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/auth/AuthController.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,12 @@ class AuthController {
3737

3838
private fun getAuthNeededState(
3939
amazonqConnectionState: ActiveConnection,
40-
codeWhispereConnectionState: ActiveConnection,
40+
codeWhispererConnectionState: ActiveConnection,
4141
onlyIamIdcConnection: Boolean = false
4242
): AuthNeededState? =
4343
when (amazonqConnectionState) {
4444
ActiveConnection.NotConnected -> {
45-
if (codeWhispereConnectionState == ActiveConnection.NotConnected) {
45+
if (codeWhispererConnectionState == ActiveConnection.NotConnected) {
4646
AuthNeededState(
4747
message = message("q.connection.disconnected"),
4848
authType = AuthFollowUpType.FullAuth,

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/startup/AmazonQStartupActivity.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@ import com.intellij.openapi.application.runInEdt
77
import com.intellij.openapi.project.Project
88
import com.intellij.openapi.startup.ProjectActivity
99
import com.intellij.openapi.wm.ToolWindowManager
10+
import software.aws.toolkits.jetbrains.core.gettingstarted.emitUserState
1011
import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindow
1112
import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindowFactory
1213
import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager
14+
import java.util.concurrent.atomic.AtomicBoolean
1315

1416
class AmazonQStartupActivity : ProjectActivity {
17+
private val runOnce = AtomicBoolean(false)
18+
1519
override suspend fun execute(project: Project) {
1620
// initialize html contents in BGT so users don't have to wait when they open the tool window
1721
AmazonQToolWindow.getInstance(project)
@@ -23,5 +27,9 @@ class AmazonQStartupActivity : ProjectActivity {
2327
CodeWhispererExplorerActionManager.getInstance().setIsFirstRestartAfterQInstall(false)
2428
}
2529
}
30+
31+
if (runOnce.get()) return
32+
emitUserState(project)
33+
runOnce.set(true)
2634
}
2735
}

plugins/core/jetbrains-community/resources/telemetryOverride.json

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,54 @@
222222
"q",
223223
"codewhispererQ"
224224
]
225+
},
226+
{
227+
"name": "authStatus",
228+
"type": "string",
229+
"allowedValues": ["connected", "notConnected", "expired"],
230+
"description": "Status of the an auth connection."
231+
},
232+
{
233+
"name": "authEnabledFeatures",
234+
"type": "string",
235+
"description": "Comma-delimited list of features for which auth is enabled."
236+
},
237+
{
238+
"name": "authEnabledConnections",
239+
"type": "string",
240+
"description": "Comma-delimited list of enabled auths."
241+
},
242+
{
243+
"name": "component",
244+
"allowedValues": [
245+
"editor",
246+
"viewer",
247+
"filesystem",
248+
"explorer",
249+
"infobar",
250+
"hover",
251+
"webview",
252+
"quickfix",
253+
"Manage Extensions",
254+
"Got It",
255+
"Read More",
256+
"Install",
257+
"Dismiss"
258+
],
259+
"description": "The IDE or OS component used for the action. (Examples: S3 download to filesystem, S3 upload from editor, ...)"
260+
},
261+
{
262+
"name": "reAuth",
263+
"type": "boolean",
264+
"description": "Connection requires re-authentication."
265+
},
266+
{
267+
"name": "startUpState",
268+
"allowedValues": [
269+
"firstStartUp",
270+
"reloaded"
271+
],
272+
"description": "Toolkit run state."
225273
}
226274
],
227275
"metrics": [
@@ -558,6 +606,109 @@
558606
"required": false
559607
}
560608
]
609+
},
610+
{
611+
"name": "auth_userState",
612+
"description": "The state of the user's authentication.",
613+
"metadata": [
614+
{
615+
"type": "source",
616+
"required": true
617+
},
618+
{
619+
"type": "authStatus",
620+
"required": true
621+
},
622+
{
623+
"type": "authEnabledConnections",
624+
"required": true
625+
}
626+
],
627+
"passive": true
628+
},
629+
{
630+
"name": "webview_amazonqSignInOpened",
631+
"description": "Called when a Amazon Q sign in webview is opened.",
632+
"metadata": [
633+
{
634+
"type": "reAuth",
635+
"required": true
636+
}
637+
],
638+
"passive": true
639+
},
640+
{
641+
"name": "aws_loginWithBrowser",
642+
"description": "Called when a connection requires login using the browser",
643+
"metadata": [
644+
{
645+
"type": "credentialSourceId",
646+
"required": false
647+
},
648+
{
649+
"type": "credentialStartUrl",
650+
"required": false
651+
},
652+
{
653+
"type": "credentialType",
654+
"required": false
655+
},
656+
{
657+
"type": "isReAuth",
658+
"required": false
659+
},
660+
{
661+
"type": "reason",
662+
"required": false
663+
},
664+
{
665+
"type": "result"
666+
}
667+
]
668+
},
669+
{
670+
"name": "auth_addConnection",
671+
"description": "Captures the result of adding a new connection in the 'Add New Connection' workflow",
672+
"metadata": [
673+
{
674+
"type": "attempts",
675+
"required": false
676+
},
677+
{
678+
"type": "credentialSourceId"
679+
},
680+
{
681+
"type": "featureId",
682+
"required": false
683+
},
684+
{
685+
"type": "invalidInputFields",
686+
"required": false
687+
},
688+
{
689+
"type": "isAggregated",
690+
"required": false
691+
},
692+
{
693+
"type": "reason",
694+
"required": false
695+
},
696+
{
697+
"type": "result"
698+
},
699+
{
700+
"type": "source",
701+
"required": false
702+
},
703+
{
704+
"type": "credentialStartUrl",
705+
"required": false
706+
},
707+
{
708+
"type": "isReAuth",
709+
"required": false
710+
}
711+
]
561712
}
562713
]
563714
}

plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/LoginUtils.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ import software.aws.toolkits.jetbrains.core.credentials.sono.SONO_URL
2525
import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.InteractiveBearerTokenProvider
2626
import software.aws.toolkits.jetbrains.utils.runUnderProgressIfNeeded
2727
import software.aws.toolkits.resources.message
28+
import software.aws.toolkits.telemetry.AuthTelemetry
2829
import software.aws.toolkits.telemetry.CredentialSourceId
30+
import software.aws.toolkits.telemetry.Result
2931
import java.io.IOException
3032

3133
sealed interface Login {
@@ -177,9 +179,24 @@ fun authAndUpdateConfig(
177179
reauthConnectionIfNeeded(project, connection, onPendingToken)
178180
}
179181
} catch (e: Exception) {
182+
val message = ssoErrorMessageFromException(e)
183+
180184
onError(e, profile)
185+
AuthTelemetry.addConnection(
186+
project,
187+
credentialSourceId = CredentialSourceId.SharedCredentials,
188+
credentialStartUrl = updatedProfile.startUrl,
189+
reason = message,
190+
result = Result.Failed
191+
)
181192
return null
182193
}
194+
AuthTelemetry.addConnection(
195+
project,
196+
credentialSourceId = CredentialSourceId.SharedCredentials,
197+
credentialStartUrl = updatedProfile.startUrl,
198+
result = Result.Succeeded
199+
)
183200

184201
configFilesFacade.updateSectionInConfig(
185202
SsoSessionConstants.SSO_SESSION_SECTION_NAME,

plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/webview/LoginBrowser.kt

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,17 @@ import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection
2424
import software.aws.toolkits.jetbrains.core.credentials.Login
2525
import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection
2626
import software.aws.toolkits.jetbrains.core.credentials.reauthConnectionIfNeeded
27+
import software.aws.toolkits.jetbrains.core.credentials.sono.SONO_URL
2728
import software.aws.toolkits.jetbrains.core.credentials.sso.PendingAuthorization
2829
import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.InteractiveBearerTokenProvider
2930
import software.aws.toolkits.jetbrains.core.credentials.ssoErrorMessageFromException
3031
import software.aws.toolkits.jetbrains.utils.pollFor
3132
import software.aws.toolkits.resources.message
33+
import software.aws.toolkits.telemetry.AwsTelemetry
34+
import software.aws.toolkits.telemetry.CredentialSourceId
35+
import software.aws.toolkits.telemetry.CredentialType
3236
import software.aws.toolkits.telemetry.FeatureId
37+
import software.aws.toolkits.telemetry.Result
3338
import java.util.concurrent.Future
3439

3540
data class BrowserState(val feature: FeatureId, val browserCancellable: Boolean = false, val requireReauth: Boolean = false)
@@ -83,36 +88,88 @@ abstract class LoginBrowser(
8388
open fun loginBuilderId(scopes: List<String>) {
8489
val onError: (Exception) -> Unit = { e ->
8590
tryHandleUserCanceledLogin(e)
86-
// TODO: telemetry
91+
AwsTelemetry.loginWithBrowser(
92+
project = null,
93+
credentialStartUrl = SONO_URL,
94+
result = Result.Failed,
95+
reason = e.message,
96+
credentialSourceId = CredentialSourceId.AwsId
97+
)
8798
}
8899
loginWithBackgroundContext {
89100
Login.BuilderId(scopes, onPendingToken, onError).loginBuilderId(project)
90-
// TODO: telemetry
101+
AwsTelemetry.loginWithBrowser(
102+
project = null,
103+
credentialStartUrl = SONO_URL,
104+
result = Result.Succeeded,
105+
credentialSourceId = CredentialSourceId.AwsId
106+
)
91107
}
92108
}
93109

94110
open fun loginIdC(url: String, region: AwsRegion, scopes: List<String>) {
95111
val onError: (Exception, AuthProfile) -> Unit = { e, profile ->
112+
val message = ssoErrorMessageFromException(e)
96113
if (!tryHandleUserCanceledLogin(e)) {
97-
val message = ssoErrorMessageFromException(e)
98114
LOG.error(e) { "Failed to authenticate: message: $message; profile: $profile" }
99115
}
100-
// TODO: telemetry
116+
AwsTelemetry.loginWithBrowser(
117+
project = null,
118+
credentialStartUrl = url,
119+
result = Result.Failed,
120+
reason = message,
121+
credentialSourceId = CredentialSourceId.IamIdentityCenter
122+
)
101123
}
102124
loginWithBackgroundContext {
103125
Login.IdC(url, region, scopes, onPendingToken, onError).loginIdc(project)
104-
// TODO: telemetry
126+
AwsTelemetry.loginWithBrowser(
127+
project = null,
128+
credentialStartUrl = url,
129+
result = Result.Succeeded,
130+
credentialSourceId = CredentialSourceId.IamIdentityCenter
131+
)
105132
}
106133
}
107134

108135
open fun loginIAM(profileName: String, accessKey: String, secretKey: String) {
109-
// TODO: telemetry, callbacks
110136
runInEdt {
111137
Login.LongLivedIAM(
112138
profileName,
113139
accessKey,
114140
secretKey
115-
).loginIAM(project, {}, {}, {})
141+
).loginIAM(
142+
project,
143+
{ error ->
144+
AwsTelemetry.loginWithBrowser(
145+
project = null,
146+
result = Result.Failed,
147+
reason = error.message,
148+
credentialType = CredentialType.StaticProfile
149+
)
150+
},
151+
{
152+
AwsTelemetry.loginWithBrowser(
153+
project = null,
154+
result = Result.Failed,
155+
reason = "Profile already exists",
156+
credentialType = CredentialType.StaticProfile
157+
)
158+
},
159+
{
160+
AwsTelemetry.loginWithBrowser(
161+
project = null,
162+
result = Result.Failed,
163+
reason = "Connection validation error",
164+
credentialType = CredentialType.StaticProfile
165+
)
166+
}
167+
)
168+
AwsTelemetry.loginWithBrowser(
169+
project = null,
170+
result = Result.Succeeded,
171+
credentialType = CredentialType.StaticProfile
172+
)
116173
}
117174
}
118175

@@ -137,6 +194,13 @@ abstract class LoginBrowser(
137194
if (connection is AwsBearerTokenConnection) {
138195
loginWithBackgroundContext {
139196
reauthConnectionIfNeeded(project, connection, onPendingToken)
197+
AwsTelemetry.loginWithBrowser(
198+
project = null,
199+
isReAuth = true,
200+
result = Result.Succeeded,
201+
credentialStartUrl = connection.startUrl,
202+
credentialType = CredentialType.BearerToken
203+
)
140204
}
141205
}
142206
}

0 commit comments

Comments
 (0)