Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 3 additions & 64 deletions packages/amazonq/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,8 @@
* SPDX-License-Identifier: Apache-2.0
*/

import {
AuthUtils,
CredentialsStore,
LoginManager,
getTelemetryMetadataForConn,
initializeAuth,
isAnySsoConnection,
} from 'aws-core-vscode/auth'
import {
AuthState,
AuthUtil,
activate as activateCodeWhisperer,
shutdown as shutdownCodeWhisperer,
} from 'aws-core-vscode/codewhisperer'
import { AuthUtils, CredentialsStore, LoginManager, initializeAuth } from 'aws-core-vscode/auth'
import { activate as activateCodeWhisperer, shutdown as shutdownCodeWhisperer } from 'aws-core-vscode/codewhisperer'
import { makeEndpointsProvider, registerGenericCommands } from 'aws-core-vscode'
import { CommonAuthWebview } from 'aws-core-vscode/login'
import {
Expand All @@ -38,15 +26,13 @@ import {
globals,
initialize,
initializeComputeRegion,
isNetworkError,
messages,
placeholder,
setContext,
setupUninstallHandler,
maybeShowMinVscodeWarning,
isSageMaker,
} from 'aws-core-vscode/shared'
import { ExtStartUpSources, telemetry } from 'aws-core-vscode/telemetry'
import { ExtStartUpSources } from 'aws-core-vscode/telemetry'
import { VSCODE_EXTENSION_ID } from 'aws-core-vscode/utils'
import { join } from 'path'
import * as semver from 'semver'
Expand Down Expand Up @@ -161,53 +147,6 @@ export async function activateAmazonQCommon(context: vscode.ExtensionContext, is
void focusAmazonQPanel.execute(placeholder, 'firstStartUp')
}, 1000)
}

await telemetry.auth_userState
.run(async () => {
telemetry.record({ passive: true })

const firstUse = AuthUtils.ExtensionUse.instance.isFirstUse()
const wasUpdated = AuthUtils.ExtensionUse.instance.wasUpdated()

if (firstUse) {
telemetry.record({ source: ExtStartUpSources.firstStartUp })
} else if (wasUpdated) {
telemetry.record({ source: ExtStartUpSources.update })
} else {
telemetry.record({ source: ExtStartUpSources.reload })
}

let authState: AuthState = 'disconnected'
try {
// May call connection validate functions that try to refresh the token.
// This could result in network errors.
authState = (await AuthUtil.instance.getChatAuthState(false)).codewhispererChat
} catch (err) {
if (
isNetworkError(err) &&
AuthUtil.instance.conn &&
AuthUtil.instance.auth.getConnectionState(AuthUtil.instance.conn) === 'valid'
) {
authState = 'connectedWithNetworkError'
} else {
throw err
}
}
const currConn = AuthUtil.instance.conn
if (currConn !== undefined && !(isAnySsoConnection(currConn) || isSageMaker())) {
getLogger().error(`Current Amazon Q connection is not SSO, type is: %s`, currConn?.type)
}

telemetry.record({
authStatus:
authState === 'connected' || authState === 'expired' || authState === 'connectedWithNetworkError'
? authState
: 'notConnected',
authEnabledConnections: AuthUtils.getAuthFormIdsFromConnection(currConn).join(','),
...(await getTelemetryMetadataForConn(currConn)),
})
})
.catch((err) => getLogger().error('Error collecting telemetry for auth_userState: %s', err))
}

