Skip to content

Commit 5ab5547

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

File tree

1 file changed

+132
-0
lines changed
  • components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote

1 file changed

+132
-0
lines changed

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

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,17 @@
44

55
package io.gitpod.jetbrains.remote
66

7+
import com.intellij.openapi.client.ClientSessionsManager
8+
import org.jetbrains.ide.RestService
9+
import com.intellij.codeWithMe.ClientId
10+
import com.intellij.ide.BrowserUtil
711
import com.intellij.ide.plugins.PluginManagerCore
812
import com.intellij.notification.NotificationAction
913
import com.intellij.notification.NotificationGroupManager
1014
import com.intellij.notification.NotificationType
1115
import com.intellij.openapi.Disposable
16+
import com.intellij.openapi.client.ClientProjectSession
17+
import com.intellij.openapi.client.ClientSession
1218
import com.intellij.openapi.components.Service
1319
import com.intellij.openapi.diagnostic.thisLogger
1420
import com.intellij.openapi.extensions.PluginId
@@ -23,6 +29,10 @@ import io.gitpod.jetbrains.remote.utils.Retrier.retry
2329
import io.gitpod.supervisor.api.*
2430
import io.gitpod.supervisor.api.Info.WorkspaceInfoResponse
2531
import io.gitpod.supervisor.api.Notification.*
32+
import io.gitpod.supervisor.api.Status.OnPortExposedAction
33+
import io.gitpod.supervisor.api.Status.PortsStatus
34+
import io.gitpod.supervisor.api.Status.PortsStatusRequest
35+
import io.gitpod.supervisor.api.Status.PortsStatusResponse
2636
import io.grpc.ManagedChannel
2737
import io.grpc.ManagedChannelBuilder
2838
import io.grpc.stub.ClientCallStreamObserver
@@ -46,6 +56,7 @@ import java.util.concurrent.CancellationException
4656
import java.util.concurrent.CompletableFuture
4757
import javax.websocket.DeploymentException
4858

59+
4960
@Service
5061
class GitpodManager : Disposable {
5162

@@ -199,6 +210,127 @@ class GitpodManager : Disposable {
199210
}
200211
}
201212

213+
private val portsObserveJob = GlobalScope.launch {
214+
if (application.isHeadlessEnvironment) {
215+
return@launch
216+
}
217+
218+
// Ignore ports that aren't actually used by the user (e.g. ports used internally by JetBrains IDEs)
219+
val ignorePorts = listOf(5990, 5991, 6942, 6943)
220+
val portsStatus = hashMapOf<Int, PortsStatus>()
221+
222+
val status = StatusServiceGrpc.newStub(supervisorChannel)
223+
while (isActive) {
224+
try {
225+
val project = RestService.getLastFocusedOrOpenedProject()
226+
227+
var session: ClientSession? = null
228+
if (project != null) {
229+
session = ClientSessionsManager.getProjectSessions(project, false).first()
230+
}
231+
if (session == null) {
232+
session = ClientSessionsManager.getAppSessions(false).first()
233+
}
234+
235+
val gitpodClientProjectSessionTracker = GitpodClientProjectSessionTracker(session as ClientProjectSession)
236+
237+
val f = CompletableFuture<Void>()
238+
status.portsStatus(
239+
PortsStatusRequest.newBuilder().setObserve(true).build(),
240+
object : ClientResponseObserver<PortsStatusRequest, PortsStatusResponse> {
241+
242+
override fun beforeStart(requestStream: ClientCallStreamObserver<PortsStatusRequest>) {
243+
lifetime.onTerminationOrNow {
244+
requestStream.cancel(null, null)
245+
}
246+
}
247+
248+
override fun onNext(ps: PortsStatusResponse) {
249+
for (port in ps.portsList) {
250+
// Avoiding undesired notifications
251+
if (ignorePorts.contains(port.localPort)) {
252+
continue
253+
}
254+
255+
val hasPreviousStatus = portsStatus.containsKey(port.localPort)
256+
257+
if (!hasPreviousStatus) {
258+
portsStatus[port.localPort] = port
259+
}
260+
261+
val wasServed = portsStatus[port.localPort]?.served!!
262+
val wasExposed = portsStatus[port.localPort]?.hasExposed()!!
263+
val wasServedExposed = wasServed && wasExposed
264+
val isServedExposed = port.served && port.hasExposed()
265+
266+
// If the initial update received shows that the port is served and exposed, then notify
267+
val isFirstUpdate = !hasPreviousStatus && wasServedExposed && isServedExposed
268+
269+
// If the port changes its status to served and exposed, notify the user
270+
val shouldSendNotification = isFirstUpdate || !wasServedExposed && isServedExposed
271+
272+
portsStatus[port.localPort] = port
273+
274+
if (shouldSendNotification) {
275+
if (port.exposed.onExposed.number == OnPortExposedAction.ignore_VALUE) {
276+
continue
277+
}
278+
279+
if (port.exposed.onExposed.number == OnPortExposedAction.open_browser_VALUE) {
280+
ClientId.withClientId(session.clientId) {
281+
BrowserUtil.browse(port.exposed.url)
282+
gitpodClientProjectSessionTracker.trackEvent("jb_execute_command_gitpod_ports", mapOf("action" to "openBrowser"))
283+
}
284+
continue
285+
}
286+
287+
if (port.exposed.onExposed.number == OnPortExposedAction.open_preview_VALUE) {
288+
ClientId.withClientId(session.clientId) {
289+
BrowserUtil.browse(port.exposed.url)
290+
gitpodClientProjectSessionTracker.trackEvent("jb_execute_command_gitpod_ports", mapOf("action" to "openBrowser"))
291+
}
292+
continue
293+
}
294+
295+
val message = "A service is available on port ${port.localPort}"
296+
val notification = notificationGroup.createNotification(message, NotificationType.INFORMATION)
297+
298+
val lambda = {
299+
BrowserUtil.browse(port.exposed.url)
300+
gitpodClientProjectSessionTracker.trackEvent("jb_execute_command_gitpod_ports", mapOf("action" to "openBrowser"))
301+
}
302+
303+
val action = NotificationAction.createSimpleExpiring("Open Browser", lambda)
304+
notification.addAction(action)
305+
notification.notify(null)
306+
}
307+
}
308+
}
309+
310+
override fun onError(t: Throwable) {
311+
f.completeExceptionally(t)
312+
}
313+
314+
override fun onCompleted() {
315+
f.complete(null)
316+
}
317+
})
318+
f.await()
319+
} catch (t: Throwable) {
320+
if (t is CancellationException) {
321+
throw t
322+
}
323+
thisLogger().error("gitpod: failed to stream ports status: ", t)
324+
}
325+
delay(1000L)
326+
}
327+
}
328+
init {
329+
lifetime.onTerminationOrNow {
330+
portsObserveJob.cancel()
331+
}
332+
}
333+
202334
val pendingInfo = CompletableFuture<WorkspaceInfoResponse>()
203335
private val infoJob = GlobalScope.launch {
204336
if (application.isHeadlessEnvironment) {

0 commit comments

Comments
 (0)