Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Troubleshoot: Show auth connection issues #3750

Merged
merged 27 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c28d6e0
Add connection issues troubleshooting page
RXminuS Apr 9, 2024
0769a31
Add easier Storybook breakpoint debugging
RXminuS Apr 9, 2024
c8a41a0
Merge remote-tracking branch 'origin/main' into rnauta/troubleshootin…
RXminuS Apr 9, 2024
c7ba13e
Re-generate Kotlin bindings
RXminuS Apr 10, 2024
d4c490d
Merge remote-tracking branch 'origin/main' into rnauta/troubleshootin…
RXminuS Apr 10, 2024
24ca961
Enable testing of simulated connection issues
RXminuS Apr 11, 2024
ca6f150
Allow user to fully reset Cody on connection issue
RXminuS Apr 11, 2024
c07c162
Update e2e tests
RXminuS Apr 11, 2024
b07807b
Re-generate Kotlin bindings
RXminuS Apr 11, 2024
b9303f2
Merge remote-tracking branch 'origin/main' into rnauta/troubleshootin…
RXminuS Apr 11, 2024
6de5e4c
Update changelog
RXminuS Apr 11, 2024
26b08dd
Remove reset/show logs
RXminuS Apr 13, 2024
e1340a1
Merge remote-tracking branch 'origin/main' into rnauta/troubleshootin…
RXminuS Apr 13, 2024
24aedda
Increase e2e test timeout
RXminuS Apr 16, 2024
8930318
Merge remote-tracking branch 'origin/main' into rnauta/troubleshootin…
RXminuS Apr 16, 2024
7e8b7c3
Merge remote-tracking branch 'origin/main' into rnauta/troubleshootin…
RXminuS Apr 16, 2024
3222ed2
Remove unused 'reload' command
RXminuS Apr 16, 2024
ef04020
Merge remote-tracking branch 'origin/main' into rnauta/troubleshootin…
RXminuS Apr 17, 2024
5148e6c
Merge remote-tracking branch 'origin/main' into rnauta/troubleshootin…
RXminuS Apr 17, 2024
4c22dfd
Remove accidental scip file
RXminuS Apr 17, 2024
56e1ab7
Show loading screen early
RXminuS Apr 17, 2024
39ce3c3
Fix styling feedback
RXminuS Apr 17, 2024
8ced982
Status bar loading state
RXminuS Apr 17, 2024
25caa9e
Merge remote-tracking branch 'origin/main' into rnauta/troubleshootin…
RXminuS Apr 17, 2024
dd3e0c2
Generate Kotlin bindings
RXminuS Apr 17, 2024
6ea28e3
Fix tests
RXminuS Apr 17, 2024
bbdb78f
Revert "Show loading screen early"
RXminuS Apr 17, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,12 @@ object Constants {
const val gitlab = "gitlab"
const val google = "google"
const val abort = "abort"
const val reload = "reload"
const val `web-sign-in-token` = "web-sign-in-token"
const val getUserContext = "getUserContext"
const val `show-search-result` = "show-search-result"
const val reset = "reset"
const val `attribution-search` = "attribution-search"
const val troubleshoot_reloadAuth = "troubleshoot/reloadAuth"
const val `tree-sitter` = "tree-sitter"
const val indentation = "indentation"
const val Automatic = "Automatic"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@file:Suppress("FunctionName", "ClassName", "unused", "EnumEntryName", "UnusedImport")
package com.sourcegraph.cody.protocol_generated

typealias FixupTaskID = String // One of:

Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ sealed class WebviewMessage {
"copy" -> context.deserialize<CopyWebviewMessage>(element, CopyWebviewMessage::class.java)
"auth" -> context.deserialize<AuthWebviewMessage>(element, AuthWebviewMessage::class.java)
"abort" -> context.deserialize<AbortWebviewMessage>(element, AbortWebviewMessage::class.java)
"reload" -> context.deserialize<ReloadWebviewMessage>(element, ReloadWebviewMessage::class.java)
"simplified-onboarding" -> context.deserialize<`simplified-onboardingWebviewMessage`>(element, `simplified-onboardingWebviewMessage`::class.java)
"getUserContext" -> context.deserialize<GetUserContextWebviewMessage>(element, GetUserContextWebviewMessage::class.java)
"search" -> context.deserialize<SearchWebviewMessage>(element, SearchWebviewMessage::class.java)
"show-search-result" -> context.deserialize<`show-search-resultWebviewMessage`>(element, `show-search-resultWebviewMessage`::class.java)
"reset" -> context.deserialize<ResetWebviewMessage>(element, ResetWebviewMessage::class.java)
"attribution-search" -> context.deserialize<`attribution-searchWebviewMessage`>(element, `attribution-searchWebviewMessage`::class.java)
"troubleshoot/reloadAuth" -> context.deserialize<Troubleshoot_reloadAuthWebviewMessage>(element, Troubleshoot_reloadAuthWebviewMessage::class.java)
else -> throw Exception("Unknown discriminator ${element}")
}
}
Expand Down Expand Up @@ -316,15 +316,6 @@ data class AbortWebviewMessage(
}
}

