Skip to content

Commit 7b90580

Browse files
Merge staging into feature/redshift
2 parents 38caa80 + 5e56b15 commit 7b90580

File tree

7 files changed

+55
-118
lines changed

7 files changed

+55
-118
lines changed

src/auth/secondaryAuth.ts

Lines changed: 24 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -7,79 +7,30 @@ import globals from '../shared/extensionGlobals'
77

88
import * as vscode from 'vscode'
99
import { getLogger } from '../shared/logger'
10-
import { showQuickPick } from '../shared/ui/pickerPrompter'
1110
import { cast, Optional } from '../shared/utilities/typeConstructors'
1211
import { Auth } from './auth'
1312
import { once } from '../shared/utilities/functionUtils'
14-
import { telemetry } from '../shared/telemetry/telemetry'
15-
import { createExitButton, createHelpButton } from '../shared/ui/buttons'
1613
import { isNonNullable } from '../shared/utilities/tsUtils'
1714
import { CancellationError } from '../shared/utilities/timeoutUtils'
1815
import { Connection, SsoConnection, StatefulConnection } from './connection'
1916

20-
async function promptUseNewConnection(newConn: Connection, oldConn: Connection, tools: string[], swapNo: boolean) {
21-
// Multi-select picker would be better ?
22-
const saveConnectionItem = {
23-
label: `Yes, keep using ${newConn.label} with ${tools.join(', ')} while using ${
24-
oldConn.label
25-
} with other services.`,
26-
detail: `To remove later, select "Remove Connection from Tool" from the tool's context (right-click) menu.`,
27-
data: 'yes',
28-
} as const
29-
30-
const useConnectionItem = {
31-
label: `No, switch everything to authenticate with ${(swapNo ? newConn : oldConn).label}.`,
32-
detail: 'This will not log you out; you can reconnect at any time by switching connections.',
33-
data: 'no',
34-
} as const
35-
36-
const helpButton = createHelpButton()
37-
const openLink = helpButton.onClick.bind(helpButton)
38-
helpButton.onClick = () => {
39-
telemetry.ui_click.emit({ elementId: 'connection_multiple_auths_help' })
40-
openLink()
41-
}
42-
43-
const resp = await showQuickPick([saveConnectionItem, useConnectionItem], {
44-
title: `Some tools you've been using don't work with ${oldConn.label}. Keep using ${newConn.label} in the background while using ${oldConn.label}?`,
45-
placeholder: 'Confirm choice',
46-
buttons: [helpButton, createExitButton()],
47-
})
48-
49-
switch (resp) {
50-
case 'yes':
51-
telemetry.ui_click.emit({ elementId: 'connection_multiple_auths_yes' })
52-
break
53-
case 'no':
54-
telemetry.ui_click.emit({ elementId: 'connection_multiple_auths_no' })
55-
break
56-
default:
57-
telemetry.ui_click.emit({ elementId: 'connection_multiple_auths_exit' })
58-
}
59-
60-
return resp
61-
}
62-
63-
let oldConn: Auth['activeConnection']
17+
let currentConn: Auth['activeConnection']
6418
const auths = new Map<string, SecondaryAuth>()
6519
const multiConnectionListeners = new WeakMap<Auth, vscode.Disposable>()
6620
const registerAuthListener = (auth: Auth) => {
67-
return auth.onDidChangeActiveConnection(async conn => {
68-
const potentialConn = oldConn
69-
if (conn !== undefined && potentialConn?.state === 'valid') {
21+
return auth.onDidChangeActiveConnection(async newConn => {
22+
// When we change the active connection, there may be
23+
// secondary auths that were dependent on the previous active connection.
24+
// To ensure secondary auths still work, when we change to a new active connection,
25+
// the following will "save" the oldConn with the secondary auths that are using it.
26+
const oldConn = currentConn
27+
if (newConn && oldConn?.state === 'valid') {
7028
const saveableAuths = Array.from(auths.values()).filter(
71-
a => !a.isUsingSavedConnection && a.isUsable(potentialConn) && !a.isUsable(conn)
29+
a => !a.hasSavedConnection && a.isUsable(oldConn) && !a.isUsable(newConn)
7230
)
73-
const toolNames = saveableAuths.map(a => a.toolLabel)
74-
if (
75-
saveableAuths.length > 0 &&
76-
(await promptUseNewConnection(potentialConn, conn, toolNames, false)) === 'yes'
77-
) {
78-
await Promise.all(saveableAuths.map(a => a.saveConnection(potentialConn)))
79-
}
31+
await Promise.all(saveableAuths.map(a => a.saveConnection(oldConn)))
8032
}
81-
82-
oldConn = conn
33+
currentConn = newConn
8334
})
8435
}
8536

@@ -104,15 +55,13 @@ export function getSecondaryAuth<T extends Connection>(
10455
* Gets all {@link SecondaryAuth} instances that have saved the connection
10556
*/
10657
export function getDependentAuths(conn: Connection): SecondaryAuth[] {
107-
return Array.from(auths.values()).filter(
108-
auth => auth.isUsingSavedConnection && auth.activeConnection?.id === conn.id
109-
)
58+
return Array.from(auths.values()).filter(auth => auth.hasSavedConnection && auth.activeConnection?.id === conn.id)
11059
}
11160

11261
export function getAllConnectionsInUse(auth: Auth): StatefulConnection[] {
11362
const connMap = new Map<Connection['id'], StatefulConnection>()
11463
const toolConns = Array.from(auths.values())
115-
.filter(a => a.isUsingSavedConnection)
64+
.filter(a => a.hasSavedConnection)
11665
.map(a => a.activeConnection)
11766

11867
for (const conn of [auth.activeConnection, ...toolConns].filter(isNonNullable)) {
@@ -174,13 +123,18 @@ export class SecondaryAuth<T extends Connection = Connection> {
174123
}
175124

176125
public get activeConnection(): T | undefined {
177-
return (
178-
this.#savedConnection ??
179-
(this.#activeConnection && this.isUsable(this.#activeConnection) ? this.#activeConnection : undefined)
180-
)
126+
if (this.#savedConnection) {
127+
return this.#savedConnection
128+
}
129+
130+
if (this.#activeConnection && this.isUsable(this.#activeConnection)) {
131+
return this.#activeConnection
132+
}
133+
134+
return undefined
181135
}
182136

183-
public get isUsingSavedConnection() {
137+
public get hasSavedConnection() {
184138
return this.#savedConnection !== undefined
185139
}
186140

@@ -202,11 +156,7 @@ export class SecondaryAuth<T extends Connection = Connection> {
202156

203157
public async useNewConnection(conn: T) {
204158
if (this.auth.activeConnection !== undefined && !this.isUsable(this.auth.activeConnection)) {
205-
if ((await promptUseNewConnection(conn, this.auth.activeConnection, [this.toolLabel], true)) === 'yes') {
206-
await this.saveConnection(conn)
207-
} else {
208-
await this.auth.useConnection(conn)
209-
}
159+
await this.saveConnection(conn)
210160
} else {
211161
await this.auth.useConnection(conn)
212162
}

src/awsexplorer/activation.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,9 @@ import { telemetry } from '../shared/telemetry/telemetry'
2727
import { cdkNode, CdkRootNode } from '../cdk/explorer/rootNode'
2828
import { CodeWhispererNode, codewhispererNode } from '../codewhisperer/explorer/codewhispererNode'
2929
import { once } from '../shared/utilities/functionUtils'
30-
import { Auth } from '../auth/auth'
3130
import { CodeCatalystRootNode } from '../codecatalyst/explorer'
3231
import { CodeCatalystAuthenticationProvider } from '../codecatalyst/auth'
3332
import { S3FolderNode } from '../s3/explorer/s3FolderNode'
34-
import { AuthNode } from '../auth/utils'
3533
import { TreeNode } from '../shared/treeview/resourceTreeDataProvider'
3634

3735
/**
@@ -78,9 +76,8 @@ export async function activate(args: {
7876
)
7977

8078
const authProvider = CodeCatalystAuthenticationProvider.fromContext(args.context.extensionContext)
81-
const authNode = new AuthNode(Auth.instance)
8279
const codecatalystNode = isCloud9('classic') ? [] : [new CodeCatalystRootNode(authProvider)]
83-
const nodes = [authNode, ...codecatalystNode, cdkNode, codewhispererNode]
80+
const nodes = [...codecatalystNode, cdkNode, codewhispererNode]
8481
const developerTools = createLocalExplorerView(nodes)
8582
args.context.extensionContext.subscriptions.push(developerTools)
8683

@@ -99,7 +96,6 @@ export async function activate(args: {
9996
})
10097

10198
registerDeveloperToolsCommands(args.context.extensionContext, developerTools, {
102-
auth: authNode,
10399
codeCatalyst: codecatalystNode ? codecatalystNode[0] : undefined,
104100
codeWhisperer: codewhispererNode,
105101
})
@@ -175,7 +171,6 @@ async function registerDeveloperToolsCommands(
175171
ctx: vscode.ExtensionContext,
176172
developerTools: vscode.TreeView<TreeNode>,
177173
nodes: {
178-
auth: AuthNode
179174
codeWhisperer: CodeWhispererNode
180175
codeCatalyst: CodeCatalystRootNode | undefined
181176
}
@@ -203,10 +198,9 @@ async function registerDeveloperToolsCommands(
203198
)
204199
}
205200

206-
registerShowDeveloperToolsNode('CodeWhisperer', codewhispererNode)
201+
registerShowDeveloperToolsNode('CodeWhisperer', nodes.codeWhisperer)
207202

208-
const codeCatalystNode = nodes.codeCatalyst
209-
if (codeCatalystNode) {
210-
registerShowDeveloperToolsNode('CodeCatalyst', codeCatalystNode)
203+
if (nodes.codeCatalyst) {
204+
registerShowDeveloperToolsNode('CodeCatalyst', nodes.codeCatalyst)
211205
}
212206
}

src/codecatalyst/auth.ts

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { CodeCatalystClient, createClient } from '../shared/clients/codecatalyst
88
import { Auth } from '../auth/auth'
99
import { getSecondaryAuth } from '../auth/secondaryAuth'
1010
import { getLogger } from '../shared/logger'
11-
import * as localizedText from '../shared/localizedText'
1211
import { ToolkitError, isAwsError } from '../shared/errors'
1312
import { MetricName, MetricShapes, telemetry } from '../shared/telemetry/telemetry'
1413
import { openUrl } from '../shared/utilities/vsCodeUtils'
@@ -66,7 +65,7 @@ export class CodeCatalystAuthenticationProvider {
6665
}
6766

6867
public get isUsingSavedConnection() {
69-
return this.secondaryAuth.isUsingSavedConnection
68+
return this.secondaryAuth.hasSavedConnection
7069
}
7170

7271
public isConnectionValid(): boolean {
@@ -123,14 +122,22 @@ export class CodeCatalystAuthenticationProvider {
123122
throw new ToolkitError('Not onboarded with CodeCatalyst', { code: 'NotOnboarded', cancelled: true })
124123
}
125124

126-
public async promptNotConnected(): Promise<SsoConnection> {
125+
/**
126+
* Return a Builder ID connection that works with CodeCatalyst.
127+
*
128+
* This cannot create a Builder ID, but will return an existing Builder ID,
129+
* upgrading the scopes if necessary.
130+
*/
131+
public async tryGetBuilderIdConnection(): Promise<SsoConnection> {
132+
if (this.activeConnection && isBuilderIdConnection(this.activeConnection)) {
133+
return this.activeConnection
134+
}
135+
127136
type ConnectionFlowEvent = Partial<MetricShapes[MetricName]> & {
128137
readonly codecatalyst_connectionFlow: 'Create' | 'Switch' | 'Upgrade' // eslint-disable-line @typescript-eslint/naming-convention
129138
}
130139

131140
const conn = (await this.auth.listConnections()).find(isBuilderIdConnection)
132-
const continueItem: vscode.MessageItem = { title: localizedText.continueText }
133-
const cancelItem: vscode.MessageItem = { title: localizedText.cancel, isCloseAffordance: true }
134141

135142
if (conn === undefined) {
136143
telemetry.record({
@@ -153,21 +160,11 @@ export class CodeCatalystAuthenticationProvider {
153160
return this.secondaryAuth.addScopes(conn, defaultScopes)
154161
}
155162

156-
if (isBuilderIdConnection(conn) && this.auth.activeConnection?.id !== conn.id) {
163+
if (this.auth.activeConnection?.id !== conn.id) {
157164
telemetry.record({
158165
codecatalyst_connectionFlow: 'Switch',
159166
} satisfies ConnectionFlowEvent as MetricShapes[MetricName])
160167

161-
const resp = await vscode.window.showInformationMessage(
162-
'CodeCatalyst requires an AWS Builder ID connection.\n\n Switch to it now?',
163-
{ modal: true },
164-
continueItem,
165-
cancelItem
166-
)
167-
if (resp !== continueItem) {
168-
throw new ToolkitError('Not connected to CodeCatalyst', { code: 'NoConnection', cancelled: true })
169-
}
170-
171168
if (isUpgradeableConnection(conn)) {
172169
await upgrade()
173170
}

src/codecatalyst/commands.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ function createClientInjector(authProvider: CodeCatalystAuthenticationProvider):
136136
telemetry.record({ userId: AccountStatus.NotSet })
137137

138138
await authProvider.restore()
139-
const conn = authProvider.activeConnection ?? (await authProvider.promptNotConnected())
139+
const conn = await authProvider.tryGetBuilderIdConnection()
140140
const validatedConn = await validateConnection(conn, authProvider.auth)
141141
const client = await createClient(validatedConn)
142142
telemetry.record({ userId: client.identity.id })

src/codecatalyst/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export function isInDevEnv(): boolean {
6969
export const getStartedCommand = Commands.register('aws.codecatalyst.getStarted', setupCodeCatalystBuilderId)
7070

7171
export async function setupCodeCatalystBuilderId(authProvider: CodeCatalystAuthenticationProvider) {
72-
let conn = authProvider.activeConnection ?? (await authProvider.promptNotConnected())
72+
let conn = await authProvider.tryGetBuilderIdConnection()
7373

7474
if (authProvider.auth.getConnectionState(conn) === 'invalid') {
7575
conn = await authProvider.auth.reauthenticate(conn)

src/codewhisperer/util/authUtil.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export class AuthUtil {
104104
}
105105

106106
public get isUsingSavedConnection() {
107-
return this.conn !== undefined && this.secondaryAuth.isUsingSavedConnection
107+
return this.conn !== undefined && this.secondaryAuth.hasSavedConnection
108108
}
109109

110110
public isConnected(): boolean {

src/test/codewhisperer/util/authUtil.test.ts

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ describe('AuthUtil', async function () {
2222
authUtil = new AuthUtil(auth)
2323
})
2424

25+
afterEach(async function () {
26+
await auth.logout()
27+
})
28+
2529
it('if there is no valid AwsBuilderID conn, it will create one and use it', async function () {
2630
getTestWindow().onDidShowQuickPick(async picker => {
2731
await picker.untilReady()
@@ -83,22 +87,20 @@ describe('AuthUtil', async function () {
8387
)
8488
})
8589

86-
it('prompts to attach connection to CodeWhisperer when switching to an unsupported connection', async function () {
90+
it('CodeWhisperer uses fallback connection when switching to an unsupported connection', async function () {
8791
const supportedConn = await auth.createConnection(createBuilderIdProfile({ scopes: codewhispererScopes }))
8892
const unsupportedConn = await auth.createConnection(createSsoProfile())
8993

90-
getTestWindow().onDidShowQuickPick(picker => {
91-
assert.ok(picker.title?.startsWith(`Some tools you've been using don't work with ${unsupportedConn.label}`))
92-
const keepUsing = picker.findItemOrThrow(new RegExp(`keep using ${supportedConn.label}`))
93-
picker.acceptItem(keepUsing)
94-
})
95-
9694
await auth.useConnection(supportedConn)
9795
assert.ok(authUtil.isConnected())
9896
assert.strictEqual(auth.activeConnection?.id, authUtil.conn?.id)
9997

98+
// Switch to unsupported connection
99+
const cwAuthUpdatedConnection = captureEventOnce(authUtil.secondaryAuth.onDidChangeActiveConnection)
100100
await auth.useConnection(unsupportedConn)
101-
await captureEventOnce(authUtil.secondaryAuth.onDidChangeActiveConnection)
101+
await cwAuthUpdatedConnection
102+
103+
// Is using the fallback connection
102104
assert.ok(authUtil.isConnected())
103105
assert.ok(authUtil.isUsingSavedConnection)
104106
assert.notStrictEqual(auth.activeConnection?.id, authUtil.conn?.id)
@@ -114,13 +116,7 @@ describe('AuthUtil', async function () {
114116
assert.strictEqual((await auth.listConnections()).filter(isSsoConnection).length, 1)
115117
})
116118

117-
it('prompts to upgrade connections if they do not have the required scopes', async function () {
118-
getTestWindow().onDidShowMessage(message => {
119-
assert.ok(message.modal)
120-
message.assertMessage(/CodeWhisperer requires access to your/)
121-
message.selectItem('Proceed')
122-
})
123-
119+
it('automatically upgrades connections if they do not have the required scopes', async function () {
124120
const upgradeableConn = await auth.createConnection(createBuilderIdProfile())
125121
await auth.useConnection(upgradeableConn)
126122
assert.strictEqual(authUtil.isConnected(), false)

0 commit comments

Comments
 (0)