export async function deactivateCommon() {
Expand Down
51 changes: 49 additions & 2 deletions packages/amazonq/src/extensionNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,20 @@ import * as vscode from 'vscode'
import { activateAmazonQCommon, amazonQContextPrefix, deactivateCommon } from './extension'
import { DefaultAmazonQAppInitContext } from 'aws-core-vscode/amazonq'
import { activate as activateQGumby } from 'aws-core-vscode/amazonqGumby'
import { ExtContext, globals, CrashMonitoring } from 'aws-core-vscode/shared'
import { ExtContext, globals, CrashMonitoring, getLogger, isNetworkError, isSageMaker } from 'aws-core-vscode/shared'
import { filetypes, SchemaService } from 'aws-core-vscode/sharedNode'
import { updateDevMode } from 'aws-core-vscode/dev'
import { CommonAuthViewProvider } from 'aws-core-vscode/login'
import { isExtensionActive, VSCODE_EXTENSION_ID } from 'aws-core-vscode/utils'
import { registerSubmitFeedback } from 'aws-core-vscode/feedback'
import { DevOptions } from 'aws-core-vscode/dev'
import { Auth } from 'aws-core-vscode/auth'
import { Auth, AuthUtils, getTelemetryMetadataForConn, isAnySsoConnection } from 'aws-core-vscode/auth'
import api from './api'
import { activate as activateCWChat } from './app/chat/activation'
import { beta } from 'aws-core-vscode/dev'
import { activate as activateNotifications } from 'aws-core-vscode/notifications'
import { AuthState, AuthUtil } from 'aws-core-vscode/codewhisperer'
import { telemetry, AuthUserState } from 'aws-core-vscode/telemetry'

export async function activate(context: vscode.ExtensionContext) {
// IMPORTANT: No other code should be added to this function. Place it in one of the following 2 functions where appropriate.
Expand Down Expand Up @@ -61,6 +64,50 @@ async function activateAmazonQNode(context: vscode.ExtensionContext) {

await setupDevMode(context)
await beta.activate(context)

// TODO: Should probably emit for web as well.
// Will the web metric look the same?
const authState = await getAuthState()
telemetry.auth_userState.emit({
passive: true,
result: 'Succeeded',
source: AuthUtils.ExtensionUse.instance.sourceForTelemetry(),
...authState,
})

await activateNotifications(context, authState, getAuthState)
}

async function getAuthState(): Promise<Omit<AuthUserState, 'source'>> {
let authState: AuthState = 'disconnected'
try {
// May call connection validate functions that try to refresh the token.
// This could result in network errors.
authState = (await AuthUtil.instance.getChatAuthState(false)).codewhispererChat
} catch (err) {
if (
isNetworkError(err) &&
AuthUtil.instance.conn &&
AuthUtil.instance.auth.getConnectionState(AuthUtil.instance.conn) === 'valid'
) {
authState = 'connectedWithNetworkError'
} else {
throw err
}
}
const currConn = AuthUtil.instance.conn
if (currConn !== undefined && !(isAnySsoConnection(currConn) || isSageMaker())) {
getLogger().error(`Current Amazon Q connection is not SSO, type is: %s`, currConn?.type)
}

return {
authStatus:
authState === 'connected' || authState === 'expired' || authState === 'connectedWithNetworkError'
? authState
: 'notConnected',
authEnabledConnections: AuthUtils.getAuthFormIdsFromConnection(currConn).join(','),
...(await getTelemetryMetadataForConn(currConn)),
}
}

/**
Expand Down
14 changes: 14 additions & 0 deletions packages/core/src/auth/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import { EcsCredentialsProvider } from './providers/ecsCredentialsProvider'
import { EnvVarsCredentialsProvider } from './providers/envVarsCredentialsProvider'
import { showMessageWithUrl } from '../shared/utilities/messages'
import { credentialHelpUrl } from '../shared/constants'
import { ExtStartUpSource } from '../shared/telemetry/util'

// iam-only excludes Builder ID and IAM Identity Center from the list of valid connections
// TODO: Understand if "iam" should include these from the list at all
Expand Down Expand Up @@ -734,6 +735,19 @@ export class ExtensionUse {
return this.wasExtensionUpdated
}

/**
* Returns a {@link ExtStartUpSource} based on the current state of the extension.
*/
sourceForTelemetry(): ExtStartUpSource {
if (this.isFirstUse()) {
return ExtStartUpSources.firstStartUp
} else if (this.wasUpdated()) {
return ExtStartUpSources.update
} else {
return ExtStartUpSources.reload
}
}

private updateMemento(key: 'isExtensionFirstUse' | 'lastExtensionVersion', val: any) {
globals.globalState.tryUpdate(key, val)
}
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/awsexplorer/activationShared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,6 @@ export function registerToolView(viewNode: ToolView, context: vscode.ExtensionCo
telemetry.cdk_appExpanded.emit()
}
})

