Skip to content

Commit 199d0fa

Browse files
feate(amazonq): only display open chat codelens if authenticated (#4955)
* feate(amazonq): only display open chat codelens if authenticated * fix tests * use sync method to fix tests * Update packages/core/src/test/codewhispererChat/editor/codelens.test.ts Co-authored-by: Nikolas Komonen <[email protected]> --------- Co-authored-by: Nikolas Komonen <[email protected]>
1 parent da367e9 commit 199d0fa

File tree

5 files changed

+61
-24
lines changed

5 files changed

+61
-24
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Feature",
3+
"description": "Inline Suggestions: Only display the 'Open Chat' CodeLens if the user is signed into Amazon Q."
4+
}

packages/core/src/auth/auth.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -370,9 +370,12 @@ export class Auth implements AuthService, ConnectionManager {
370370
* Alternatively you can use the `getToken()` call on an SSO connection to do the same thing,
371371
* but it will additionally prompt for reauthentication if the connection is invalid.
372372
*/
373-
public async refreshConnectionState(connection: Pick<Connection, 'id'>): Promise<undefined> {
374-
const profile = this.store.getProfile(connection.id)
373+
public async refreshConnectionState(connection?: Pick<Connection, 'id'>): Promise<undefined> {
374+
if (connection === undefined) {
375+
return
376+
}
375377

378+
const profile = this.store.getProfile(connection.id)
376379
if (profile === undefined) {
377380
return
378381
}

packages/core/src/codewhisperer/util/authUtil.ts

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -387,28 +387,29 @@ export class AuthUtil {
387387
}
388388

389389
/**
390-
* Returns a snapshot of the overall auth state of CodeWhisperer + Chat features.
391-
*
392-
* @param shouldRefresh (default true) validate and update the current connection state.
393-
* If this setting is set to false, there is a risk that the evaluated state is outdated,
394-
* but it is safe from modifying the state of the connection.
390+
* Asynchronously returns a snapshot of the overall auth state of CodeWhisperer + Chat features.
391+
* It guarantees the latest state is correct at the risk of modifying connection state.
392+
* If this guarantee is not required, use sync method getChatAuthStateSync()
395393
*/
396-
public async getChatAuthState(shouldRefresh: boolean = true): Promise<FeatureAuthState> {
397-
const currentConnection = this.conn
394+
public async getChatAuthState(): Promise<FeatureAuthState> {
395+
// The state of the connection may not have been properly validated
396+
// and the current state we see may be stale, so refresh for latest state.
397+
await this.auth.refreshConnectionState(this.conn)
398+
return this.getChatAuthStateSync(this.conn)
399+
}
398400

399-
if (currentConnection === undefined) {
401+
/**
402+
* Synchronously returns a snapshot of the overall auth state of CodeWhisperer + Chat features without
403+
* validating or modifying the connection state. It is possible that the connection
404+
* is invalid/valid, but the current state displays something else. To guarantee the true state,
405+
* use async method getChatAuthState()
406+
*/
407+
public getChatAuthStateSync(conn = this.conn): FeatureAuthState {
408+
if (conn === undefined) {
400409
return buildFeatureAuthState(AuthStates.disconnected)
401410
}
402-
if (!isSsoConnection(currentConnection)) {
403-
throw new ToolkitError(
404-
`Connection "${currentConnection.id}" is not a valid type: ${currentConnection.type}`
405-
)
406-
}
407-
408-
// The state of the connection may not have been properly validated
409-
// and the current state we see may be stale, so refresh for latest state.
410-
if (shouldRefresh) {
411-
await this.auth.refreshConnectionState(currentConnection)
411+
if (!isSsoConnection(conn)) {
412+
throw new ToolkitError(`Connection "${conn.id}" is not a valid type: ${conn.type}`)
412413
}
413414

414415
// default to expired to indicate reauth is needed if unmodified
@@ -418,11 +419,11 @@ export class AuthUtil {
418419
return state
419420
}
420421

421-
if (isBuilderIdConnection(currentConnection) || isIdcSsoConnection(currentConnection)) {
422-
if (isValidCodeWhispererCoreConnection(currentConnection)) {
422+
if (isBuilderIdConnection(conn) || isIdcSsoConnection(conn)) {
423+
if (isValidCodeWhispererCoreConnection(conn)) {
423424
state[Features.codewhispererCore] = AuthStates.connected
424425
}
425-
if (isValidAmazonQConnection(currentConnection)) {
426+
if (isValidAmazonQConnection(conn)) {
426427
Object.values(Features).forEach(v => (state[v as Feature] = AuthStates.connected))
427428
}
428429
}

packages/core/src/codewhispererChat/editor/codelens.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { ToolkitError } from '../../shared/errors'
88
import { Commands, placeholder } from '../../shared/vscode/commands2'
99
import { platform } from 'os'
1010
import { focusAmazonQPanel } from '../commands/registerCommands'
11+
import { AuthStates, AuthUtil } from '../../codewhisperer/util/authUtil'
1112

1213
/** When the user clicks the CodeLens that prompts user to try Amazon Q chat */
1314
export const tryChatCodeLensCommand = Commands.declare(`_aws.amazonq.tryChatCodeLens`, () => async () => {
@@ -63,9 +64,13 @@ export class TryChatCodeLensProvider implements vscode.CodeLensProvider {
6364
document: vscode.TextDocument,
6465
token: vscode.CancellationToken
6566
): vscode.ProviderResult<vscode.CodeLens[]> {
66-
return new Promise(async resolve => {
67+
return new Promise(resolve => {
6768
token.onCancellationRequested(() => resolve([]))
6869

70+
if (AuthUtil.instance.getChatAuthStateSync().amazonQ !== AuthStates.connected) {
71+
return resolve([])
72+
}
73+
6974
if (this.count >= TryChatCodeLensProvider.maxCount) {
7075
// We only want to show this code lens a certain amount of times
7176
// to not annoy customers. The following ensures it is never shown again.

packages/core/src/test/codewhispererChat/editor/codelens.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import { assertTelemetry, installFakeClock, tryRegister } from '../../testUtil'
1414
import { InstalledClock } from '@sinonjs/fake-timers'
1515
import globals from '../../../shared/extensionGlobals'
1616
import { focusAmazonQPanel } from '../../../codewhispererChat/commands/registerCommands'
17+
import sinon from 'sinon'
18+
import { AuthState, AuthStates, AuthUtil, FeatureAuthState } from '../../../codewhisperer/util/authUtil'
1719

1820
describe('TryChatCodeLensProvider', () => {
1921
let instance: TryChatCodeLensProvider = new TryChatCodeLensProvider()
@@ -39,9 +41,16 @@ describe('TryChatCodeLensProvider', () => {
3941
instance.dispose()
4042
cancellationTokenSource?.dispose()
4143
clock.uninstall()
44+
sinon.restore()
4245
})
4346

47+
function stubConnection(state: AuthState) {
48+
return sinon.stub(AuthUtil.instance, 'getChatAuthStateSync').returns({ amazonQ: state } as FeatureAuthState)
49+
}
50+
4451
it('keeps returning a code lense until it hits the max times it should show', async function () {
52+
stubConnection('connected')
53+
4554
let codeLensCount = 0
4655
const modifierKey = resolveModifierKey()
4756
while (codeLensCount < 10) {
@@ -83,6 +92,21 @@ describe('TryChatCodeLensProvider', () => {
8392
})
8493
})
8594

95+
it('does NOT show codelens if amazon Q is not connected', async function () {
96+
const testConnection = async (state: AuthState) => {
97+
const stub = stubConnection(state)
98+
99+
const emptyResult = await instance.provideCodeLenses({} as any, new vscode.CancellationTokenSource().token)
100+
assert.deepStrictEqual(emptyResult, [], `codelens shown with state: ${state}`)
101+
stub.restore()
102+
}
103+
104+
const testStates = Object.values(AuthStates).filter(s => s !== AuthStates.connected)
105+
for (const state of testStates) {
106+
await testConnection(state)
107+
}
108+
})
109+
86110
it('outputs expected telemetry', async function () {
87111
await tryChatCodeLensCommand.execute()
88112
assertTelemetry('vscode_executeCommand', { command: focusAmazonQPanel.id, source: 'codeLens' })

0 commit comments

Comments
 (0)