Skip to content
This repository has been archived by the owner on Feb 22, 2020. It is now read-only.

Commit

Permalink
Show an error notification when misconfigured
Browse files Browse the repository at this point in the history
  • Loading branch information
chrismwendt committed May 4, 2019
1 parent 3eafee6 commit 98395b8
Show file tree
Hide file tree
Showing 2 changed files with 163 additions and 21 deletions.
16 changes: 16 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,29 @@
"description": "Submit code intel feedback",
"iconURL": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTExLjk5IDJDNi40NyAyIDIgNi40OCAyIDEyczQuNDcgMTAgOS45OSAxMEMxNy41MiAyMiAyMiAxNy41MiAyMiAxMlMxNy41MiAyIDExLjk5IDJ6TTEyIDIwYy00LjQyIDAtOC0zLjU4LTgtOHMzLjU4LTggOC04IDggMy41OCA4IDgtMy41OCA4LTggOHptMy41LTljLjgzIDAgMS41LS42NyAxLjUtMS41UzE2LjMzIDggMTUuNSA4IDE0IDguNjcgMTQgOS41cy42NyAxLjUgMS41IDEuNXptLTcgMGMuODMgMCAxLjUtLjY3IDEuNS0xLjVTOS4zMyA4IDguNSA4IDcgOC42NyA3IDkuNSA3LjY3IDExIDguNSAxMXptMy41IDYuNWMyLjMzIDAgNC4zMS0xLjQ2IDUuMTEtMy41SDYuODljLjggMi4wNCAyLjc4IDMuNSA1LjExIDMuNXoiLz48L3N2Zz4="
}
},
{
"id": "error",
"command": "open",
"title": "${get(context, `codeIntel.error.message`)}",
"commandArguments": [
"${get(context, `codeIntel.error.link`)}"
],
"actionItem": {
"description": "${get(context, `codeIntel.error.message`)}",
"iconURL": "data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgaWQ9ImVsXzZNY29hYTdIbCI+PHN0eWxlPkAtd2Via2l0LWtleWZyYW1lcyBrZl9lbF9rSEpMSGNuRkJfX2FuXzYyTk9qRWRVanswJXtvcGFjaXR5OiAxO301MCV7b3BhY2l0eTogMC41O30xMDAle29wYWNpdHk6IDE7fX1Aa2V5ZnJhbWVzIGtmX2VsX2tISkxIY25GQl9fYW5fNjJOT2pFZFVqezAle29wYWNpdHk6IDE7fTUwJXtvcGFjaXR5OiAwLjU7fTEwMCV7b3BhY2l0eTogMTt9fSNlbF82TWNvYWE3SGwgKnstd2Via2l0LWFuaW1hdGlvbi1kdXJhdGlvbjogMnM7YW5pbWF0aW9uLWR1cmF0aW9uOiAyczstd2Via2l0LWFuaW1hdGlvbi1pdGVyYXRpb24tY291bnQ6IGluZmluaXRlO2FuaW1hdGlvbi1pdGVyYXRpb24tY291bnQ6IGluZmluaXRlOy13ZWJraXQtYW5pbWF0aW9uLXRpbWluZy1mdW5jdGlvbjogY3ViaWMtYmV6aWVyKDAsIDAsIDEsIDEpO2FuaW1hdGlvbi10aW1pbmctZnVuY3Rpb246IGN1YmljLWJlemllcigwLCAwLCAxLCAxKTt9I2VsX055VUdfdXBrRWZ7ZmlsbDogbm9uZTt9I2VsX2tISkxIY25GQl97ZmlsbDogcmVkOy13ZWJraXQtYW5pbWF0aW9uLWZpbGwtbW9kZTogYmFja3dhcmRzO2FuaW1hdGlvbi1maWxsLW1vZGU6IGJhY2t3YXJkcztvcGFjaXR5OiAxOy13ZWJraXQtYW5pbWF0aW9uLW5hbWU6IGtmX2VsX2tISkxIY25GQl9fYW5fNjJOT2pFZFVqO2FuaW1hdGlvbi1uYW1lOiBrZl9lbF9rSEpMSGNuRkJfX2FuXzYyTk9qRWRVajstd2Via2l0LWFuaW1hdGlvbi10aW1pbmctZnVuY3Rpb246IGN1YmljLWJlemllcigwLCAwLCAxLCAxKTthbmltYXRpb24tdGltaW5nLWZ1bmN0aW9uOiBjdWJpYy1iZXppZXIoMCwgMCwgMSwgMSk7fTwvc3R5bGU+PHBhdGggZD0iTTAgMGgyNHYyNEgweiIgaWQ9ImVsX055VUdfdXBrRWYiLz48cGF0aCBkPSJNMTIgMkM2LjQ4IDIgMiA2LjQ4IDIgMTJzNC40OCAxMCAxMCAxMCAxMC00LjQ4IDEwLTEwUzE3LjUyIDIgMTIgMnptMSAxNWgtMnYtNmgydjZ6bTAtOGgtMlY3aDJ2MnoiIGlkPSJlbF9rSEpMSGNuRkJfIi8+PC9zdmc+"
}
}
],
"menus": {
"editor/title": [
{
"action": "feedback",
"when": "showFeedback"
},
{
"action": "error",
"when": "showError"
}
],
"panel/toolbar": [
Expand Down
168 changes: 147 additions & 21 deletions src/lang-go.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
switchMap,
take,
finalize,
filter,
} from 'rxjs/operators'

import { ConsoleLogger, createWebSocketConnection } from '@sourcegraph/vscode-ws-jsonrpc'
Expand Down Expand Up @@ -189,31 +190,22 @@ async function tryToCreateAccessToken(): Promise<string | undefined> {
}

async function connectAndInitialize(
address: string,
address: URL,
root: URL,
token: string | undefined
): Promise<rpc.MessageConnection> {
const connectingToGoLangserverHelp = [
`Unable to connect to the Go language server at ${address}.`,
`Make sure ${'go.address' as keyof Settings} in your Sourcegraph settings is set to the address of the language server (e.g. wss://sourcegraph.example.com/go).`,
`Read the full documentation for more information: https://github.com/sourcegraph/sourcegraph-go`,
].join('\n')

const connectingToSourcegraphHelp = [
`The Go language server running on ${address} was unable to fetch repository contents from Sourcegraph running on ${sourcegraphURL()}.`,
`Make sure ${'go.sourcegraphUrl' as keyof Settings} in your settings is set to the address of Sourcegraph from the perspective of the language server (e.g. http://sourcegraph-frontend:30080 when running in Kubernetes).`,
`Read the full documentation for more information: https://github.com/sourcegraph/sourcegraph-go`,
].join('\n')

const connection = (await new Promise((resolve, reject) => {
const connection = (await new Promise<wsrpc.MessageConnection>((resolve, reject) => {
try {
const webSocket = new WebSocket(address)
const webSocket = new WebSocket(address.href)
const conn = createWebSocketConnection(wsrpc.toSocket(webSocket), new ConsoleLogger())
webSocket.addEventListener('open', () => resolve(conn))
webSocket.addEventListener('error', event => reject(new Error(connectingToGoLangserverHelp)))
webSocket.addEventListener('error', event => {
notifyUnableToConnectToLanguageServer(address)
reject(event)
})
} catch (e) {
if ('message' in e && /Failed to construct/.test(e.message)) {
console.error(connectingToGoLangserverHelp)
notifyUnableToConnectToLanguageServer(address)
}
reject(e)
}
Expand Down Expand Up @@ -246,8 +238,11 @@ async function connectAndInitialize(
}
)
} catch (e) {
if ('message' in e && (/no such host/.test(e.message) || /i\/o timeout/.test(e.message))) {
console.error(connectingToSourcegraphHelp)
if (
'message' in e &&
(/no such host/.test(e.message) || /i\/o timeout/.test(e.message) || /connection refused/.test(e.message))
) {
notifyLanguageServerUnableToConnectToSourcegraph()
}
throw e
}
Expand Down Expand Up @@ -286,7 +281,7 @@ function repoNameFromDoc(doc: sourcegraph.TextDocument): string {
* Internally, this maintains a mapping from rootURI to the connection
* associated with that rootURI, so it supports multiple roots (untested).
*/
function mkSendRequest(address: string, token: string | undefined): Observable<SendRequest> {
function mkSendRequest(address: URL, token: string | undefined): Observable<SendRequest> {
const rootURIToConnection: { [rootURI: string]: Promise<rpc.MessageConnection> } = {}
async function connectionFor(root: URL): Promise<rpc.MessageConnection> {
if (rootURIToConnection[root.href]) {
Expand Down Expand Up @@ -563,6 +558,121 @@ function positionParams(doc: sourcegraph.TextDocument, pos: sourcegraph.Position
}
}

function notifyUnableToConnectToLanguageServer(address: URL): void {
sourcegraph.internal.updateContext({
showError: true,
'codeIntel.error.message': 'Error in language server setup. Click to open documentation.',
'codeIntel.error.link': 'https://sourcegraph.com/extensions/sourcegraph/go',
})
const portByProtocol: { [protocol: string]: string } = { 'wss:': '443', 'ws:': '80' }
sourcegraph.app.activeWindow!.showNotification(
[
'**❌ Unable to access to the Go language server**',
'',
'Your browser could not access:',
'',
'```',
`"go.serverUrl": "${address}"`,
'```',
'',
...(['ws:', 'wss:'].includes(address.protocol)
? [
'Troubleshoot using these commands:',
'',
`- **\`ping ${address.hostname}\`**`,
`- **\`telnet ${address.hostname} ${address.port || portByProtocol[address.protocol]}\`**`,
'',
]
: [
`The protocol **\`${
address.protocol
}\`** is invalid. Only **\`ws:\`** or **\`wss:\`** are valid.`,
'',
]),
'',
`To apply the fix:`,
'',
`- Correct the value in your [global settings](${
sourcegraph.internal.sourcegraphURL
}site-admin/global-settings).`,
'',
'To temporarily disable:',
'',
`- Comment out **\`go.serverUrl\`** in your [global settings](${
sourcegraph.internal.sourcegraphURL
}site-admin/global-settings).`,
].join('\n')
)
}

function notifyLanguageServerUnableToConnectToSourcegraph(): void {
sourcegraph.internal.updateContext({
showError: true,
'codeIntel.error.message': 'Error in language server setup. Click to open documentation.',
'codeIntel.error.link': 'https://sourcegraph.com/extensions/sourcegraph/go',
})
sourcegraph.app.activeWindow!.showNotification(
[
'**❌ Error in Go language server setup**',
'',
`The Go language server was unable to fetch repository contents from Sourcegraph at:`,
'',
'```',
`"go.sourcegraphUrl": "${sourcegraphURL()}"`,
'```',
'',
'To troubleshoot this, **`docker exec`** into the go-langserver container and run:',
'',
`- **\`ping ${sourcegraphURL().hostname}\`**`,
`- **\`curl ${sourcegraphURL()}\`**`,
'',
`To apply the fix:`,
'',
`- Correct the value in your [global settings](${
sourcegraph.internal.sourcegraphURL
}site-admin/global-settings).`,
'',
'To temporarily disable:',
'',
`- Comment out **\`go.serverUrl\`** in your [global settings](${
sourcegraph.internal.sourcegraphURL
}site-admin/global-settings).`,
].join('\n')
)
}

async function canary(address: URL): Promise<void> {
const data = await queryGraphQL('{currentUser{siteAdmin}}')
if (!data || !data.currentUser || data.currentUser.siteAdmin === undefined) {
console.log(
'Failed to determine whether or not the current user is an admin. Check the Network tab to see what went wrong.'
)
return
}
if (!data.currentUser.siteAdmin) {
// Don't show these notifications to normal users because they don't
// have access to global settings, and therefore can't fix this for all
// users.
return
}

try {
await new Promise<void>((resolve, reject) => {
let webSocket: WebSocket
try {
webSocket = new WebSocket(address.href)
} catch (e) {
notifyUnableToConnectToLanguageServer(address)
return
}
webSocket.addEventListener('open', () => resolve())
webSocket.addEventListener('error', error => reject(error))
})
} catch (e) {
notifyUnableToConnectToLanguageServer(address)
}
}

/**
* Uses WebSockets to communicate with a language server.
*/
Expand All @@ -574,7 +684,23 @@ export async function activateUsingWebSockets(ctx: sourcegraph.ExtensionContext)
settings.next(sourcegraph.configuration.get<Settings>().value)
})
)
const langserverAddress = settings.pipe(map(settings => settings['go.serverUrl']))
const langserverAddress = settings.pipe(
map(settings => settings['go.serverUrl']),
concatMap(address => {
if (address === undefined) {
return [undefined]
}
try {
return [new URL(address)]
} catch (e) {
return []
}
})
)

ctx.subscriptions.add(
langserverAddress.pipe(filter((x: URL | undefined): x is URL => Boolean(x))).subscribe(url => canary(url))
)

const NO_ADDRESS_ERROR = `To get Go code intelligence, add "${'go.address' as keyof Settings}": "wss://example.com" to your settings.`

Expand Down

0 comments on commit 98395b8

Please sign in to comment.