Skip to content

Commit 7a10c72

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

File tree

1 file changed

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

1 file changed

+133
-0
lines changed

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

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

0 commit comments

Comments
 (0)