Skip to content

Commit b899e82

Browse files
authored
feat(amazonq): initial standalone telemetry (aws#4719)
Implemented telemetry: -auth_userState -Describes the state of auth after activation. - toolkit_showNotification -The user was shown a notification about Amazon Q being auto-installed or about the extension and if they'd like to install it. - toolkit_invokeAction -The user installed, learned more, or dismissed the previously mentioned notification. - ui_click -amazonq_switchToQSignIn - sources on various codewhisperer commands Other stuff: - Collapse amazon q commands into one set (registered from amazon q) - ExtensionUse.wasUpdated util func. Follow up items: Moving the definitions to common is a prioritized work item and will occur after this PR Login page telemetry commits: * add initial telemetry definitions * add notification telemetry * add telemetry for opening login page * collapse switch to Q commands * fix close page telemetry, docstring, export telemetry from core * add wasUpdated util function * add auth_userState metric to amazonq * update toolkit_invokeAction emissions * reloaded -> reload * add start up source telemetry enum * rename dismiss command, remove from package.json * add various fixes for existing telemtry * fix tests * resolve feedback - ExtStartUpSources enum -> const - fix compositeKey arg for _aws.toolkit.amazonq.dismiss
1 parent 724affe commit b899e82

File tree

22 files changed

+324
-121
lines changed

22 files changed

+324
-121
lines changed

packages/amazonq/src/extensionShared.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
shutdown as codewhispererShutdown,
1313
amazonQDismissedKey,
1414
refreshToolkitQState,
15+
AuthUtil,
1516
} from 'aws-core-vscode/codewhisperer'
1617
import {
1718
ExtContext,
@@ -25,13 +26,14 @@ import {
2526
globals,
2627
RegionProvider,
2728
} from 'aws-core-vscode/shared'
28-
import { initializeAuth, CredentialsStore, LoginManager, ExtensionUse } from 'aws-core-vscode/auth'
29+
import { initializeAuth, CredentialsStore, LoginManager, AuthUtils } from 'aws-core-vscode/auth'
2930
import { makeEndpointsProvider, registerCommands } from 'aws-core-vscode'
3031
import { activate as activateCWChat } from 'aws-core-vscode/amazonq'
3132
import { activate as activateQGumby } from 'aws-core-vscode/amazonqGumby'
3233
import { CommonAuthViewProvider } from 'aws-core-vscode/login'
3334
import { isExtensionActive, VSCODE_EXTENSION_ID } from 'aws-core-vscode/utils'
3435
import { registerSubmitFeedback } from 'aws-core-vscode/feedback'
36+
import { telemetry, ExtStartUpSources } from 'aws-core-vscode/telemetry'
3537

3638
export async function activateShared(context: vscode.ExtensionContext) {
3739
const contextPrefix = 'amazonq'
@@ -121,9 +123,37 @@ export async function activateShared(context: vscode.ExtensionContext) {
121123
// enable auto suggestions on activation
122124
await CodeSuggestionsState.instance.setSuggestionsEnabled(true)
123125

124-
if (ExtensionUse.instance.isFirstUse()) {
126+
if (AuthUtils.ExtensionUse.instance.isFirstUse()) {
125127
await vscode.commands.executeCommand('workbench.view.extension.amazonq')
126128
}
129+
130+
await telemetry.auth_userState.run(async () => {
131+
telemetry.record({ passive: true })
132+
133+
const firstUse = AuthUtils.ExtensionUse.instance.isFirstUse()
134+
const wasUpdated = AuthUtils.ExtensionUse.instance.wasUpdated()
135+
136+
if (firstUse) {
137+
telemetry.record({ source: ExtStartUpSources.firstStartUp })
138+
} else if (wasUpdated) {
139+
telemetry.record({ source: ExtStartUpSources.update })
140+
} else {
141+
telemetry.record({ source: ExtStartUpSources.reload })
142+
}
143+
144+
const authState = (await AuthUtil.instance.getChatAuthState()).codewhispererChat
145+
const authKinds: AuthUtils.AuthSimpleId[] = []
146+
if (await AuthUtils.hasBuilderId('codewhisperer')) {
147+
authKinds.push('builderIdCodeWhisperer')
148+
}
149+
if (await AuthUtils.hasSso('codewhisperer')) {
150+
authKinds.push('identityCenterCodeWhisperer')
151+
}
152+
telemetry.record({
153+
authStatus: authState === 'connected' || authState === 'expired' ? authState : 'notConnected',
154+
enabledAuthConnections: authKinds.join(','),
155+
})
156+
})
127157
}
128158

129159
export async function deactivateShared() {

packages/core/package.json

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@
3636
"./amazonqGumby": "./dist/src/amazonqGumby/index.js",
3737
"./login": "./dist/src/login/webview/index.js",
3838
"./utils": "./dist/src/shared/utilities/index.js",
39-
"./feedback": "./dist/src/feedback/index.js"
39+
"./feedback": "./dist/src/feedback/index.js",
40+
"./telemetry": "./dist/src/shared/telemetry/index.js"
4041
},
4142
"contributes": {
4243
"configuration": {
@@ -1176,10 +1177,6 @@
11761177
{
11771178
"command": "aws.toolkit.amazonq.extensionpage",
11781179
"when": "false"
1179-
},
1180-
{
1181-
"command": "aws.toolkit.amazonq.dismiss",
1182-
"when": "false"
11831180
}
11841181
],
11851182
"editor/title": [
@@ -3459,11 +3456,6 @@
34593456
"title": "Open Amazon Q Extension",
34603457
"category": "%AWS.title%"
34613458
},
3462-
{
3463-
"command": "aws.toolkit.amazonq.dismiss",
3464-
"title": "Dismiss Q Notification",
3465-
"category": "%AWS.title%"
3466-
},
34673459
{
34683460
"command": "aws.dev.openMenu",
34693461
"title": "Open Developer Menu",

packages/core/src/amazonq/auth/controller.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import * as vscode from 'vscode'
76
import { reconnect } from '../../codewhisperer/commands/basicCommands'
87
import { amazonQChatSource } from '../../codewhisperer/commands/types'
98
import { recordTelemetryChatRunCommand } from '../../codewhispererChat/controllers/chat/telemetryHelper'
109
import { placeholder } from '../../shared/vscode/commands2'
10+
import { switchToAmazonQSignInCommand } from '../explorer/commonNodes'
1111
import { AuthFollowUpType } from './model'
1212

1313
export class AuthController {
@@ -27,8 +27,7 @@ export class AuthController {
2727
}
2828

2929
private handleFullAuth() {
30-
void vscode.commands.executeCommand('setContext', 'aws.amazonq.showLoginView', true)
31-
void vscode.commands.executeCommand('aws.amazonq.AmazonCommonAuth.focus')
30+
void switchToAmazonQSignInCommand.execute('amazonQChat')
3231
}
3332

3433
private handleReAuth() {

packages/core/src/amazonq/explorer/amazonQChildrenNodes.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import { VSCODE_EXTENSION_ID } from '../../shared/extensions'
1414
import { globals } from '../../shared'
1515
import { amazonQDismissedKey } from '../../codewhisperer/models/constants'
1616
import { _switchToAmazonQ } from './commonNodes'
17+
import { ExtStartUpSources, telemetry } from '../../shared/telemetry'
18+
import { ExtensionUse } from '../../auth/utils'
1719

1820
const localize = nls.loadMessageBundle()
1921

@@ -25,12 +27,21 @@ export const qExtensionPageCommand = Commands.declare('aws.toolkit.amazonq.exten
2527
void vscode.env.openExternal(vscode.Uri.parse(`vscode:extension/${VSCODE_EXTENSION_ID.amazonq}`))
2628
})
2729

28-
export const dismissQTree = Commands.declare('aws.toolkit.amazonq.dismiss', () => async () => {
29-
await globals.context.globalState.update(amazonQDismissedKey, true)
30-
await vscode.commands.executeCommand('setContext', amazonQDismissedKey, true)
31-
})
30+
export const dismissQTree = Commands.declare(
31+
{ id: '_aws.toolkit.amazonq.dismiss', compositeKey: { 0: 'source' } },
32+
() => async (source: string) => {
33+
await telemetry.toolkit_invokeAction.run(async () => {
34+
telemetry.record({
35+
source: ExtensionUse.instance.isFirstUse() ? ExtStartUpSources.firstStartUp : ExtStartUpSources.none,
36+
})
37+
38+
await globals.context.globalState.update(amazonQDismissedKey, true)
39+
await vscode.commands.executeCommand('setContext', amazonQDismissedKey, true)
3240

33-
export const toolkitSwitchToAmazonQCommand = Commands.declare('_aws.toolkit.amazonq.focusView', () => _switchToAmazonQ)
41+
telemetry.record({ action: 'dismissQExplorerTree' })
42+
})
43+
}
44+
)
3445

3546
// Learn more button of Amazon Q now opens the Amazon Q marketplace page.
3647
export const createLearnMoreNode = () =>
@@ -48,7 +59,7 @@ export function createInstallQNode() {
4859
}
4960

5061
export function createDismissNode() {
51-
return dismissQTree.build().asTreeNode({
62+
return dismissQTree.build(cwTreeNodeSource).asTreeNode({
5263
label: 'Dismiss', // TODO: localize
5364
iconPath: getIcon('vscode-close'),
5465
})

packages/core/src/amazonq/explorer/amazonQTreeNode.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,7 @@ import * as vscode from 'vscode'
77
import { createFreeTierLimitMet, createReconnect } from '../../codewhisperer/ui/codeWhispererNodes'
88
import { ResourceTreeDataProvider, TreeNode } from '../../shared/treeview/resourceTreeDataProvider'
99
import { AuthState, AuthUtil, isPreviousQUser } from '../../codewhisperer/util/authUtil'
10-
import {
11-
createLearnMoreNode,
12-
createInstallQNode,
13-
createDismissNode,
14-
toolkitSwitchToAmazonQCommand,
15-
} from './amazonQChildrenNodes'
10+
import { createLearnMoreNode, createInstallQNode, createDismissNode } from './amazonQChildrenNodes'
1611
import { Command, Commands } from '../../shared/vscode/commands2'
1712
import { listCodeWhispererCommands } from '../../codewhisperer/ui/statusBarMenu'
1813
import { getIcon } from '../../shared/icons'
@@ -89,13 +84,11 @@ export class AmazonQNode implements TreeNode {
8984
}
9085

9186
if (AmazonQNode.amazonQState !== 'connected') {
92-
return [createSignIn('tree', toolkitSwitchToAmazonQCommand), createLearnMoreNode()]
87+
return [createSignIn('tree'), createLearnMoreNode()]
9388
}
9489

9590
return [
96-
vsCodeState.isFreeTierLimitReached
97-
? createFreeTierLimitMet('tree')
98-
: switchToAmazonQNode('tree', toolkitSwitchToAmazonQCommand),
91+
vsCodeState.isFreeTierLimitReached ? createFreeTierLimitMet('tree') : switchToAmazonQNode('tree'),
9992
createNewMenuButton(),
10093
]
10194
}

packages/core/src/amazonq/explorer/commonNodes.ts

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,28 @@
55

66
import * as vscode from 'vscode'
77
import * as nls from 'vscode-nls'
8-
import { Command, DeclaredCommand } from '../../shared/vscode/commands2'
8+
import { Command, Commands } from '../../shared/vscode/commands2'
99
import { codicon, getIcon } from '../../shared/icons'
1010
import { telemetry } from '../../shared/telemetry/telemetry'
1111
import { DataQuickPickItem } from '../../shared/ui/pickerPrompter'
1212
import { TreeNode } from '../../shared/treeview/resourceTreeDataProvider'
13+
import { CodeWhispererSource } from '../../codewhisperer/commands/types'
1314

1415
const localize = nls.loadMessageBundle()
1516

1617
/**
17-
* Do not call this function directly, use the necessary equivalent command registered by the extensions:
18-
* - switchToAmazonQCommand, _aws.amazonq.focusView (for Amazon Q code)
19-
* - toolkitSwitchToAmazonQCommand, _aws.toolkit.amazonq.focusView (for Toolkit code)
18+
* Do not call this function directly, use the necessary equivalent commands below,
19+
* which areregistered by the Amazon Q extension.
20+
* - switchToAmazonQCommand
21+
* - switchToAmazonQSignInCommand
2022
*/
21-
export async function _switchToAmazonQ(signIn: boolean = false) {
23+
export async function _switchToAmazonQ(signIn: boolean) {
2224
if (signIn) {
2325
await vscode.commands.executeCommand('setContext', 'aws.amazonq.showLoginView', true)
26+
telemetry.ui_click.emit({
27+
elementId: 'amazonq_switchToQSignIn',
28+
passive: false,
29+
})
2430
} else {
2531
telemetry.ui_click.emit({
2632
elementId: 'amazonq_switchToQChat',
@@ -34,6 +40,18 @@ export async function _switchToAmazonQ(signIn: boolean = false) {
3440
await vscode.commands.executeCommand('aws.amazonq.AmazonCommonAuth.focus')
3541
}
3642

43+
export const switchToAmazonQCommand = Commands.declare(
44+
{ id: '_aws.amazonq.focusView', compositeKey: { 0: 'source' } },
45+
() =>
46+
(source: CodeWhispererSource, signIn: boolean = false) =>
47+
_switchToAmazonQ(false)
48+
)
49+
50+
export const switchToAmazonQSignInCommand = Commands.declare(
51+
{ id: '_aws.amazonq.signIn.focusView', compositeKey: { 0: 'source' } },
52+
() => (source: CodeWhispererSource) => _switchToAmazonQ(true)
53+
)
54+
3755
/**
3856
* Common nodes that can be used by mutliple UIs, e.g. status bar menu, explorer tree, etc.
3957
* Individual extensions may register their own commands for the nodes, so it must be passed in.
@@ -42,19 +60,13 @@ export async function _switchToAmazonQ(signIn: boolean = false) {
4260
* and only use the one registered in Amazon Q.
4361
*/
4462

45-
export function switchToAmazonQNode(
46-
type: 'item',
47-
cmd: DeclaredCommand<typeof _switchToAmazonQ>
48-
): DataQuickPickItem<'openChatPanel'>
49-
export function switchToAmazonQNode(type: 'tree', cmd: DeclaredCommand<typeof _switchToAmazonQ>): TreeNode<Command>
50-
export function switchToAmazonQNode(
51-
type: 'item' | 'tree',
52-
cmd: DeclaredCommand<typeof _switchToAmazonQ>
53-
): DataQuickPickItem<'openChatPanel'> | TreeNode<Command>
54-
export function switchToAmazonQNode(type: 'item' | 'tree', cmd: DeclaredCommand<typeof _switchToAmazonQ>): any {
63+
export function switchToAmazonQNode(type: 'item'): DataQuickPickItem<'openChatPanel'>
64+
export function switchToAmazonQNode(type: 'tree'): TreeNode<Command>
65+
export function switchToAmazonQNode(type: 'item' | 'tree'): DataQuickPickItem<'openChatPanel'> | TreeNode<Command>
66+
export function switchToAmazonQNode(type: 'item' | 'tree'): any {
5567
switch (type) {
5668
case 'tree':
57-
return cmd.build().asTreeNode({
69+
return switchToAmazonQCommand.build('codewhispererTreeNode').asTreeNode({
5870
label: 'Open Chat Panel',
5971
iconPath: getIcon('vscode-comment'),
6072
contextValue: 'awsToAmazonQChatNode',
@@ -64,32 +76,29 @@ export function switchToAmazonQNode(type: 'item' | 'tree', cmd: DeclaredCommand<
6476
data: 'openChatPanel',
6577
label: 'Open Chat Panel',
6678
iconPath: getIcon('vscode-comment'),
67-
onClick: () => cmd.execute(),
79+
onClick: () => switchToAmazonQCommand.execute('codewhispererQuickPick'),
6880
} as DataQuickPickItem<'openChatPanel'>
6981
}
7082
}
7183

72-
export function createSignIn(type: 'item', cmd: DeclaredCommand<typeof _switchToAmazonQ>): DataQuickPickItem<'signIn'>
73-
export function createSignIn(type: 'tree', cmd: DeclaredCommand<typeof _switchToAmazonQ>): TreeNode<Command>
74-
export function createSignIn(
75-
type: 'item' | 'tree',
76-
cmd: DeclaredCommand<typeof _switchToAmazonQ>
77-
): DataQuickPickItem<'signIn'> | TreeNode<Command>
78-
export function createSignIn(type: 'item' | 'tree', cmd: DeclaredCommand<typeof _switchToAmazonQ>): any {
84+
export function createSignIn(type: 'item'): DataQuickPickItem<'signIn'>
85+
export function createSignIn(type: 'tree'): TreeNode<Command>
86+
export function createSignIn(type: 'item' | 'tree'): DataQuickPickItem<'signIn'> | TreeNode<Command>
87+
export function createSignIn(type: 'item' | 'tree'): any {
7988
const label = localize('AWS.codewhisperer.signInNode.label', 'Sign in to get started')
8089
const icon = getIcon('vscode-account')
8190

8291
switch (type) {
8392
case 'tree':
84-
return cmd.build(true).asTreeNode({
93+
return switchToAmazonQSignInCommand.build('codewhispererTreeNode').asTreeNode({
8594
label: label,
8695
iconPath: icon,
8796
})
8897
case 'item':
8998
return {
9099
data: 'signIn',
91100
label: codicon`${icon} ${label}`,
92-
onClick: () => cmd.execute(true),
101+
onClick: () => switchToAmazonQSignInCommand.execute('codewhispererQuickPick'),
93102
} as DataQuickPickItem<'signIn'>
94103
}
95104
}

packages/core/src/auth/auth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ import { randomUUID } from '../common/crypto'
6565

6666
interface AuthService {
6767
/**
68-
* Lists all connections known to the Toolkit.
68+
* Lists all connections known to the extension.
6969
*/
7070
listConnections(): Promise<Connection[]>
7171

packages/core/src/auth/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ export { Connection, AwsConnection } from './connection'
1414
export { Auth } from './auth'
1515
export { CredentialsStore } from './credentials/store'
1616
export { LoginManager } from './deprecated/loginManager'
17-
export { ExtensionUse } from './utils'
17+
export * as AuthUtils from './utils'

packages/core/src/auth/ui/vue/show.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import { debounce } from 'lodash'
5454
import { submitFeedback } from '../../../feedback/vue/submitFeedback'
5555
import { InvalidGrantException } from '@aws-sdk/client-sso-oidc'
5656
import { isWeb } from '../../../common/webUtils'
57+
import { ExtStartUpSources } from '../../../shared/telemetry'
5758

5859
export class AuthWebview extends VueWebview {
5960
public static readonly sourcePath: string = 'src/auth/ui/vue/index.js'
@@ -842,7 +843,7 @@ export async function emitWebviewClosed(authWebview: ClassToInterfaceType<AuthWe
842843
result = 'Cancelled'
843844
}
844845

845-
if (source === 'firstStartup' && numConnectionsAdded === 0) {
846+
if (source === ExtStartUpSources.firstStartUp && numConnectionsAdded === 0) {
846847
if (numConnectionsInitial > 0) {
847848
// This is the users first startup of the extension and no new connections were added, but they already had connections setup on their
848849
// system which we discovered. We consider this a success even though they added no new connections.

0 commit comments

Comments
 (0)