return toolView
}
55 changes: 1 addition & 54 deletions packages/core/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { Commands } from './shared/vscode/commands2'
import { endpointsFileUrl, githubCreateIssueUrl, githubUrl } from './shared/constants'
import { getIdeProperties, aboutExtension, isCloud9, getDocUrl } from './shared/extensionUtilities'
import { logAndShowError, logAndShowWebviewError } from './shared/utilities/logAndShowUtils'
import { AuthStatus, telemetry } from './shared/telemetry/telemetry'
import { telemetry } from './shared/telemetry/telemetry'
import { openUrl } from './shared/utilities/vsCodeUtils'
import { activateViewsShared } from './awsexplorer/activationShared'
import fs from './shared/fs/fs'
Expand All @@ -45,11 +45,6 @@ import { UriHandler } from './shared/vscode/uriHandler'
import { disableAwsSdkWarning } from './shared/awsClientBuilder'
import { FileResourceFetcher } from './shared/resourcefetcher/fileResourceFetcher'
import { ResourceFetcher } from './shared/resourcefetcher/resourcefetcher'
import { ExtStartUpSources } from './shared/telemetry/util'
import { ExtensionUse, getAuthFormIdsFromConnection } from './auth/utils'
import { Auth } from './auth'
import { AuthFormId } from './login/webview/vue/types'
import { getTelemetryMetadataForConn, isSsoConnection } from './auth/connection'
import { registerCommands } from './commands'

// In web mode everything must be in a single file, so things like the endpoints file will not be available.
Expand Down Expand Up @@ -258,51 +253,3 @@ function wrapWithProgressForCloud9(channel: vscode.OutputChannel): (typeof vscod
})
}
}

