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

Commit fa4e56f

Browse files
committed
Show an error notification when misconfigured
1 parent 3eafee6 commit fa4e56f

File tree

2 files changed

+163
-21
lines changed

2 files changed

+163
-21
lines changed

package.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,29 @@
5858
"description": "Submit code intel feedback",
5959
"iconURL": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTExLjk5IDJDNi40NyAyIDIgNi40OCAyIDEyczQuNDcgMTAgOS45OSAxMEMxNy41MiAyMiAyMiAxNy41MiAyMiAxMlMxNy41MiAyIDExLjk5IDJ6TTEyIDIwYy00LjQyIDAtOC0zLjU4LTgtOHMzLjU4LTggOC04IDggMy41OCA4IDgtMy41OCA4LTggOHptMy41LTljLjgzIDAgMS41LS42NyAxLjUtMS41UzE2LjMzIDggMTUuNSA4IDE0IDguNjcgMTQgOS41cy42NyAxLjUgMS41IDEuNXptLTcgMGMuODMgMCAxLjUtLjY3IDEuNS0xLjVTOS4zMyA4IDguNSA4IDcgOC42NyA3IDkuNSA3LjY3IDExIDguNSAxMXptMy41IDYuNWMyLjMzIDAgNC4zMS0xLjQ2IDUuMTEtMy41SDYuODljLjggMi4wNCAyLjc4IDMuNSA1LjExIDMuNXoiLz48L3N2Zz4="
6060
}
61+
},
62+
{
63+
"id": "error",
64+
"command": "open",
65+
"title": "${get(context, `codeIntel.error.message`)}",
66+
"commandArguments": [
67+
"${get(context, `codeIntel.error.link`)}"
68+
],
69+
"actionItem": {
70+
"description": "${get(context, `codeIntel.error.message`)}",
71+
"iconURL": "data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgaWQ9ImVsXzZNY29hYTdIbCI+PHN0eWxlPkAtd2Via2l0LWtleWZyYW1lcyBrZl9lbF9rSEpMSGNuRkJfX2FuXzYyTk9qRWRVanswJXtvcGFjaXR5OiAxO301MCV7b3BhY2l0eTogMC41O30xMDAle29wYWNpdHk6IDE7fX1Aa2V5ZnJhbWVzIGtmX2VsX2tISkxIY25GQl9fYW5fNjJOT2pFZFVqezAle29wYWNpdHk6IDE7fTUwJXtvcGFjaXR5OiAwLjU7fTEwMCV7b3BhY2l0eTogMTt9fSNlbF82TWNvYWE3SGwgKnstd2Via2l0LWFuaW1hdGlvbi1kdXJhdGlvbjogMnM7YW5pbWF0aW9uLWR1cmF0aW9uOiAyczstd2Via2l0LWFuaW1hdGlvbi1pdGVyYXRpb24tY291bnQ6IGluZmluaXRlO2FuaW1hdGlvbi1pdGVyYXRpb24tY291bnQ6IGluZmluaXRlOy13ZWJraXQtYW5pbWF0aW9uLXRpbWluZy1mdW5jdGlvbjogY3ViaWMtYmV6aWVyKDAsIDAsIDEsIDEpO2FuaW1hdGlvbi10aW1pbmctZnVuY3Rpb246IGN1YmljLWJlemllcigwLCAwLCAxLCAxKTt9I2VsX055VUdfdXBrRWZ7ZmlsbDogbm9uZTt9I2VsX2tISkxIY25GQl97ZmlsbDogcmVkOy13ZWJraXQtYW5pbWF0aW9uLWZpbGwtbW9kZTogYmFja3dhcmRzO2FuaW1hdGlvbi1maWxsLW1vZGU6IGJhY2t3YXJkcztvcGFjaXR5OiAxOy13ZWJraXQtYW5pbWF0aW9uLW5hbWU6IGtmX2VsX2tISkxIY25GQl9fYW5fNjJOT2pFZFVqO2FuaW1hdGlvbi1uYW1lOiBrZl9lbF9rSEpMSGNuRkJfX2FuXzYyTk9qRWRVajstd2Via2l0LWFuaW1hdGlvbi10aW1pbmctZnVuY3Rpb246IGN1YmljLWJlemllcigwLCAwLCAxLCAxKTthbmltYXRpb24tdGltaW5nLWZ1bmN0aW9uOiBjdWJpYy1iZXppZXIoMCwgMCwgMSwgMSk7fTwvc3R5bGU+PHBhdGggZD0iTTAgMGgyNHYyNEgweiIgaWQ9ImVsX055VUdfdXBrRWYiLz48cGF0aCBkPSJNMTIgMkM2LjQ4IDIgMiA2LjQ4IDIgMTJzNC40OCAxMCAxMCAxMCAxMC00LjQ4IDEwLTEwUzE3LjUyIDIgMTIgMnptMSAxNWgtMnYtNmgydjZ6bTAtOGgtMlY3aDJ2MnoiIGlkPSJlbF9rSEpMSGNuRkJfIi8+PC9zdmc+"
72+
}
6173
}
6274
],
6375
"menus": {
6476
"editor/title": [
6577
{
6678
"action": "feedback",
6779
"when": "showFeedback"
80+
},
81+
{
82+
"action": "error",
83+
"when": "showError"
6884
}
6985
],
7086
"panel/toolbar": [

src/lang-go.ts

Lines changed: 147 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
switchMap,
2222
take,
2323
finalize,
24+
filter,
2425
} from 'rxjs/operators'
2526

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

191192
async function connectAndInitialize(
192-
address: string,
193+
address: URL,
193194
root: URL,
194195
token: string | undefined
195196
): Promise<rpc.MessageConnection> {
196-
const connectingToGoLangserverHelp = [
197-
`Unable to connect to the Go language server at ${address}.`,
198-
`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).`,
199-
`Read the full documentation for more information: https://github.com/sourcegraph/sourcegraph-go`,
200-
].join('\n')
201-
202-
const connectingToSourcegraphHelp = [
203-
`The Go language server running on ${address} was unable to fetch repository contents from Sourcegraph running on ${sourcegraphURL()}.`,
204-
`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).`,
205-
`Read the full documentation for more information: https://github.com/sourcegraph/sourcegraph-go`,
206-
].join('\n')
207-
208-
const connection = (await new Promise((resolve, reject) => {
197+
const connection = (await new Promise<wsrpc.MessageConnection>((resolve, reject) => {
209198
try {
210-
const webSocket = new WebSocket(address)
199+
const webSocket = new WebSocket(address.href)
211200
const conn = createWebSocketConnection(wsrpc.toSocket(webSocket), new ConsoleLogger())
212201
webSocket.addEventListener('open', () => resolve(conn))
213-
webSocket.addEventListener('error', event => reject(new Error(connectingToGoLangserverHelp)))
202+
webSocket.addEventListener('error', event => {
203+
notifyUnableToConnectToLanguageServer(address)
204+
reject(event)
205+
})
214206
} catch (e) {
215207
if ('message' in e && /Failed to construct/.test(e.message)) {
216-
console.error(connectingToGoLangserverHelp)
208+
notifyUnableToConnectToLanguageServer(address)
217209
}
218210
reject(e)
219211
}
@@ -246,8 +238,11 @@ async function connectAndInitialize(
246238
}
247239
)
248240
} catch (e) {
249-
if ('message' in e && (/no such host/.test(e.message) || /i\/o timeout/.test(e.message))) {
250-
console.error(connectingToSourcegraphHelp)
241+
if (
242+
'message' in e &&
243+
(/no such host/.test(e.message) || /i\/o timeout/.test(e.message) || /connection refused/.test(e.message))
244+
) {
245+
notifyLanguageServerUnableToConnectToSourcegraph()
251246
}
252247
throw e
253248
}
@@ -286,7 +281,7 @@ function repoNameFromDoc(doc: sourcegraph.TextDocument): string {
286281
* Internally, this maintains a mapping from rootURI to the connection
287282
* associated with that rootURI, so it supports multiple roots (untested).
288283
*/
289-
function mkSendRequest(address: string, token: string | undefined): Observable<SendRequest> {
284+
function mkSendRequest(address: URL, token: string | undefined): Observable<SendRequest> {
290285
const rootURIToConnection: { [rootURI: string]: Promise<rpc.MessageConnection> } = {}
291286
async function connectionFor(root: URL): Promise<rpc.MessageConnection> {
292287
if (rootURIToConnection[root.href]) {
@@ -563,6 +558,121 @@ function positionParams(doc: sourcegraph.TextDocument, pos: sourcegraph.Position
563558
}
564559
}
565560

561+
function notifyUnableToConnectToLanguageServer(address: URL) {
562+
sourcegraph.internal.updateContext({
563+
showError: true,
564+
'codeIntel.error.message': 'Error in language server setup. Click to open documentation.',
565+
'codeIntel.error.link': 'https://sourcegraph.com/extensions/sourcegraph/go',
566+
})
567+
const portByProtocol: { [protocol: string]: string } = { 'wss:': '443', 'ws:': '80' }
568+
sourcegraph.app.activeWindow!.showNotification(
569+
[
570+
'**❌ Unable to access to the Go language server**',
571+
'',
572+
'Your browser could not access:',
573+
'',
574+
'```',
575+
`"go.serverUrl": "${address}"`,
576+
'```',
577+
'',
578+
...(['ws:', 'wss:'].includes(address.protocol)
579+
? [
580+
'Troubleshoot using these commands:',
581+
'',
582+
`- **\`ping ${address.hostname}\`**`,
583+
`- **\`telnet ${address.hostname} ${address.port || portByProtocol[address.protocol]}\`**`,
584+
'',
585+
]
586+
: [
587+
`The protocol **\`${
588+
address.protocol
589+
}\`** is invalid. Only **\`ws:\`** or **\`wss:\`** are valid.`,
590+
'',
591+
]),
592+
'',
593+
`To apply the fix:`,
594+
'',
595+
`- Correct the value in your [global settings](${
596+
sourcegraph.internal.sourcegraphURL
597+
}site-admin/global-settings).`,
598+
'',
599+
'To temporarily disable:',
600+
'',
601+
`- Comment out **\`go.serverUrl\`** in your [global settings](${
602+
sourcegraph.internal.sourcegraphURL
603+
}site-admin/global-settings).`,
604+
].join('\n')
605+
)
606+
}
607+
608+
function notifyLanguageServerUnableToConnectToSourcegraph() {
609+
sourcegraph.internal.updateContext({
610+
showError: true,
611+
'codeIntel.error.message': 'Error in language server setup. Click to open documentation.',
612+
'codeIntel.error.link': 'https://sourcegraph.com/extensions/sourcegraph/go',
613+
})
614+
sourcegraph.app.activeWindow!.showNotification(
615+
[
616+
'**❌ Error in Go language server setup**',
617+
'',
618+
`The Go language server was unable to fetch repository contents from Sourcegraph at:`,
619+
'',
620+
'```',
621+
`"go.sourcegraphUrl": "${sourcegraphURL()}"`,
622+
'```',
623+
'',
624+
'To troubleshoot this, **`docker exec`** into the go-langserver container and run:',
625+
'',
626+
`- **\`ping ${sourcegraphURL().hostname}\`**`,
627+
`- **\`curl ${sourcegraphURL()}\`**`,
628+
'',
629+
`To apply the fix:`,
630+
'',
631+
`- Correct the value in your [global settings](${
632+
sourcegraph.internal.sourcegraphURL
633+
}site-admin/global-settings).`,
634+
'',
635+
'To temporarily disable:',
636+
'',
637+
`- Comment out **\`go.serverUrl\`** in your [global settings](${
638+
sourcegraph.internal.sourcegraphURL
639+
}site-admin/global-settings).`,
640+
].join('\n')
641+
)
642+
}
643+
644+
async function canary(address: URL): Promise<void> {
645+
const data = await queryGraphQL('{currentUser{siteAdmin}}')
646+
if (!data || !data.currentUser || data.currentUser.siteAdmin === undefined) {
647+
console.log(
648+
'Failed to determine whether or not the current user is an admin. Check the Network tab to see what went wrong.'
649+
)
650+
return
651+
}
652+
if (!data.currentUser.siteAdmin) {
653+
// Don't show these notifications to normal users because they don't
654+
// have access to global settings, and therefore can't fix this for all
655+
// users.
656+
return
657+
}
658+
659+
try {
660+
await new Promise<void>((resolve, reject) => {
661+
let webSocket: WebSocket
662+
try {
663+
webSocket = new WebSocket(address.href)
664+
} catch (e) {
665+
notifyUnableToConnectToLanguageServer(address)
666+
return
667+
}
668+
webSocket.addEventListener('open', () => resolve())
669+
webSocket.addEventListener('error', error => reject(error))
670+
})
671+
} catch (e) {
672+
notifyUnableToConnectToLanguageServer(address)
673+
}
674+
}
675+
566676
/**
567677
* Uses WebSockets to communicate with a language server.
568678
*/
@@ -574,7 +684,23 @@ export async function activateUsingWebSockets(ctx: sourcegraph.ExtensionContext)
574684
settings.next(sourcegraph.configuration.get<Settings>().value)
575685
})
576686
)
577-
const langserverAddress = settings.pipe(map(settings => settings['go.serverUrl']))
687+
const langserverAddress = settings.pipe(
688+
map(settings => settings['go.serverUrl']),
689+
concatMap(address => {
690+
if (address === undefined) {
691+
return [undefined]
692+
}
693+
try {
694+
return [new URL(address)]
695+
} catch (e) {
696+
return []
697+
}
698+
})
699+
)
700+
701+
ctx.subscriptions.add(
702+
langserverAddress.pipe(filter((x: URL | undefined): x is URL => Boolean(x))).subscribe(url => canary(url))
703+
)
578704

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

0 commit comments

Comments
 (0)