Skip to content

Commit 57e3dee

Browse files
author
Andrea Falzetti
committed
feat(jb): observe ports status and send notification
1 parent 6972cd5 commit 57e3dee

File tree

2 files changed

+115
-0
lines changed

2 files changed

+115
-0
lines changed

.gitpod.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,21 @@ ports:
3030
onOpen: ignore
3131
# Dev Theia
3232
- port: 13444
33+
- port: 8080
34+
onOpen: open-browser
35+
visibility: public
36+
- port: 8081
37+
onOpen: notify
38+
visibility: public
39+
- port: 8082
40+
onOpen: ignore
41+
- port: 8083
42+
onOpen: notify
3343
tasks:
44+
- command: npx --yes live-server .
45+
- command: npx --yes live-server --port 8081 .
46+
- command: npx --yes live-server --port 8082 .
47+
- command: npx --yes live-server --port 8083 .
3448
- name: Add Harvester kubeconfig
3549
command: |
3650
./dev/preview/util/download-and-merge-harvester-kubeconfig.sh

components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/GitpodManager.kt

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
package io.gitpod.jetbrains.remote
66

7+
import com.intellij.ide.BrowserUtil
78
import com.intellij.ide.plugins.PluginManagerCore
89
import com.intellij.notification.NotificationAction
910
import com.intellij.notification.NotificationGroupManager
@@ -23,6 +24,10 @@ import io.gitpod.jetbrains.remote.utils.Retrier.retry
2324
import io.gitpod.supervisor.api.*
2425
import io.gitpod.supervisor.api.Info.WorkspaceInfoResponse
2526
import io.gitpod.supervisor.api.Notification.*
27+
import io.gitpod.supervisor.api.Status.OnPortExposedAction
28+
import io.gitpod.supervisor.api.Status.PortsStatus
29+
import io.gitpod.supervisor.api.Status.PortsStatusRequest
30+
import io.gitpod.supervisor.api.Status.PortsStatusResponse
2631
import io.grpc.ManagedChannel
2732
import io.grpc.ManagedChannelBuilder
2833
import io.grpc.stub.ClientCallStreamObserver
@@ -46,6 +51,7 @@ import java.util.concurrent.CancellationException
4651
import java.util.concurrent.CompletableFuture
4752
import javax.websocket.DeploymentException
4853

54+
4955
@Service
5056
class GitpodManager : Disposable {
5157

@@ -199,6 +205,101 @@ class GitpodManager : Disposable {
199205
}
200206
}
201207

208+
private val portsObserveJob = GlobalScope.launch {
209+
if (application.isHeadlessEnvironment) {
210+
return@launch
211+
}
212+
213+
// Ignore JetBrains default ports
214+
val ignorePorts = listOf(5990, 5991, 6679, 6942, 6943)
215+
val portsStatus = hashMapOf<Int, PortsStatus>()
216+
217+
val status = StatusServiceGrpc.newStub(supervisorChannel)
218+
while (isActive) {
219+
try {
220+
val f = CompletableFuture<Void>()
221+
status.portsStatus(
222+
PortsStatusRequest.newBuilder().setObserve(true).build(),
223+
object : ClientResponseObserver<PortsStatusRequest, PortsStatusResponse> {
224+
override fun beforeStart(requestStream: ClientCallStreamObserver<PortsStatusRequest>) {
225+
lifetime.onTerminationOrNow {
226+
requestStream.cancel(null, null)
227+
}
228+
}
229+
230+
override fun onNext(ps: PortsStatusResponse) {
231+
for (port in ps.portsList) {
232+
if (ignorePorts.contains(port.localPort)) {
233+
continue
234+
}
235+
236+
val isNew = !portsStatus.containsKey(port.localPort)
237+
238+
if (isNew) {
239+
portsStatus[port.localPort] = port
240+
//continue
241+
}
242+
243+
//println("DEBUG[${port.localPort}] -> before hasExposed(${portsStatus[port.localPort]?.hasExposed().toString()}) -> now hasExposed(${port.hasExposed()})")
244+
245+
val wasAlreadyExposed = !portsStatus[port.localPort]?.hasExposed()!!
246+
if ((isNew || wasAlreadyExposed) && port.hasExposed()) {
247+
if (port.exposed.onExposed.number == OnPortExposedAction.ignore_VALUE) {
248+
continue
249+
}
250+
251+
if (port.exposed.onExposed.number == OnPortExposedAction.open_browser_VALUE) {
252+
BrowserUtil.browse(port.exposed.url)
253+
continue
254+
}
255+
256+
if (port.exposed.onExposed.number == OnPortExposedAction.open_preview_VALUE) {
257+
BrowserUtil.browse(port.exposed.url)
258+
continue
259+
}
260+
261+
if (port.served && !port.exposed.url.isNullOrEmpty()) {
262+
println("Show port notification‼️‼️ ${port.localPort}")
263+
val message =
264+
"Your application running on port " + port.localPort + " is available."
265+
println(message)
266+
val notification =
267+
notificationGroup.createNotification(message, NotificationType.INFORMATION)
268+
// TODO(andreafalzetti): add analytics event
269+
val lambda = { BrowserUtil.browse(port.exposed.url) }
270+
val action = NotificationAction.createSimpleExpiring("Open in browser", lambda)
271+
notification.addAction(action)
272+
notification.notify(null)
273+
}
274+
}
275+
}
276+
277+
}
278+
279+
override fun onError(t: Throwable) {
280+
f.completeExceptionally(t)
281+
}
282+
283+
override fun onCompleted() {
284+
f.complete(null)
285+
}
286+
})
287+
f.await()
288+
} catch (t: Throwable) {
289+
if (t is CancellationException) {
290+
throw t
291+
}
292+
thisLogger().error("gitpod: failed to stream ports status: ", t)
293+
}
294+
delay(1000)
295+
}
296+
}
297+
init {
298+
lifetime.onTerminationOrNow {
299+
portsObserveJob.cancel()
300+
}
301+
}
302+
202303
val pendingInfo = CompletableFuture<WorkspaceInfoResponse>()
203304
private val infoJob = GlobalScope.launch {
204305
if (application.isHeadlessEnvironment) {

0 commit comments

Comments
 (0)