export async function emitUserState() {
await telemetry.auth_userState.run(async () => {
telemetry.record({ passive: true })

const firstUse = ExtensionUse.instance.isFirstUse()
const wasUpdated = ExtensionUse.instance.wasUpdated()

if (firstUse) {
telemetry.record({ source: ExtStartUpSources.firstStartUp })
} else if (wasUpdated) {
telemetry.record({ source: ExtStartUpSources.update })
} else {
telemetry.record({ source: ExtStartUpSources.reload })
}

let authStatus: AuthStatus = 'notConnected'
const enabledConnections: Set<AuthFormId> = new Set()
const enabledScopes: Set<string> = new Set()
if (Auth.instance.hasConnections) {
authStatus = 'expired'
;(await Auth.instance.listConnections()).forEach((conn) => {
const state = Auth.instance.getConnectionState(conn)
if (state === 'valid') {
authStatus = 'connected'
}

getAuthFormIdsFromConnection(conn).forEach((id) => enabledConnections.add(id))
if (isSsoConnection(conn)) {
conn.scopes?.forEach((s) => enabledScopes.add(s))
}
})
}

// There may be other SSO connections in toolkit, but there is no use case for
// displaying registration info for non-active connections at this time.
const activeConn = Auth.instance.activeConnection
if (activeConn?.type === 'sso') {
telemetry.record(await getTelemetryMetadataForConn(activeConn))
}

telemetry.record({
authStatus,
authEnabledConnections: [...enabledConnections].join(','),
authScopes: [...enabledScopes].join(','),
})
})
}
54 changes: 50 additions & 4 deletions packages/core/src/extensionNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,27 @@ import * as beta from './dev/beta'
import { activate as activateApplicationComposer } from './applicationcomposer/activation'
import { activate as activateRedshift } from './awsService/redshift/activation'
import { activate as activateIamPolicyChecks } from './awsService/accessanalyzer/activation'
import { activate as activateNotifications } from './notifications/activation'
import { SchemaService } from './shared/schemas'
import { AwsResourceManager } from './dynamicResources/awsResourceManager'
import globals from './shared/extensionGlobals'
import { Experiments, Settings, showSettingsFailedMsg } from './shared/settings'
import { isReleaseVersion } from './shared/vscode/env'
import { telemetry } from './shared/telemetry/telemetry'
import { AuthStatus, AuthUserState, telemetry } from './shared/telemetry/telemetry'
import { Auth, SessionSeparationPrompt } from './auth/auth'
import { getTelemetryMetadataForConn } from './auth/connection'
import { registerSubmitFeedback } from './feedback/vue/submitFeedback'
import { activateCommon, deactivateCommon, emitUserState } from './extension'
import { activateCommon, deactivateCommon } from './extension'
import { learnMoreAmazonQCommand, qExtensionPageCommand, dismissQTree } from './amazonq/explorer/amazonQChildrenNodes'
import { AuthUtil, codeWhispererCoreScopes, isPreviousQUser } from './codewhisperer/util/authUtil'
import { installAmazonQExtension } from './codewhisperer/commands/basicCommands'
import { isExtensionInstalled, VSCODE_EXTENSION_ID } from './shared/utilities'
import { ExtensionUse, initializeCredentialsProviderManager } from './auth/utils'
import { ExtensionUse, getAuthFormIdsFromConnection, initializeCredentialsProviderManager } from './auth/utils'
import { ExtStartUpSources } from './shared/telemetry'
import { activate as activateThreatComposerEditor } from './threatComposer/activation'
import { isSsoConnection, hasScopes } from './auth/connection'
import { CrashMonitoring, setContext } from './shared'
import { AuthFormId } from './login/webview/vue/types'

let localize: nls.LocalizeFunc

Expand Down Expand Up @@ -233,7 +236,17 @@ export async function activate(context: vscode.ExtensionContext) {
globals.telemetry.assertPassiveTelemetry(globals.didReload)
}

await emitUserState()
// TODO: Should probably emit for web as well.
// Will the web metric look the same?
const authState = await getAuthState()
telemetry.auth_userState.emit({
passive: true,
result: 'Succeeded',
source: ExtensionUse.instance.sourceForTelemetry(),
...authState,
})

await activateNotifications(context, authState, getAuthState)
} catch (error) {
const stacktrace = (error as Error).stack?.split('\n')
// truncate if the stacktrace is unusually long
Expand Down Expand Up @@ -324,3 +337,36 @@ function recordToolkitInitialization(activationStartedOn: number, settingsValid:
logger?.error(err as Error)
}
}

async function getAuthState(): Promise<Omit<AuthUserState, 'source'>> {
let authStatus: AuthStatus = 'notConnected'
const enabledConnections: Set<AuthFormId> = new Set()
const enabledScopes: Set<string> = new Set()
if (Auth.instance.hasConnections) {
authStatus = 'expired'
;(await Auth.instance.listConnections()).forEach((conn) => {
const state = Auth.instance.getConnectionState(conn)
if (state === 'valid') {
authStatus = 'connected'
}

getAuthFormIdsFromConnection(conn).forEach((id) => enabledConnections.add(id))
if (isSsoConnection(conn)) {
conn.scopes?.forEach((s) => enabledScopes.add(s))
}
})
}

// There may be other SSO connections in toolkit, but there is no use case for
// displaying registration info for non-active connections at this time.
const activeConn = Auth.instance.activeConnection
if (activeConn?.type === 'sso') {
telemetry.record(await getTelemetryMetadataForConn(activeConn))
}

return {
authStatus,
authEnabledConnections: [...enabledConnections].sort().join(','),
authScopes: [...enabledScopes].sort().join(','),
}
}
Loading
Loading