data class ReloadWebviewMessage(
val command: CommandEnum? = null, // Oneof: reload
) : WebviewMessage() {

enum class CommandEnum {
@SerializedName("reload") Reload,
}
}

data class `simplified-onboardingWebviewMessage`(
val command: CommandEnum? = null, // Oneof: simplified-onboarding
val onboardingKind: OnboardingKindEnum? = null, // Oneof: web-sign-in-token
Expand Down Expand Up @@ -389,3 +380,12 @@ data class `attribution-searchWebviewMessage`(
}
}

data class Troubleshoot_reloadAuthWebviewMessage(
val command: CommandEnum? = null, // Oneof: troubleshoot/reloadAuth
) : WebviewMessage() {

enum class CommandEnum {
@SerializedName("troubleshoot/reloadAuth") Troubleshoot_reloadAuth,
}
}

2 changes: 1 addition & 1 deletion lib/shared/src/sourcegraph-api/graphql/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -853,7 +853,7 @@ export class SourcegraphGraphQLAPIClient {

// make an anonymous request to the Testing API
private fetchSourcegraphTestingAPI<T>(body: Record<string, any>): Promise<T | Error> {
const url = 'http://localhost:49300/.api/testLogging'
const url = 'http://localhost:49300/.test/testLogging'
const headers = new Headers({
'Content-Type': 'application/json',
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { logError } from '../../logger'
import { isError } from '../../utils'

const MOCK_URL = 'http://localhost:49300'
const ENDPOINT = '/.api/mockEventRecording'
const ENDPOINT = '/.test/mockEventRecording'

/**
* MockServerTelemetryExporter exports events to a mock endpoint at
* http://localhost:49300/.api/mockEventRecording
* http://localhost:49300/.test/mockEventRecording
*/
export class MockServerTelemetryExporter implements TelemetryExporter {
constructor(private anonymousUserID: string) {}
Expand Down
28 changes: 28 additions & 0 deletions vscode/.storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Replace your-framework with the framework you are using (e.g., react, vue3)
import type { Preview } from '@storybook/react'

const preview: Preview = {
parameters: {
viewport: {
viewports: [
{
name: 'VSCode Normal Sidebar',
styles: { width: '400px', height: '800px' },
type: 'desktop',
},
{
name: 'VSCode Wide Sidebar',
styles: { width: '700px', height: '800px' },
type: 'desktop',
},
{
name: 'VSCode Tall Sidebar',
styles: { width: '500px', height: '1200px' },
type: 'desktop',
},
],
},
},
}

export default preview
2 changes: 2 additions & 0 deletions vscode/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ This is a log of all notable changes to Cody for VS Code. [Unreleased] changes a

- Generate Unit Tests: Fixed an issue where Cody would generate tests for the wrong code in the file. [pull/3759](https://github.com/sourcegraph/cody/pull/3759)
- Chat: Fixed an issue where changing the chat model did not update the token limit for the model. [pull/3762](https://github.com/sourcegraph/cody/pull/3762)
- Troubleshoot: Don't show SignIn page if the authentication error is because of network connectivity issues [pull/3750](https://github.com/sourcegraph/cody/pull/3750)

- Edit: Large file warnings for @-mentions are now updated dynamically as you add or remove them. [pull/3767](https://github.com/sourcegraph/cody/pull/3767)
- Generate Unit Tests: Improved quality for creating file names. [pull/3763](https://github.com/sourcegraph/cody/pull/3763)
- Custom Commands: Fixed an issue where newly added custom commands were not working when clicked in the sidebar tree view. [pull/3804](https://github.com/sourcegraph/cody/pull/3804)
Expand Down
7 changes: 6 additions & 1 deletion vscode/src/chat/ContextProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,12 @@ export class ContextProvider implements vscode.Disposable, ContextStatusProvider
await this.onConfigurationChange(newConfig)
// When logged out, user's endpoint will be set to null
const isLoggedOut = !authStatus.isLoggedIn && !authStatus.endpoint
const eventValue = isLoggedOut ? 'disconnected' : authStatus.isLoggedIn ? 'connected' : 'failed'
const isAuthError = authStatus?.showNetworkError || authStatus?.showInvalidAccessTokenError
const eventValue = isLoggedOut
? 'disconnected'
: authStatus.isLoggedIn && !isAuthError
? 'connected'
: 'failed'
switch (ContextEvent.Auth) {
case 'auth':
telemetryService.log(`${logPrefix(newConfig.agentIDE)}:Auth:${eventValue}`, undefined, {
Expand Down
26 changes: 19 additions & 7 deletions vscode/src/chat/chat-view/SidebarViewController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,6 @@ export class SidebarViewController implements vscode.WebviewViewProvider {
await vscode.commands.executeCommand(`cody.auth.${message.authKind}`)
break
}
case 'reload':
await this.authProvider.reloadAuthStatus()
telemetryService.log('CodyVSCodeExtension:authReloadButton:clicked', undefined, {
hasV2Event: true,
})
telemetryRecorder.recordEvent('cody.authReloadButton', 'clicked')
break
case 'event':
telemetryService.log(message.eventName, message.properties)
break
Expand Down Expand Up @@ -151,6 +144,25 @@ export class SidebarViewController implements vscode.WebviewViewProvider {
case 'show-page':
await vscode.commands.executeCommand('show-page', message.page)
break
case 'troubleshoot/reloadAuth': {
RXminuS marked this conversation as resolved.
Show resolved Hide resolved
await this.authProvider.reloadAuthStatus()
const nextAuth = this.authProvider.getAuthStatus()
telemetryService.log(
'CodyVSCodeExtension:troubleshoot:reloadAuth',
{
success: Boolean(nextAuth?.isLoggedIn),
},
{
hasV2Event: true,
}
)
telemetryRecorder.recordEvent('cody.troubleshoot', 'reloadAuth', {
metadata: {
success: nextAuth.isLoggedIn ? 1 : 0,
},
})
break
}
default:
this.handleError(new Error('Invalid request type from Webview'), 'system')
}
Expand Down
4 changes: 3 additions & 1 deletion vscode/src/chat/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ export type WebviewMessage =
authMethod?: AuthMethod
}
| { command: 'abort' }
| { command: 'reload' }
| {
command: 'simplified-onboarding'
onboardingKind: 'web-sign-in-token'
Expand All @@ -90,6 +89,9 @@ export type WebviewMessage =
command: 'attribution-search'
snippet: string
}
| {
command: 'troubleshoot/reloadAuth'
}
RXminuS marked this conversation as resolved.
Show resolved Hide resolved

/**
* A message sent from the extension host to the webview.
Expand Down
4 changes: 4 additions & 0 deletions vscode/src/services/AuthProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ export class AuthProvider {
public async init(): Promise<void> {
let lastEndpoint = localStorage?.getEndpoint() || this.config.serverEndpoint
let token = (await secretStorage.get(lastEndpoint || '')) || this.config.accessToken
logDebug(
'AuthProvider:init',
token?.trim() ? 'Token recovered from secretStorage' : 'No token found in secretStorage'
)
if (lastEndpoint === LOCAL_APP_URL.toString()) {
// If the user last signed in to app, which talks to dotcom, try
// signing them in to dotcom.
Expand Down
37 changes: 30 additions & 7 deletions vscode/src/services/SecretStorageProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ export async function getAccessToken(): Promise<string | null> {
}
}

export async function clearAccessToken(): Promise<void> {
await secretStorage.delete(CODY_ACCESS_TOKEN_SECRET)
}

interface SecretStorage {
get(key: string): Promise<string | undefined>
store(key: string, value: string): Promise<void>
Expand Down Expand Up @@ -120,12 +124,28 @@ export class VSCodeSecretStorage implements SecretStorage {
}

class InMemorySecretStorage implements SecretStorage {
private storage: Map<string, string>
private callbacks: ((key: string) => Promise<void>)[]

constructor() {
this.storage = new Map<string, string>()
this.callbacks = []
private storage: Map<string, string> = new Map<string, string>()
private callbacks: ((key: string) => Promise<void>)[] = []

constructor(initialState?: string | undefined, initialToken?: string | undefined) {
if (initialState) {
const parsedState = JSON.parse(initialState)
if (Array.isArray(parsedState)) {
for (const [key, value] of parsedState) {
this.storage.set(key, value)
}
} else {
throw new Error('Initial secret storage state must be an array of (key, value) entries')
}
}
if (initialToken) {
const parsedToken = JSON.parse(initialToken)
if (Array.isArray(parsedToken) && parsedToken.length === 2) {
this.storeToken(parsedToken[0], parsedToken[1])
} else {
throw new Error('Initial token must be an array with [endpoint, value]')
}
}
}

public async get(key: string): Promise<string | undefined> {
Expand Down Expand Up @@ -202,5 +222,8 @@ interface ConfigJson {
*/
export const secretStorage =
process.env.CODY_TESTING === 'true' || process.env.CODY_PROFILE_TEMP === 'true'
? new InMemorySecretStorage()
? new InMemorySecretStorage(
process.env.CODY_TESTING === 'true' ? process.env.TESTING_SECRET_STORAGE_STATE : undefined,
process.env.CODY_TESTING === 'true' ? process.env.TESTING_SECRET_STORAGE_TOKEN : undefined
)
: new VSCodeSecretStorage()
20 changes: 15 additions & 5 deletions vscode/src/services/StatusBar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,11 +263,21 @@ export function createStatusBar(): CodyStatusBar {
// Only show this if authStatus is present, otherwise you get a flash of
// yellow status bar icon when extension first loads but login hasn't
// initialized yet
if (authStatus && !authStatus.isLoggedIn) {
statusBarItem.text = '$(cody-logo-heavy) Sign In'
statusBarItem.tooltip = 'Sign in to get started with Cody'
statusBarItem.backgroundColor = new vscode.ThemeColor('statusBarItem.warningBackground')
return
if (authStatus) {
if (authStatus.showNetworkError) {
statusBarItem.text = '$(cody-logo-heavy) Connection Issues'
statusBarItem.tooltip = 'Resolve network issues for Cody to work again'
// statusBarItem.color = new vscode.ThemeColor('statusBarItem.errorForeground')
statusBarItem.backgroundColor = new vscode.ThemeColor('statusBarItem.errorBackground')
return
}
if (!authStatus.isLoggedIn) {
statusBarItem.text = '$(cody-logo-heavy) Sign In'
statusBarItem.tooltip = 'Sign in to get started with Cody'
// statusBarItem.color = new vscode.ThemeColor('statusBarItem.warningForeground')
statusBarItem.backgroundColor = new vscode.ThemeColor('statusBarItem.warningBackground')
return
}
}

if (errors.length > 0) {
Expand Down
5 changes: 2 additions & 3 deletions vscode/test/e2e/auth.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { expect } from '@playwright/test'

import { SERVER_URL, VALID_TOKEN } from '../fixtures/mock-server'

import { type ExpectedEvents, signOut, test } from './helpers'
Expand All @@ -26,7 +25,7 @@ test.extend<ExpectedEvents>({
],
})('requires a valid auth token and allows logouts', async ({ page, sidebar }) => {
await expect(page.getByText('Authentication failed.')).not.toBeVisible()
await sidebar.getByRole('button', { name: 'Sign In to Your Enterprise Instance' }).click()
await sidebar?.getByRole('button', { name: 'Sign In to Your Enterprise Instance' }).click()
await page.getByRole('option', { name: 'Sign In with URL and Access Token' }).click()
await page.getByRole('combobox', { name: 'input' }).fill(SERVER_URL)
await page.getByRole('combobox', { name: 'input' }).press('Enter')
Expand All @@ -35,7 +34,7 @@ test.extend<ExpectedEvents>({

await expect(page.getByRole('alert').getByText('Authentication failed.')).toBeVisible()

await sidebar.getByRole('button', { name: 'Sign In to Your Enterprise Instance' }).click()
await sidebar?.getByRole('button', { name: 'Sign In to Your Enterprise Instance' }).click()
await page.getByRole('option', { name: 'Sign In with URL and Access Token' }).click()
await page.getByRole('combobox', { name: 'input' }).fill(SERVER_URL)
await page.getByRole('combobox', { name: 'input' }).press('Enter')
Expand Down
9 changes: 8 additions & 1 deletion vscode/test/e2e/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ import { executeCommandInPalette } from './helpers'
// Sign into Cody with valid auth from the sidebar
export const sidebarSignin = async (
page: Page,
sidebar: Frame,
sidebar: Frame | null,
enableNotifications = false
): Promise<void> => {
if (sidebar === null) {
throw new Error('Sidebar is null, likely because preAuthenticate is `true`')
}
await sidebar.getByRole('button', { name: 'Sign In to Your Enterprise Instance' }).click()
await page.getByRole('option', { name: 'Sign In with URL and Access Token' }).click()
await page.getByRole('combobox', { name: 'input' }).fill(SERVER_URL)
Expand All @@ -21,6 +24,10 @@ export const sidebarSignin = async (
await disableNotifications(page)
}

await expectAuthenticated(page)
}

export async function expectAuthenticated(page: Page) {
await expect(page.getByText('Chat alongside your code, attach files,')).toBeVisible()
}

Expand Down
Loading
Loading