Skip to content

Commit

Permalink
feat(jb): observe ports status and send notification
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrea Falzetti committed May 26, 2022
1 parent 6972cd5 commit 5ab5547
Showing 1 changed file with 132 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@

package io.gitpod.jetbrains.remote

import com.intellij.openapi.client.ClientSessionsManager
import org.jetbrains.ide.RestService
import com.intellij.codeWithMe.ClientId
import com.intellij.ide.BrowserUtil
import com.intellij.ide.plugins.PluginManagerCore
import com.intellij.notification.NotificationAction
import com.intellij.notification.NotificationGroupManager
import com.intellij.notification.NotificationType
import com.intellij.openapi.Disposable
import com.intellij.openapi.client.ClientProjectSession
import com.intellij.openapi.client.ClientSession
import com.intellij.openapi.components.Service
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.extensions.PluginId
Expand All @@ -23,6 +29,10 @@ import io.gitpod.jetbrains.remote.utils.Retrier.retry
import io.gitpod.supervisor.api.*
import io.gitpod.supervisor.api.Info.WorkspaceInfoResponse
import io.gitpod.supervisor.api.Notification.*
import io.gitpod.supervisor.api.Status.OnPortExposedAction
import io.gitpod.supervisor.api.Status.PortsStatus
import io.gitpod.supervisor.api.Status.PortsStatusRequest
import io.gitpod.supervisor.api.Status.PortsStatusResponse
import io.grpc.ManagedChannel
import io.grpc.ManagedChannelBuilder
import io.grpc.stub.ClientCallStreamObserver
Expand All @@ -46,6 +56,7 @@ import java.util.concurrent.CancellationException
import java.util.concurrent.CompletableFuture
import javax.websocket.DeploymentException


@Service
class GitpodManager : Disposable {

Expand Down Expand Up @@ -199,6 +210,127 @@ class GitpodManager : Disposable {
}
}

private val portsObserveJob = GlobalScope.launch {
if (application.isHeadlessEnvironment) {
return@launch
}

// Ignore ports that aren't actually used by the user (e.g. ports used internally by JetBrains IDEs)
val ignorePorts = listOf(5990, 5991, 6942, 6943)
val portsStatus = hashMapOf<Int, PortsStatus>()

val status = StatusServiceGrpc.newStub(supervisorChannel)
while (isActive) {
try {
val project = RestService.getLastFocusedOrOpenedProject()

var session: ClientSession? = null
if (project != null) {
session = ClientSessionsManager.getProjectSessions(project, false).first()
}
if (session == null) {
session = ClientSessionsManager.getAppSessions(false).first()
}

val gitpodClientProjectSessionTracker = GitpodClientProjectSessionTracker(session as ClientProjectSession)

val f = CompletableFuture<Void>()
status.portsStatus(
PortsStatusRequest.newBuilder().setObserve(true).build(),
object : ClientResponseObserver<PortsStatusRequest, PortsStatusResponse> {

override fun beforeStart(requestStream: ClientCallStreamObserver<PortsStatusRequest>) {
lifetime.onTerminationOrNow {
requestStream.cancel(null, null)
}
}

override fun onNext(ps: PortsStatusResponse) {
for (port in ps.portsList) {
// Avoiding undesired notifications
if (ignorePorts.contains(port.localPort)) {
continue
}

val hasPreviousStatus = portsStatus.containsKey(port.localPort)

if (!hasPreviousStatus) {
portsStatus[port.localPort] = port
}

val wasServed = portsStatus[port.localPort]?.served!!
val wasExposed = portsStatus[port.localPort]?.hasExposed()!!
val wasServedExposed = wasServed && wasExposed
val isServedExposed = port.served && port.hasExposed()

// If the initial update received shows that the port is served and exposed, then notify
val isFirstUpdate = !hasPreviousStatus && wasServedExposed && isServedExposed

// If the port changes its status to served and exposed, notify the user
val shouldSendNotification = isFirstUpdate || !wasServedExposed && isServedExposed

portsStatus[port.localPort] = port

if (shouldSendNotification) {
if (port.exposed.onExposed.number == OnPortExposedAction.ignore_VALUE) {
continue
}

if (port.exposed.onExposed.number == OnPortExposedAction.open_browser_VALUE) {
ClientId.withClientId(session.clientId) {
BrowserUtil.browse(port.exposed.url)
gitpodClientProjectSessionTracker.trackEvent("jb_execute_command_gitpod_ports", mapOf("action" to "openBrowser"))
}
continue
}

if (port.exposed.onExposed.number == OnPortExposedAction.open_preview_VALUE) {
ClientId.withClientId(session.clientId) {
BrowserUtil.browse(port.exposed.url)
gitpodClientProjectSessionTracker.trackEvent("jb_execute_command_gitpod_ports", mapOf("action" to "openBrowser"))
}
continue
}

val message = "A service is available on port ${port.localPort}"
val notification = notificationGroup.createNotification(message, NotificationType.INFORMATION)

val lambda = {
BrowserUtil.browse(port.exposed.url)
gitpodClientProjectSessionTracker.trackEvent("jb_execute_command_gitpod_ports", mapOf("action" to "openBrowser"))
}

val action = NotificationAction.createSimpleExpiring("Open Browser", lambda)
notification.addAction(action)
notification.notify(null)
}
}
}

override fun onError(t: Throwable) {
f.completeExceptionally(t)
}

override fun onCompleted() {
f.complete(null)
}
})
f.await()
} catch (t: Throwable) {
if (t is CancellationException) {
throw t
}
thisLogger().error("gitpod: failed to stream ports status: ", t)
}
delay(1000L)
}
}
init {
lifetime.onTerminationOrNow {
portsObserveJob.cancel()
}
}

val pendingInfo = CompletableFuture<WorkspaceInfoResponse>()
private val infoJob = GlobalScope.launch {
if (application.isHeadlessEnvironment) {
Expand Down

0 comments on commit 5ab5547

Please sign in to comment.