Add Gitpod-related actions to JetBrains IDEs

This commit is contained in:
Victor Nogueira 2022-08-30 14:19:45 +00:00 committed by Robo Quat
parent 87ee97d7cd
commit a794d7f2a1
21 changed files with 533 additions and 5 deletions

View File

@ -37,4 +37,16 @@ public interface GitpodServer {
@JsonRequest
CompletableFuture<WorkspaceInstancePort> openPort(String workspaceId, WorkspaceInstancePort port);
@JsonRequest
CompletableFuture<String> takeSnapshot(TakeSnapshotOptions options);
@JsonRequest
CompletableFuture<Void> waitForSnapshot(String snapshotId);
@JsonRequest
CompletableFuture<SetWorkspaceTimeoutResult> setWorkspaceTimeout(String workspaceId, String duration);
@JsonRequest
CompletableFuture<Void> stopWorkspace(String workspaceId);
}

View File

@ -0,0 +1,20 @@
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.
package io.gitpod.gitpodprotocol.api.entities;
public enum Error {
NOT_FOUND(404),
SNAPSHOT_ERROR(630);
private int errCode;
Error(int errCode) {
this.errCode = errCode;
}
public int getErrCode() {
return errCode;
}
}

View File

@ -0,0 +1,17 @@
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.
package io.gitpod.gitpodprotocol.api.entities;
public class SetWorkspaceTimeoutResult {
private String[] resetTimeoutOnWorkspaces;
public SetWorkspaceTimeoutResult(String[] resetTimeoutOnWorkspaces) {
this.resetTimeoutOnWorkspaces = resetTimeoutOnWorkspaces;
}
public String[] getResetTimeoutOnWorkspaces() {
return resetTimeoutOnWorkspaces;
}
}

View File

@ -0,0 +1,46 @@
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.
package io.gitpod.gitpodprotocol.api.entities;
public class TakeSnapshotOptions {
private String workspaceId;
private String layoutData;
private boolean dontWait;
public TakeSnapshotOptions(final String workspaceId, final String layoutData, final Boolean dontWait) {
this.workspaceId = workspaceId;
this.layoutData = layoutData;
this.dontWait = dontWait;
}
public TakeSnapshotOptions(final String workspaceId, final Boolean dontWait) {
this.workspaceId = workspaceId;
this.dontWait = dontWait;
}
public String getLayoutData() {
return layoutData;
}
public void setLayoutData(String layoutData) {
this.layoutData = layoutData;
}
public String getWorkspaceId() {
return workspaceId;
}
public void setWorkspaceId(String workspaceId) {
this.workspaceId = workspaceId;
}
public boolean isDontWait() {
return dontWait;
}
public void setDontWait(boolean dontWait) {
this.dontWait = dontWait;
}
}

View File

@ -0,0 +1,22 @@
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.
package io.gitpod.gitpodprotocol.api.entities;
public enum WorkspaceTimeoutDuration {
DURATION_SHORT("short"),
DURATION_LONG("long"),
DURATION_EXTENDED("extended"),
DURATION_180M("180m"); // for backwards compatibility since the IDE uses this
private String value;
WorkspaceTimeoutDuration(String value) {
this.value = value;
}
public String toString() {
return value;
}
}

View File

