Skip to content

Commit dc63c93

Browse files
authored
Various telemetry adjustments (aws#4871)
* fix(telemetry): get proper authEnabledConnections for amazonq auth_userState * refactor: circular dependency * feat(telemetry): set source for aws_loginWithBrowser for internal connections Only covers direct connections via command cases. We will have to follow up with additional cases later.
1 parent 2d2d31a commit dc63c93

File tree

10 files changed

+87
-93
lines changed

10 files changed

+87
-93
lines changed

packages/amazonq/src/auth/util.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@
44
*/
55

66
import { AuthUtil } from 'aws-core-vscode/codewhisperer'
7-
import { AuthStatus } from '../../../core/dist/src/shared/telemetry/telemetry'
8-
import { AwsConnection } from 'aws-core-vscode/auth'
7+
import { AuthStatus } from 'aws-core-vscode/telemetry'
8+
import { AwsConnection, Connection, AuthUtils } from 'aws-core-vscode/auth'
99
import { activateExtension, getLogger } from 'aws-core-vscode/shared'
1010
import { VSCODE_EXTENSION_ID } from 'aws-core-vscode/utils'
1111

1212
/** Provides the status of the Auth connection for Amazon Q, specifically for telemetry purposes. */
13-
export async function getAuthStatus(): Promise<AuthStatus> {
13+
export async function getAuthStatus() {
1414
// Get auth state from the Amazon Q extension
1515
const authState = (await AuthUtil.instance.getChatAuthState()).codewhispererChat
16+
let authEnabledConnections = AuthUtils.getAuthFormIdsFromConnection(AuthUtil.instance.conn)
1617
let authStatus: AuthStatus = authState === 'connected' || authState === 'expired' ? authState : 'notConnected'
1718

1819
// If the Q extension does not have its own connection, it will fallback and check
@@ -28,10 +29,14 @@ export async function getAuthStatus(): Promise<AuthStatus> {
2829
// Determine the status of the Toolkit connection we will autoconnect to
2930
if (autoConnectConn) {
3031
authStatus = autoConnectConn.state === 'valid' ? 'connected' : 'expired'
32+
33+
// Though TS won't say it, AwsConnection sufficiently overlaps with Connection for the purposes
34+
// of `getAuthFormIdsFromConnection`
35+
authEnabledConnections = AuthUtils.getAuthFormIdsFromConnection(autoConnectConn as unknown as Connection)
3136
}
3237
}
3338

34-
return authStatus
39+
return { authStatus, authEnabledConnections: authEnabledConnections.join(',') }
3540
}
3641

3742
/**

packages/amazonq/src/extensionShared.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -156,19 +156,10 @@ export async function activateShared(context: vscode.ExtensionContext, isWeb: bo
156156
telemetry.record({ source: ExtStartUpSources.reload })
157157
}
158158

159-
const authKinds: AuthUtils.AuthSimpleId[] = []
160-
if (await AuthUtils.hasBuilderId('codewhisperer')) {
161-
authKinds.push('builderIdCodeWhisperer')
162-
}
163-
if (await AuthUtils.hasSso('codewhisperer')) {
164-
authKinds.push('identityCenterCodeWhisperer')
165-
}
166-
167-
const authStatus = await getAuthStatus()
168-
159+
const { authStatus, authEnabledConnections } = await getAuthStatus()
169160
telemetry.record({
170161
authStatus,
171-
authEnabledConnections: authKinds.join(','),
162+
authEnabledConnections,
172163
})
173164
})
174165
}

packages/core/src/auth/sso/ssoAccessTokenProvider.ts

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import {
2929
isNetworkError,
3030
} from '../../shared/errors'
3131
import { getLogger } from '../../shared/logger'
32-
import { AwsRefreshCredentials, telemetry } from '../../shared/telemetry/telemetry'
32+
import { AwsLoginWithBrowser, AwsRefreshCredentials, Metric, telemetry } from '../../shared/telemetry/telemetry'
3333
import { indent } from '../../shared/utilities/textUtilities'
3434
import { AuthSSOServer } from './server'
3535
import { CancellationError, sleep } from '../../shared/utilities/timeoutUtils'
@@ -84,6 +84,16 @@ const refreshGrantType = 'refresh_token'
8484
* - RefreshToken (optional)
8585
*/
8686
export abstract class SsoAccessTokenProvider {
87+
/**
88+
* Source to pass to aws_loginWithBrowser metric. Due to the complexity of how auth can be called,
89+
* there is no other easy way to pass this in without signficant refactors.
90+
*/
91+
private static _authSource: string = 'unknown'
92+
93+
public static set authSource(val: string) {
94+
SsoAccessTokenProvider._authSource = val
95+
}
96+
8797
public constructor(
8898
protected readonly profile: Pick<SsoProfile, 'startUrl' | 'region' | 'scopes' | 'identifier'>,
8999
protected readonly cache = getCache(),
@@ -195,15 +205,28 @@ export abstract class SsoAccessTokenProvider {
195205
* Wraps the given function with telemetry related to the browser login.
196206
*/
197207
protected withBrowserLoginTelemetry<T extends (...args: any[]) => any>(func: T): ReturnType<T> {
198-
if (telemetry.spans.some(s => s.name === 'aws_loginWithBrowser')) {
199-
// During certain flows, eg reauthentication, we are already running within a span (run())
200-
// so we don't need to create a new one.
208+
const run = (span: Metric<AwsLoginWithBrowser>) => {
209+
span.record({
210+
credentialStartUrl: this.profile.startUrl,
211+
source: SsoAccessTokenProvider._authSource,
212+
})
213+
214+
// Reset source in case there is a case where browser login was called but we forgot to set the source.
215+
// We don't want to attribute the wrong source.
216+
SsoAccessTokenProvider.authSource = 'unknown'
217+
201218
return func()
202219
}
203220

221+
// During certain flows, eg reauthentication, we are already running within a span (run())
222+
// so we don't need to create a new one.
223+
const span = telemetry.spans.find(s => s.name === 'aws_loginWithBrowser')
224+
if (span !== undefined) {
225+
return run(span as unknown as Metric<AwsLoginWithBrowser>)
226+
}
227+
204228
return telemetry.aws_loginWithBrowser.run(span => {
205-
span.record({ credentialStartUrl: this.profile.startUrl })
206-
return func()
229+
return run(span)
207230
})
208231
}
209232

packages/core/src/auth/utils.ts

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,19 @@ import {
3838
isIamConnection,
3939
isValidCodeCatalystConnection,
4040
createSsoProfile,
41+
hasScopes,
42+
scopesSsoAccountAccess,
4143
} from './connection'
4244
import { Commands, placeholder, RegisteredCommand, vscodeComponent } from '../shared/vscode/commands2'
4345
import { Auth } from './auth'
4446
import { validateIsNewSsoUrl, validateSsoUrlFormat } from './sso/validation'
4547
import { getLogger } from '../shared/logger'
46-
import { isValidCodeWhispererCoreConnection } from '../codewhisperer/util/authUtil'
48+
import { isValidAmazonQConnection, isValidCodeWhispererCoreConnection } from '../codewhisperer/util/authUtil'
4749
import { authHelpUrl } from '../shared/constants'
4850
import { getResourceFromTreeNode } from '../shared/treeview/utils'
4951
import { Instance } from '../shared/utilities/typeConstructors'
5052
import { openUrl } from '../shared/utilities/vsCodeUtils'
53+
import { AuthFormId } from './ui/vue/authForms/types'
5154
import { extensionVersion } from '../shared/vscode/env'
5255
import { ExtStartUpSources } from '../shared/telemetry'
5356
import { CommonAuthWebview } from '../login/webview/vue/backend'
@@ -684,19 +687,6 @@ export class ExtensionUse {
684687
}
685688
}
686689

687-
/**
688-
* @deprecated
689-
* Remove in favor of AuthFormId
690-
* Simple readable ID for telemetry reporting
691-
*/
692-
export type AuthSimpleId =
693-
| 'sharedCredentials'
694-
| 'builderIdCodeWhisperer'
695-
| 'builderIdCodeCatalyst'
696-
| 'identityCenterCodeWhisperer'
697-
| 'identityCenterCodeCatalyst'
698-
| 'identityCenterExplorer'
699-
700690
type AuthCommands = {
701691
switchConnections: RegisteredCommand<(auth: Auth | TreeNode | unknown) => Promise<void>>
702692
help: RegisteredCommand<() => Promise<void>>
@@ -832,3 +822,37 @@ export function initAuthCommands(prefix: string) {
832822
}),
833823
}
834824
}
825+
826+
/**
827+
* Returns readable auth Ids for a connection, which are used by telemetry.
828+
*/
829+
export function getAuthFormIdsFromConnection(conn?: Connection): AuthFormId[] {
830+
if (!conn) {
831+
return []
832+
}
833+
834+
const authIds: AuthFormId[] = []
835+
let connType: 'builderId' | 'identityCenter'
836+
837+
if (isIamConnection(conn)) {
838+
return ['credentials']
839+
} else if (isBuilderIdConnection(conn)) {
840+
connType = 'builderId'
841+
} else if (isIdcSsoConnection(conn)) {
842+
connType = 'identityCenter'
843+
if (hasScopes(conn, scopesSsoAccountAccess)) {
844+
authIds.push('identityCenterExplorer')
845+
}
846+
} else {
847+
return ['unknown']
848+
}
849+
850+
if (isValidCodeCatalystConnection(conn)) {
851+
authIds.push(`${connType}CodeCatalyst`)
852+
}
853+
if (isValidAmazonQConnection(conn)) {
854+
authIds.push(`${connType}CodeWhisperer`)
855+
}
856+
857+
return authIds
858+
}

packages/core/src/codewhisperer/commands/basicCommands.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import { focusAmazonQPanel } from '../../codewhispererChat/commands/registerComm
3838
import { removeDiagnostic } from '../service/diagnosticsProvider'
3939
import { SecurityIssueHoverProvider } from '../service/securityIssueHoverProvider'
4040
import { SecurityIssueCodeActionProvider } from '../service/securityIssueCodeActionProvider'
41+
import { SsoAccessTokenProvider } from '../../auth/sso/ssoAccessTokenProvider'
4142

4243
export const toggleCodeSuggestions = Commands.declare(
4344
{ id: 'aws.amazonq.toggleCodeSuggestion', compositeKey: { 1: 'source' } },
@@ -183,23 +184,13 @@ export const connectWithCustomization = Commands.declare(
183184
customizationArn?: string,
184185
customizationNamePrefix?: string
185186
) => {
187+
SsoAccessTokenProvider.authSource = source
186188
if (startUrl && region) {
187189
await connectToEnterpriseSso(startUrl, region)
188190
} else {
189191
await getStartUrl()
190192
}
191193

192-
// This shortcut is unusual, and currently would only be used if another extension
193-
// triggered a connection to Amazon Q. We should still capture and emit the event.
194-
telemetry.auth_addConnection.emit({
195-
source,
196-
isReAuth: false,
197-
credentialStartUrl: startUrl,
198-
region,
199-
authEnabledFeatures: 'codewhisperer',
200-
credentialSourceId: 'iamIdentityCenter',
201-
})
202-
203194
// No customization match information given, exit early.
204195
if (!customizationArn && !customizationNamePrefix) {
205196
return

packages/core/src/codewhisperer/commands/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { ExtStartUpSources } from '../../shared/telemetry'
6+
import { ExtStartUpSources } from '../../shared/telemetry/util'
77
import { CompositeKey, Commands, vscodeComponent } from '../../shared/vscode/commands2'
88

99
/** Indicates a CodeWhisperer command was executed through a tree node */

packages/core/src/extensionShared.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ import { UriHandler } from './shared/vscode/uriHandler'
4242
import { disableAwsSdkWarning } from './shared/awsClientBuilder'
4343
import { FileResourceFetcher } from './shared/resourcefetcher/fileResourceFetcher'
4444
import { ResourceFetcher } from './shared/resourcefetcher/resourcefetcher'
45-
import { ExtStartUpSources, getAuthFormIdsFromConnection } from './shared/telemetry/util'
46-
import { ExtensionUse } from './auth/utils'
45+
import { ExtStartUpSources } from './shared/telemetry/util'
46+
import { ExtensionUse, getAuthFormIdsFromConnection } from './auth/utils'
4747
import { Auth } from './auth'
4848
import { AuthFormId } from './auth/ui/vue/authForms/types'
4949

packages/core/src/login/webview/util.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@ import { vscodeComponent } from '../../shared/vscode/commands2'
1414
*/
1515
export const AuthSources = {
1616
addConnectionQuickPick: 'addConnectionQuickPick',
17-
firstStartup: ExtStartUpSources.firstStartUp,
17+
firstStartUp: ExtStartUpSources.firstStartUp,
1818
codecatalystDeveloperTools: 'codecatalystDeveloperTools',
1919
vscodeComponent: vscodeComponent,
2020
cwQuickPick: cwQuickPickSource,
2121
cwTreeNode: cwTreeNodeSource,
2222
amazonQChat: amazonQChatSource,
23-
authNode: 'authNode',
23+
authNode: 'authNode', // deprecated?
24+
unknown: 'unknown',
2425
} as const
2526

2627
export type AuthSource = (typeof AuthSources)[keyof typeof AuthSources]

packages/core/src/shared/telemetry/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
*/
55

66
export { activate } from './activation'
7-
export { telemetry } from './telemetry'
7+
export { telemetry, AuthStatus } from './telemetry'
88
export { ExtStartUpSources } from './util'

packages/core/src/shared/telemetry/util.ts

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,6 @@ import { isExtensionInstalled, VSCODE_EXTENSION_ID } from '../utilities'
2020
import { randomUUID } from '../../common/crypto'
2121
import { activateExtension } from '../utilities/vsCodeUtils'
2222
import { ClassToInterfaceType } from '../utilities/tsUtils'
23-
import {
24-
Connection,
25-
hasScopes,
26-
isBuilderIdConnection,
27-
isIamConnection,
28-
isIdcSsoConnection,
29-
isValidCodeCatalystConnection,
30-
scopesSsoAccountAccess,
31-
} from '../../auth/connection'
32-
import { AuthFormId } from '../../auth/ui/vue/authForms/types'
33-
import { isValidAmazonQConnection } from '../../codewhisperer/util/authUtil'
3423

3524
const legacySettingsTelemetryValueDisable = 'Disable'
3625
const legacySettingsTelemetryValueEnable = 'Enable'
@@ -268,33 +257,3 @@ export const ExtStartUpSources = {
268257
} as const
269258

270259
export type ExtStartUpSource = (typeof ExtStartUpSources)[keyof typeof ExtStartUpSources]
271-
272-
/**
273-
* Returns readable auth Ids for a connection, which are used by telemetry.
274-
*/
275-
export function getAuthFormIdsFromConnection(conn: Connection): AuthFormId[] {
276-
const authIds: AuthFormId[] = []
277-
let connType: 'builderId' | 'identityCenter'
278-
279-
if (isIamConnection(conn)) {
280-
return ['credentials']
281-
} else if (isBuilderIdConnection(conn)) {
282-
connType = 'builderId'
283-
} else if (isIdcSsoConnection(conn)) {
284-
connType = 'identityCenter'
285-
if (hasScopes(conn, scopesSsoAccountAccess)) {
286-
authIds.push('identityCenterExplorer')
287-
}
288-
} else {
289-
return ['unknown']
290-
}
291-
292-
if (isValidCodeCatalystConnection(conn)) {
293-
authIds.push(`${connType}CodeCatalyst`)
294-
}
295-
if (isValidAmazonQConnection(conn)) {
296-
authIds.push(`${connType}CodeWhisperer`)
297-
}
298-
299-
return authIds
300-
}

0 commit comments

Comments
 (0)