@ -4,6 +4,7 @@
package io.gitpod.jetbrains.remote
import com.intellij.ide.BrowserUtil
import com.intellij.ide.plugins.PluginManagerCore
import com.intellij.notification.NotificationAction
import com.intellij.notification.NotificationGroupManager
@ -53,6 +54,7 @@ import java.util.concurrent.CancellationException
import java.util.concurrent.CompletableFuture
import javax.websocket.DeploymentException
@Suppress("UnstableApiUsage", "OPT_IN_USAGE")
@Service
class GitpodManager : Disposable {
@ -258,9 +260,12 @@ class GitpodManager : Disposable {
val tokenResponse = retry(3) {
val request = Token.GetTokenRequest.newBuilder()
.setHost(info.gitpodApi.host)
.addScope("function:sendHeartBeat")
.addScope("function:trackEvent")
.addScope("function:openPort")
.addScope("function:sendHeartBeat")
.addScope("function:setWorkspaceTimeout")
.addScope("function:stopWorkspace")
.addScope("function:takeSnapshot")
.addScope("function:trackEvent")
.setKind("gitpod")
.build()
@ -393,4 +398,10 @@ class GitpodManager : Disposable {
metricsJob.cancel()
}
}
/** Opens the give URL in the Browser and records an event indicating it was open from a custom IntelliJ Action. */
fun openUrlFromAction(url: String) {
trackEvent("jb_execute_command_gitpod_open_link", mapOf("url" to url))
BrowserUtil.browse(url)
}
}

View File

@ -0,0 +1,23 @@
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.
package io.gitpod.jetbrains.remote.actions
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.components.service
import io.gitpod.jetbrains.remote.GitpodManager
import org.apache.http.client.utils.URIBuilder
class AccessControlAction : AnAction() {
private val manager = service<GitpodManager>()
override fun actionPerformed(event: AnActionEvent) {
manager.pendingInfo.thenAccept { workspaceInfo ->
URIBuilder(workspaceInfo.gitpodHost).setPath("integrations").build().toString().let { url ->
manager.openUrlFromAction(url)
}
}
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.
package io.gitpod.jetbrains.remote.actions
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.components.service
import io.gitpod.jetbrains.remote.GitpodManager
class CommunityChatAction : AnAction() {
private val manager = service<GitpodManager>()
override fun actionPerformed(event: AnActionEvent) {
manager.openUrlFromAction("https://www.gitpod.io/chat")
}
}

View File

@ -0,0 +1,20 @@
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.
package io.gitpod.jetbrains.remote.actions
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.components.service
import io.gitpod.jetbrains.remote.GitpodManager
class ContextAction : AnAction() {
private val manager = service<GitpodManager>()
override fun actionPerformed(event: AnActionEvent) {
manager.pendingInfo.thenAccept { workspaceInfo ->
manager.openUrlFromAction(workspaceInfo.workspaceContextUrl)
}
}
}

View File

@ -0,0 +1,20 @@
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.
package io.gitpod.jetbrains.remote.actions
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.components.service
import io.gitpod.jetbrains.remote.GitpodManager
class DashboardAction : AnAction() {
private val manager = service<GitpodManager>()
override fun actionPerformed(event: AnActionEvent) {
manager.pendingInfo.thenAccept { workspaceInfo ->
manager.openUrlFromAction(workspaceInfo.gitpodHost)
}
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.
package io.gitpod.jetbrains.remote.actions
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.components.service
import io.gitpod.jetbrains.remote.GitpodManager
class DocumentationAction : AnAction() {
private val manager = service<GitpodManager>()
override fun actionPerformed(event: AnActionEvent) {
manager.openUrlFromAction("https://www.gitpod.io/docs")
}
}

View File

@ -0,0 +1,47 @@
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.
package io.gitpod.jetbrains.remote.actions
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.thisLogger
import io.gitpod.gitpodprotocol.api.entities.WorkspaceTimeoutDuration
import io.gitpod.jetbrains.remote.GitpodManager
import com.intellij.notification.NotificationType
class ExtendWorkspaceTimeoutAction : AnAction() {
private val manager = service<GitpodManager>()
override fun actionPerformed(event: AnActionEvent) {
manager.pendingInfo.thenAccept { workspaceInfo ->
manager.trackEvent("jb_execute_command_gitpod_workspace", mapOf(
"action" to "extend-timeout"
))
manager.client.server.setWorkspaceTimeout(workspaceInfo.workspaceId, WorkspaceTimeoutDuration.DURATION_180M.toString()).whenComplete { result, e ->
var message: String
var notificationType: NotificationType
if (e != null) {
message = "Cannot extend workspace timeout: ${e.message}"
notificationType = NotificationType.ERROR
thisLogger().error("gitpod: failed to extend workspace timeout", e)
} else {
if (result.resetTimeoutOnWorkspaces.isNotEmpty()) {
message = "Workspace timeout has been extended to three hours. This reset the workspace timeout for other workspaces."
notificationType = NotificationType.WARNING
} else {
message = "Workspace timeout has been extended to three hours."
notificationType = NotificationType.INFORMATION
}
}
val notification = manager.notificationGroup.createNotification(message, notificationType)
notification.notify(null)
}
}
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.
package io.gitpod.jetbrains.remote.actions
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.components.service
import io.gitpod.jetbrains.remote.GitpodManager
class FollowUsOnTwitterAction : AnAction() {
private val manager = service<GitpodManager>()
override fun actionPerformed(event: AnActionEvent) {
manager.openUrlFromAction("https://twitter.com/gitpod")
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.
package io.gitpod.jetbrains.remote.actions
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.components.service
import io.gitpod.jetbrains.remote.GitpodManager
class ReportIssueAction : AnAction() {
private val manager = service<GitpodManager>()
override fun actionPerformed(event: AnActionEvent) {
manager.openUrlFromAction("https://github.com/gitpod-io/gitpod/issues/new/choose")
}
}

View File

@ -0,0 +1,23 @@
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.
package io.gitpod.jetbrains.remote.actions
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.components.service
import io.gitpod.jetbrains.remote.GitpodManager
import org.apache.http.client.utils.URIBuilder
class SettingsAction : AnAction() {
private val manager = service<GitpodManager>()
override fun actionPerformed(event: AnActionEvent) {
manager.pendingInfo.thenAccept { workspaceInfo ->
URIBuilder(workspaceInfo.gitpodHost).setPath("settings").build().toString().let { url ->
manager.openUrlFromAction(url)
}
}
}
}

View File

@ -0,0 +1,74 @@
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.
package io.gitpod.jetbrains.remote.actions
import com.intellij.notification.NotificationAction
import com.intellij.notification.NotificationType
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.util.ExceptionUtil
import io.gitpod.gitpodprotocol.api.entities.TakeSnapshotOptions
import io.gitpod.gitpodprotocol.api.entities.Error
import io.gitpod.jetbrains.remote.GitpodManager
import org.eclipse.lsp4j.jsonrpc.ResponseErrorException
import java.awt.Toolkit
import java.awt.datatransfer.StringSelection
class ShareWorkspaceSnapshotAction : AnAction() {
private val manager = service<GitpodManager>()
override fun actionPerformed(event: AnActionEvent) {
manager.pendingInfo.thenAccept { workspaceInfo ->
manager.trackEvent(
"jb_execute_command_gitpod_workspace", mapOf(
"action" to "snapshot"
)
)
val takeSnapshotOptions = TakeSnapshotOptions(workspaceInfo.workspaceId, true)
manager.client.server.takeSnapshot(takeSnapshotOptions).whenComplete { snapshotId, t ->
if (t != null) {
val notification = manager.notificationGroup.createNotification(
"Cannot capture workspace snapshot: ${t.message}",
NotificationType.ERROR
)
notification.notify(null)
thisLogger().error("gitpod: failed to capture workspace snapshot", t)
} else {
thisLogger().warn("gitpod: snapshot started ($snapshotId)")
val notification = manager.notificationGroup.createNotification(
"Capturing workspace snapshot: this might take a moment, you will get a notification when the snapshot is ready",
NotificationType.INFORMATION
)
notification.notify(null)
manager.client.server.waitForSnapshot(snapshotId).whenComplete { _, t ->
if (t != null) {
val error = ExceptionUtil.findCause(t, ResponseErrorException::class.java)
if (error.responseError.code == Error.SNAPSHOT_ERROR.errCode || error.responseError.code == Error.NOT_FOUND.errCode) {
// this is indeed an error with snapshot creation itself, break here!
throw t
}
}
val notification = manager.notificationGroup.createNotification(
"The current state is captured in a snapshot. Using this link anybody can create their own copy of this workspace.",
NotificationType.INFORMATION
)
val copyUrlAction = NotificationAction.createSimple("Copy URL to Clipboard") {
val uri = "${workspaceInfo.gitpodHost}#snapshot/$snapshotId";
val clipboard = Toolkit.getDefaultToolkit().systemClipboard
clipboard.setContents(StringSelection(uri), null)
}
notification.addAction(copyUrlAction)
notification.notify(null)
}
}
}
}
}
}

View File

@ -0,0 +1,24 @@
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.
package io.gitpod.jetbrains.remote.actions
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.components.service
import io.gitpod.jetbrains.remote.GitpodManager
class StopWorkspaceAction : AnAction() {
private val manager = service<GitpodManager>()
override fun actionPerformed(event: AnActionEvent) {
manager.pendingInfo.thenAccept { workspaceInfo ->
manager.trackEvent("jb_execute_command_gitpod_workspace", mapOf(
"action" to "stop"
))
manager.client.server.stopWorkspace(workspaceInfo.workspaceId)
}
}
}

View File

@ -0,0 +1,23 @@
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.
package io.gitpod.jetbrains.remote.actions
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.components.service
import io.gitpod.jetbrains.remote.GitpodManager
import org.apache.http.client.utils.URIBuilder
class UpgradeSubscriptionAction : AnAction() {
private val manager = service<GitpodManager>()
override fun actionPerformed(event: AnActionEvent) {
manager.pendingInfo.thenAccept { workspaceInfo ->
URIBuilder(workspaceInfo.gitpodHost).setPath("plans").build().toString().let { url ->
manager.openUrlFromAction(url)
}
}
}
}

View File

@ -44,4 +44,78 @@
restartRequired="true"/>
</extensions>
<actions>
<action id="io.gitpod.jetbrains.remote.actions.StopWorkspaceAction"
class="io.gitpod.jetbrains.remote.actions.StopWorkspaceAction"
text="Gitpod: Stop Workspace"
icon="AllIcons.Actions.Pause">
<add-to-group group-id="UnattendedHostDropdownGroup" anchor="last"/>
</action>
<action id="io.gitpod.jetbrains.remote.actions.DashboardAction"
class="io.gitpod.jetbrains.remote.actions.DashboardAction"
text="Gitpod: Open Dashboard"
icon="AllIcons.General.ProjectTab">
<add-to-group group-id="UnattendedHostDropdownGroup" anchor="last"/>
</action>
<action id="io.gitpod.jetbrains.remote.actions.ExtendWorkspaceTimeoutAction"
class="io.gitpod.jetbrains.remote.actions.ExtendWorkspaceTimeoutAction"
text="Gitpod: Extend Workspace Timeout"
icon="AllIcons.Vcs.History">
<add-to-group group-id="UnattendedHostDropdownGroup" anchor="last"/>
</action>
<action id="io.gitpod.jetbrains.remote.actions.ContextAction"
class="io.gitpod.jetbrains.remote.actions.ContextAction"
text="Gitpod: Open Context"
icon="AllIcons.General.FitContent">
<add-to-group group-id="UnattendedHostDropdownGroup" anchor="last"/>
</action>
<action id="io.gitpod.jetbrains.remote.actions.SettingsAction"
class="io.gitpod.jetbrains.remote.actions.SettingsAction"
text="Gitpod: Open Settings"
icon="AllIcons.General.Settings">
<add-to-group group-id="UnattendedHostDropdownGroup" anchor="last"/>
</action>
<action id="io.gitpod.jetbrains.remote.actions.ShareWorkspaceSnapshotAction"
class="io.gitpod.jetbrains.remote.actions.ShareWorkspaceSnapshotAction"
text="Gitpod: Share Workspace Snapshot"
icon="AllIcons.Actions.Dump">
<add-to-group group-id="UnattendedHostDropdownGroup" anchor="last"/>
</action>
<action id="io.gitpod.jetbrains.remote.actions.AccessControlAction"
class="io.gitpod.jetbrains.remote.actions.AccessControlAction"
text="Gitpod: Open Access Control"
icon="AllIcons.Actions.Properties">
<add-to-group group-id="UnattendedHostDropdownGroup" anchor="last"/>
</action>
<action id="io.gitpod.jetbrains.remote.actions.DocumentationAction"
class="io.gitpod.jetbrains.remote.actions.DocumentationAction"
text="Gitpod: Documentation"
icon="AllIcons.General.ReaderMode">
<add-to-group group-id="UnattendedHostDropdownGroup" anchor="last"/>
</action>
<action id="io.gitpod.jetbrains.remote.actions.UpgradeSubscriptionAction"
class="io.gitpod.jetbrains.remote.actions.UpgradeSubscriptionAction"
text="Gitpod: Upgrade Subscription"
icon="AllIcons.Actions.PreviousOccurence">
<add-to-group group-id="UnattendedHostDropdownGroup" anchor="last"/>
</action>
<action id="io.gitpod.jetbrains.remote.actions.CommunityChatAction"
class="io.gitpod.jetbrains.remote.actions.CommunityChatAction"
text="Gitpod: Open Community Chat"
icon="AllIcons.General.Balloon">
<add-to-group group-id="UnattendedHostDropdownGroup" anchor="last"/>
</action>
<action id="io.gitpod.jetbrains.remote.actions.ReportIssueAction"
class="io.gitpod.jetbrains.remote.actions.ReportIssueAction"
text="Gitpod: Report Issue"
icon="AllIcons.General.ShowWarning">
<add-to-group group-id="UnattendedHostDropdownGroup" anchor="last"/>
</action>
<action id="io.gitpod.jetbrains.remote.actions.FollowUsOnTwitterAction"
class="io.gitpod.jetbrains.remote.actions.FollowUsOnTwitterAction"
text="Gitpod: Follow Us On Twitter"
icon="AllIcons.Gutter.ExtAnnotation">
<add-to-group group-id="UnattendedHostDropdownGroup" anchor="last"/>
</action>
</actions>
</idea-plugin>

View File

@ -244,9 +244,9 @@ class GitpodConnectionProvider : GatewayConnectionProvider {
if (thinClientJob == null && update.status.phase == "running") {
thinClientJob = launch {
try {
val updatedIdeUrl = URL(update.ideUrl);
val updatedIdeUrl = URL(update.ideUrl)
val sshHostUrl =
URL(update.ideUrl.replace(update.workspaceId, "${update.workspaceId}.ssh"));
URL(update.ideUrl.replace(update.workspaceId, "${update.workspaceId}.ssh"))
val hostKeys = resolveHostKeys(updatedIdeUrl, connectParams)
if (hostKeys.isNullOrEmpty()) {
setErrorMessage("${connectParams.gitpodHost} installation does not allow SSH access, public keys cannot be found")

View File

@ -76,7 +76,7 @@ class GitpodStartWorkspaceView(
if (contextUrl.component.text.isNotBlank()) {
backendsModel.selectedItem?.let {
backendToId[it]?.let { backend ->
BrowserUtil.browse("https://${settings.gitpodHost}#referrer:jetbrains-gateway:${backend}/${contextUrl.component.text}")
BrowserUtil.browse("https://${settings.gitpodHost}#referrer:jetbrains-gateway:$backend/${contextUrl.component.text}")
}
}
}