diff --git a/components/dashboard/src/data/featureflag-query.ts b/components/dashboard/src/data/featureflag-query.ts index 050469fbbc2a17..a8e1d19158032b 100644 --- a/components/dashboard/src/data/featureflag-query.ts +++ b/components/dashboard/src/data/featureflag-query.ts @@ -27,6 +27,7 @@ const featureFlags = { dashboard_logging_tracing: false, showBrowserExtensionPromotion: false, usage_update_scheduler_duration: "15m", + enable_experimental_jbtb: false, }; type FeatureFlags = typeof featureFlags; diff --git a/components/dashboard/src/service/service.tsx b/components/dashboard/src/service/service.tsx index 5e2453f9861410..a0cba936c5bb04 100644 --- a/components/dashboard/src/service/service.tsx +++ b/components/dashboard/src/service/service.tsx @@ -344,8 +344,15 @@ export class IDEFrontendService implements IDEFrontendDashboardService.IServer { const desktopLink = new URL(url); // allow to redirect only for whitelisted trusted protocols // IDE-69 - const trustedProtocols = ["vscode:", "vscode-insiders:", "jetbrains-gateway:"]; + const trustedProtocols = ["vscode:", "vscode-insiders:", "jetbrains-gateway:", "jetbrains:"]; redirect = trustedProtocols.includes(desktopLink.protocol); + if ( + redirect && + desktopLink.protocol === "jetbrains:" && + !desktopLink.href.startsWith("jetbrains://gateway/io.gitpod.toolbox.gateway/") + ) { + redirect = false; + } } catch (e) { console.error("invalid desktop link:", e); } diff --git a/components/dashboard/src/user-settings/SelectIDE.tsx b/components/dashboard/src/user-settings/SelectIDE.tsx index 3371abeb17bf99..6e2887304ac599 100644 --- a/components/dashboard/src/user-settings/SelectIDE.tsx +++ b/components/dashboard/src/user-settings/SelectIDE.tsx @@ -13,6 +13,8 @@ import { useUpdateCurrentUserMutation } from "../data/current-user/update-mutati import { converter } from "../service/public-api"; import { isOrganizationOwned } from "@gitpod/public-api-common/lib/user-utils"; import Alert from "../components/Alert"; +import { useFeatureFlag } from "../data/featureflag-query"; +import { IDESettingsVersion } from "@gitpod/gitpod-protocol/lib/ide-protocol"; export type IDEChangedTrackLocation = "workspace_list" | "workspace_start" | "preferences"; interface SelectIDEProps { @@ -25,18 +27,21 @@ export default function SelectIDE(props: SelectIDEProps) { const [defaultIde, setDefaultIde] = useState(user?.editorSettings?.name || "code"); const [useLatestVersion, setUseLatestVersion] = useState(user?.editorSettings?.version === "latest"); + const [preferToolbox, setPreferToolbox] = useState(user?.editorSettings?.preferToolbox || false); const [ideWarning, setIdeWarning] = useState(undefined); + const enableExperimentalJBTB = useFeatureFlag("enable_experimental_jbtb"); const isOrgOwnedUser = user && isOrganizationOwned(user); const actualUpdateUserIDEInfo = useCallback( - async (selectedIde: string, useLatestVersion: boolean) => { + async (selectedIde: string, useLatestVersion: boolean, preferToolbox: boolean) => { // update stored autostart options to match useLatestVersion value set here const workspaceAutostartOptions = user?.workspaceAutostartOptions?.map((o) => { const option = converter.fromWorkspaceAutostartOption(o); if (option.ideSettings) { option.ideSettings.useLatestVersion = useLatestVersion; + option.ideSettings.preferToolbox = preferToolbox; } return option; @@ -46,9 +51,10 @@ export default function SelectIDE(props: SelectIDEProps) { additionalData: { workspaceAutostartOptions, ideSettings: { - settingVersion: "2.0", + settingVersion: IDESettingsVersion, defaultIde: selectedIde, useLatestVersion: useLatestVersion, + preferToolbox: preferToolbox, }, }, }); @@ -59,18 +65,26 @@ export default function SelectIDE(props: SelectIDEProps) { const actuallySetDefaultIde = useCallback( async (value: string) => { - await actualUpdateUserIDEInfo(value, useLatestVersion); + await actualUpdateUserIDEInfo(value, useLatestVersion, preferToolbox); setDefaultIde(value); }, - [actualUpdateUserIDEInfo, useLatestVersion], + [actualUpdateUserIDEInfo, useLatestVersion, preferToolbox], ); const actuallySetUseLatestVersion = useCallback( async (value: boolean) => { - await actualUpdateUserIDEInfo(defaultIde, value); + await actualUpdateUserIDEInfo(defaultIde, value, preferToolbox); setUseLatestVersion(value); }, - [actualUpdateUserIDEInfo, defaultIde], + [actualUpdateUserIDEInfo, defaultIde, preferToolbox], + ); + + const actuallySetPreferToolbox = useCallback( + async (value: boolean) => { + await actualUpdateUserIDEInfo(defaultIde, useLatestVersion, value); + setPreferToolbox(value); + }, + [actualUpdateUserIDEInfo, defaultIde, useLatestVersion], ); const shouldShowJetbrainsNotice = isJetbrains(defaultIde); @@ -142,6 +156,24 @@ export default function SelectIDE(props: SelectIDEProps) { checked={useLatestVersion} onChange={(checked) => actuallySetUseLatestVersion(checked)} /> + + {enableExperimentalJBTB && ( + + Launch in JetBrains Toolbox{" "} + + + BETA + + + + } + hint={Launch JetBrains IDEs in the JetBrains Toolbox.} + checked={preferToolbox} + onChange={(checked) => actuallySetPreferToolbox(checked)} + /> + )} ); } diff --git a/components/dashboard/src/workspaces/CreateWorkspacePage.tsx b/components/dashboard/src/workspaces/CreateWorkspacePage.tsx index e68d6082187c2c..c1b94913c8eff8 100644 --- a/components/dashboard/src/workspaces/CreateWorkspacePage.tsx +++ b/components/dashboard/src/workspaces/CreateWorkspacePage.tsx @@ -81,7 +81,9 @@ export function CreateWorkspacePage() { props.ideSettings?.useLatestVersion !== undefined ? props.ideSettings.useLatestVersion : user?.editorSettings?.version === "latest"; + const defaultPreferToolbox = props.ideSettings?.preferToolbox ?? user?.editorSettings?.preferToolbox ?? false; const [useLatestIde, setUseLatestIde] = useState(defaultLatestIde); + const [preferToolbox, setPreferToolbox] = useState(defaultPreferToolbox); // Note: it has data fetching and UI rendering race between the updating of `selectedProjectId` and `selectedIde` // We have to stored the using repositoryId locally so that we can know selectedIde is updated because if which repo // so that it doesn't show ide error messages in middle state @@ -145,6 +147,7 @@ export function CreateWorkspacePage() { editorSettings: new EditorReference({ name: selectedIde, version: useLatestIde ? "latest" : "stable", + preferToolbox: preferToolbox, }), }), ); @@ -156,7 +159,17 @@ export function CreateWorkspacePage() { }, }); setUser(updatedUser); - }, [updateUser, currentOrg, selectedIde, selectedWsClass, setUser, useLatestIde, user, workspaceContext.data]); + }, [ + updateUser, + currentOrg, + selectedIde, + selectedWsClass, + setUser, + useLatestIde, + preferToolbox, + user, + workspaceContext.data, + ]); // see if we have a matching project based on context url and project's repo url const project = useMemo(() => { @@ -271,6 +284,7 @@ export function CreateWorkspacePage() { contextUrlSource.editor = { name: selectedIde, version: useLatestIde ? "latest" : undefined, + preferToolbox: preferToolbox, }; } opts.source = { @@ -301,6 +315,7 @@ export function CreateWorkspacePage() { selectedWsClass, selectedIde, useLatestIde, + preferToolbox, createWorkspaceMutation, selectedProjectID, storeAutoStartOptions, @@ -358,6 +373,7 @@ export function CreateWorkspacePage() { } setSelectedIde(rememberedOptions.editorSettings?.name, false); setUseLatestIde(rememberedOptions.editorSettings?.version === "latest"); + setPreferToolbox(rememberedOptions.editorSettings?.preferToolbox || false); } if (!selectedWsClassIsDirty) { @@ -374,6 +390,7 @@ export function CreateWorkspacePage() { if (!selectedIdeIsDirty) { setSelectedIde(defaultIde, false); setUseLatestIde(defaultLatestIde); + setPreferToolbox(defaultPreferToolbox); } if (!selectedWsClassIsDirty) { const projectWsClass = project?.settings?.workspaceClasses?.regular; diff --git a/components/gitpod-protocol/go/gitpod-service.go b/components/gitpod-protocol/go/gitpod-service.go index 2c894f69c450ac..37ab198ba51842 100644 --- a/components/gitpod-protocol/go/gitpod-service.go +++ b/components/gitpod-protocol/go/gitpod-service.go @@ -2058,6 +2058,7 @@ type IDESettings struct { UseDesktopIde bool `json:"useDesktopIde,omitempty"` DefaultDesktopIde string `json:"defaultDesktopIde,omitempty"` UseLatestVersion bool `json:"useLatestVersion"` + PreferToolbox bool `json:"preferToolbox"` } // EmailNotificationSettings is the EmailNotificationSettings message type diff --git a/components/gitpod-protocol/src/protocol.ts b/components/gitpod-protocol/src/protocol.ts index 61bcfd5dd237cf..071e66d29aaa97 100644 --- a/components/gitpod-protocol/src/protocol.ts +++ b/components/gitpod-protocol/src/protocol.ts @@ -171,6 +171,7 @@ export type IDESettings = { settingVersion?: string; defaultIde?: string; useLatestVersion?: boolean; + preferToolbox?: boolean; // DEPRECATED: Use defaultIde after `settingVersion: 2.0`, no more specialify desktop or browser. useDesktopIde?: boolean; // DEPRECATED: Same with useDesktopIde. diff --git a/components/gitpod-protocol/src/workspace-instance.ts b/components/gitpod-protocol/src/workspace-instance.ts index 43815bdaa1d81f..9054ae551e87bc 100644 --- a/components/gitpod-protocol/src/workspace-instance.ts +++ b/components/gitpod-protocol/src/workspace-instance.ts @@ -269,6 +269,7 @@ export namespace WorkspaceInstanceRepoStatus { export interface ConfigurationIdeConfig { useLatest?: boolean; ide?: string; + preferToolbox?: boolean; } export interface IdeSetup { diff --git a/components/ide-service/example-ide-config.json b/components/ide-service/example-ide-config.json index 5a356d4271725b..80e7740ace0d0f 100644 --- a/components/ide-service/example-ide-config.json +++ b/components/ide-service/example-ide-config.json @@ -129,6 +129,18 @@ "If you don't see an open dialog in your browser, make sure you have the \u003ca target='_blank' class='gp-link' href='https://www.gitpod.io/docs/ides-and-editors/jetbrains-gateway#getting-started-jetbrains-gateway'\u003eJetBrains Gateway with Gitpod Plugin\u003c/a\u003e installed on your machine, and then click \u003cb\u003e${OPEN_LINK_LABEL}\u003c/b\u003e below." ] }, + "jetbrains-toolbox": { + "defaultDesktopIDE": "intellij", + "desktopIDEs": [ + "intellij", + "goland", + "pycharm", + "phpstorm" + ], + "installationSteps": [ + "If you don't see an open dialog in your browser, make sure you have the \u003ca target='_blank' class='gp-link' href='https://www.gitpod.io/docs/ides-and-editors/jetbrains-gateway#getting-started-jetbrains-gateway'\u003eJetBrains Toolbox with Gitpod Plugin\u003c/a\u003e installed on your machine, and then click \u003cb\u003e${OPEN_LINK_LABEL}\u003c/b\u003e below." + ] + }, "vscode": { "defaultDesktopIDE": "code-desktop", "desktopIDEs": [ diff --git a/components/ide-service/pkg/server/server.go b/components/ide-service/pkg/server/server.go index 66638df8ad235c..f47a6958aaba4c 100644 --- a/components/ide-service/pkg/server/server.go +++ b/components/ide-service/pkg/server/server.go @@ -303,6 +303,7 @@ func grpcProbe(cfg baseserver.ServerConfiguration) func() error { type IDESettings struct { DefaultIde string `json:"defaultIde,omitempty"` UseLatestVersion bool `json:"useLatestVersion,omitempty"` + PreferToolbox bool `json:"preferToolbox,omitempty"` PinnedIDEversions map[string]string `json:"pinnedIDEversions,omitempty"` } @@ -440,12 +441,22 @@ func (s *IDEServiceServer) ResolveWorkspaceConfig(ctx context.Context, req *api. userIdeName := "" useLatest := false + preferToolbox := false resultingIdeName := ideConfig.IdeOptions.DefaultIde chosenIDE := ideConfig.IdeOptions.Options[resultingIdeName] if ideSettings != nil { userIdeName = ideSettings.DefaultIde useLatest = ideSettings.UseLatestVersion + preferToolbox = ideSettings.PreferToolbox + } + + if preferToolbox { + preferToolboxEnv := api.EnvironmentVariable{ + Name: "GITPOD_PREFER_TOOLBOX", + Value: "true", + } + resp.Envvars = append(resp.Envvars, &preferToolboxEnv) } if userIdeName != "" { @@ -499,6 +510,7 @@ func (s *IDEServiceServer) ResolveWorkspaceConfig(ctx context.Context, req *api. resultingIdeSettings := &IDESettings{ DefaultIde: resultingIdeName, UseLatestVersion: useLatest, + PreferToolbox: preferToolbox, } err = enc.Encode(resultingIdeSettings) diff --git a/components/ide/jetbrains/launcher/main.go b/components/ide/jetbrains/launcher/main.go index 0fb3c1a30e1aee..56f2ceaa13be79 100644 --- a/components/ide/jetbrains/launcher/main.go +++ b/components/ide/jetbrains/launcher/main.go @@ -57,6 +57,7 @@ type LaunchContext struct { label string warmup bool + preferToolbox bool qualifier string productDir string backendDir string @@ -98,6 +99,7 @@ func main() { // supervisor refer see https://github.com/gitpod-io/gitpod/blob/main/components/supervisor/pkg/supervisor/supervisor.go#L961 shouldWaitBackendPlugin := os.Getenv("GITPOD_WAIT_IDE_BACKEND") == "true" debugEnabled := os.Getenv("SUPERVISOR_DEBUG_ENABLE") == "true" + preferToolbox := os.Getenv("GITPOD_PREFER_TOOLBOX") == "true" log.Init(ServiceName, constant.Version, true, debugEnabled) log.Info(ServiceName + ": " + constant.Version) startTime := time.Now() @@ -165,6 +167,7 @@ func main() { alias: alias, label: label, + preferToolbox: preferToolbox, qualifier: qualifier, productDir: productDir, backendDir: backendDir, @@ -178,6 +181,15 @@ func main() { launch(launchCtx) return } + + if preferToolbox { + err = configureToolboxCliProperties(backendDir) + if err != nil { + log.WithError(err).Error("failed to write toolbox cli config file") + return + } + } + // we should start serving immediately and postpone launch // in order to enable a JB Gateway to connect as soon as possible go launch(launchCtx) @@ -277,6 +289,22 @@ func serve(launchCtx *LaunchContext) { fmt.Fprint(w, jsonLink) }) http.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) { + if launchCtx.preferToolbox { + response := make(map[string]string) + toolboxLink, err := resolveToolboxLink(launchCtx.wsInfo) + if err != nil { + log.WithError(err).Error("cannot resolve toolbox link") + http.Error(w, err.Error(), http.StatusServiceUnavailable) + return + } + response["link"] = toolboxLink + response["label"] = launchCtx.label + response["clientID"] = "jetbrains-toolbox" + response["kind"] = launchCtx.alias + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(response) + return + } backendPort := r.URL.Query().Get("backendPort") if backendPort == "" { backendPort = defaultBackendPort @@ -359,6 +387,21 @@ type JoinLinkResponse struct { JoinLink string `json:"joinLink"` } +func resolveToolboxLink(wsInfo *supervisor.WorkspaceInfoResponse) (string, error) { + gitpodUrl, err := url.Parse(wsInfo.GitpodHost) + if err != nil { + return "", err + } + debugWorkspace := wsInfo.DebugWorkspaceType != supervisor.DebugWorkspaceType_noDebug + link := url.URL{ + Scheme: "jetbrains", + Host: "gateway", + Path: "io.gitpod.toolbox.gateway/open-in-toolbox", + RawQuery: fmt.Sprintf("host=%s&workspaceId=%s&debugWorkspace=%t", gitpodUrl.Hostname(), wsInfo.WorkspaceId, debugWorkspace), + } + return link.String(), nil +} + func resolveGatewayLink(backendPort string, wsInfo *supervisor.WorkspaceInfoResponse) (string, error) { gitpodUrl, err := url.Parse(wsInfo.GitpodHost) if err != nil { @@ -554,6 +597,8 @@ func run(launchCtx *LaunchContext) { var args []string if launchCtx.warmup { args = append(args, "warmup") + } else if launchCtx.preferToolbox { + args = append(args, "serverMode") } else { args = append(args, "run") } @@ -1184,3 +1229,38 @@ func resolveProjectContextDir(launchCtx *LaunchContext) string { return launchCtx.projectDir } + +func configureToolboxCliProperties(backendDir string) error { + userHomeDir, err := os.UserHomeDir() + if err != nil { + return err + } + + toolboxCliPropertiesDir := fmt.Sprintf("%s/.local/share/JetBrains/Toolbox", userHomeDir) + _, err = os.Stat(toolboxCliPropertiesDir) + if !os.IsNotExist(err) { + return err + } + err = os.MkdirAll(toolboxCliPropertiesDir, os.ModePerm) + if err != nil { + return err + } + + toolboxCliPropertiesFilePath := fmt.Sprintf("%s/environment.json", toolboxCliPropertiesDir) + + // TODO(hw): restrict IDE installation + content := fmt.Sprintf(`{ + "tools": { + "allowInstallation": true, + "allowUpdate": false, + "allowUninstallation": true, + "location": [ + { + "path": "%s" + } + ] + } +}`, backendDir) + + return os.WriteFile(toolboxCliPropertiesFilePath, []byte(content), 0o644) +} diff --git a/components/public-api/buf.gen.yaml b/components/public-api/buf.gen.yaml index e2893f2f420b1a..fb2bd6835a98ac 100644 --- a/components/public-api/buf.gen.yaml +++ b/components/public-api/buf.gen.yaml @@ -27,7 +27,9 @@ plugins: opt: target=ts path: typescript/node_modules/.bin/protoc-gen-connect-es - - plugin: buf.build/connectrpc/kotlin + # https://buf.build/connectrpc/kotlin?version=v0.7.0 + - plugin: buf.build/connectrpc/kotlin:v0.7.0 out: java/src/main/java - - plugin: buf.build/protocolbuffers/java + # https://buf.build/protocolbuffers/java?version=v27.2 + - plugin: buf.build/protocolbuffers/java:v27.2 out: java/src/main/java diff --git a/components/public-api/gitpod/v1/editor.proto b/components/public-api/gitpod/v1/editor.proto index ad132bff437c2d..8dd8b17ee7caf6 100644 --- a/components/public-api/gitpod/v1/editor.proto +++ b/components/public-api/gitpod/v1/editor.proto @@ -8,4 +8,8 @@ option java_package = "io.gitpod.publicapi.v1"; message EditorReference { string name = 1; string version = 2; + + // prefer_toolbox indicates whether the editor should be launched with the + // JetBrains Toolbox instead of JetBrains Gateway + bool prefer_toolbox = 3; } diff --git a/components/public-api/go/v1/editor.pb.go b/components/public-api/go/v1/editor.pb.go index 2f89183d106023..6758bf2ae3ebe7 100644 --- a/components/public-api/go/v1/editor.pb.go +++ b/components/public-api/go/v1/editor.pb.go @@ -31,6 +31,9 @@ type EditorReference struct { Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` + // prefer_toolbox indicates whether the editor should be launched with the + // JetBrains Toolbox instead of JetBrains Gateway + PreferToolbox bool `protobuf:"varint,3,opt,name=prefer_toolbox,json=preferToolbox,proto3" json:"prefer_toolbox,omitempty"` } func (x *EditorReference) Reset() { @@ -79,21 +82,31 @@ func (x *EditorReference) GetVersion() string { return "" } +func (x *EditorReference) GetPreferToolbox() bool { + if x != nil { + return x.PreferToolbox + } + return false +} + var File_gitpod_v1_editor_proto protoreflect.FileDescriptor var file_gitpod_v1_editor_proto_rawDesc = []byte{ 0x0a, 0x16, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x64, 0x69, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, - 0x2e, 0x76, 0x31, 0x22, 0x3f, 0x0a, 0x0f, 0x45, 0x64, 0x69, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x66, + 0x2e, 0x76, 0x31, 0x22, 0x66, 0x0a, 0x0f, 0x45, 0x64, 0x69, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x51, 0x0a, 0x16, 0x69, 0x6f, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, - 0x64, 0x2e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x5a, 0x37, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, - 0x64, 0x2d, 0x69, 0x6f, 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2f, 0x63, 0x6f, 0x6d, 0x70, - 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x61, 0x70, - 0x69, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x5f, 0x74, + 0x6f, 0x6f, 0x6c, 0x62, 0x6f, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x70, 0x72, + 0x65, 0x66, 0x65, 0x72, 0x54, 0x6f, 0x6f, 0x6c, 0x62, 0x6f, 0x78, 0x42, 0x51, 0x0a, 0x16, 0x69, + 0x6f, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, + 0x70, 0x69, 0x2e, 0x76, 0x31, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2d, 0x69, 0x6f, 0x2f, 0x67, 0x69, 0x74, 0x70, + 0x6f, 0x64, 0x2f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x70, 0x75, + 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/components/public-api/java/src/main/java/io/gitpod/publicapi/v1/Editor.java b/components/public-api/java/src/main/java/io/gitpod/publicapi/v1/Editor.java index fd27451791ba9d..0e8ebe44fe20ba 100644 --- a/components/public-api/java/src/main/java/io/gitpod/publicapi/v1/Editor.java +++ b/components/public-api/java/src/main/java/io/gitpod/publicapi/v1/Editor.java @@ -56,6 +56,17 @@ public interface EditorReferenceOrBuilder extends */ com.google.protobuf.ByteString getVersionBytes(); + + /** + *
+     * prefer_toolbox indicates whether the editor should be launched with the
+     * JetBrains Toolbox instead of JetBrains Gateway
+     * 
+ * + * bool prefer_toolbox = 3 [json_name = "preferToolbox"]; + * @return The preferToolbox. + */ + boolean getPreferToolbox(); } /** * Protobuf type {@code gitpod.v1.EditorReference} @@ -174,6 +185,22 @@ public java.lang.String getVersion() { } } + public static final int PREFER_TOOLBOX_FIELD_NUMBER = 3; + private boolean preferToolbox_ = false; + /** + *
+     * prefer_toolbox indicates whether the editor should be launched with the
+     * JetBrains Toolbox instead of JetBrains Gateway
+     * 
+ * + * bool prefer_toolbox = 3 [json_name = "preferToolbox"]; + * @return The preferToolbox. + */ + @java.lang.Override + public boolean getPreferToolbox() { + return preferToolbox_; + } + private byte memoizedIsInitialized = -1; @java.lang.Override public final boolean isInitialized() { @@ -194,6 +221,9 @@ public void writeTo(com.google.protobuf.CodedOutputStream output) if (!com.google.protobuf.GeneratedMessage.isStringEmpty(version_)) { com.google.protobuf.GeneratedMessage.writeString(output, 2, version_); } + if (preferToolbox_ != false) { + output.writeBool(3, preferToolbox_); + } getUnknownFields().writeTo(output); } @@ -209,6 +239,10 @@ public int getSerializedSize() { if (!com.google.protobuf.GeneratedMessage.isStringEmpty(version_)) { size += com.google.protobuf.GeneratedMessage.computeStringSize(2, version_); } + if (preferToolbox_ != false) { + size += com.google.protobuf.CodedOutputStream + .computeBoolSize(3, preferToolbox_); + } size += getUnknownFields().getSerializedSize(); memoizedSize = size; return size; @@ -228,6 +262,8 @@ public boolean equals(final java.lang.Object obj) { .equals(other.getName())) return false; if (!getVersion() .equals(other.getVersion())) return false; + if (getPreferToolbox() + != other.getPreferToolbox()) return false; if (!getUnknownFields().equals(other.getUnknownFields())) return false; return true; } @@ -243,6 +279,9 @@ public int hashCode() { hash = (53 * hash) + getName().hashCode(); hash = (37 * hash) + VERSION_FIELD_NUMBER; hash = (53 * hash) + getVersion().hashCode(); + hash = (37 * hash) + PREFER_TOOLBOX_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( + getPreferToolbox()); hash = (29 * hash) + getUnknownFields().hashCode(); memoizedHashCode = hash; return hash; @@ -376,6 +415,7 @@ public Builder clear() { bitField0_ = 0; name_ = ""; version_ = ""; + preferToolbox_ = false; return this; } @@ -415,6 +455,9 @@ private void buildPartial0(io.gitpod.publicapi.v1.Editor.EditorReference result) if (((from_bitField0_ & 0x00000002) != 0)) { result.version_ = version_; } + if (((from_bitField0_ & 0x00000004) != 0)) { + result.preferToolbox_ = preferToolbox_; + } } @java.lang.Override @@ -439,6 +482,9 @@ public Builder mergeFrom(io.gitpod.publicapi.v1.Editor.EditorReference other) { bitField0_ |= 0x00000002; onChanged(); } + if (other.getPreferToolbox() != false) { + setPreferToolbox(other.getPreferToolbox()); + } this.mergeUnknownFields(other.getUnknownFields()); onChanged(); return this; @@ -475,6 +521,11 @@ public Builder mergeFrom( bitField0_ |= 0x00000002; break; } // case 18 + case 24: { + preferToolbox_ = input.readBool(); + bitField0_ |= 0x00000004; + break; + } // case 24 default: { if (!super.parseUnknownField(input, extensionRegistry, tag)) { done = true; // was an endgroup tag @@ -636,6 +687,53 @@ public Builder setVersionBytes( return this; } + private boolean preferToolbox_ ; + /** + *
+       * prefer_toolbox indicates whether the editor should be launched with the
+       * JetBrains Toolbox instead of JetBrains Gateway
+       * 
+ * + * bool prefer_toolbox = 3 [json_name = "preferToolbox"]; + * @return The preferToolbox. + */ + @java.lang.Override + public boolean getPreferToolbox() { + return preferToolbox_; + } + /** + *
+       * prefer_toolbox indicates whether the editor should be launched with the
+       * JetBrains Toolbox instead of JetBrains Gateway
+       * 
+ * + * bool prefer_toolbox = 3 [json_name = "preferToolbox"]; + * @param value The preferToolbox to set. + * @return This builder for chaining. + */ + public Builder setPreferToolbox(boolean value) { + + preferToolbox_ = value; + bitField0_ |= 0x00000004; + onChanged(); + return this; + } + /** + *
+       * prefer_toolbox indicates whether the editor should be launched with the
+       * JetBrains Toolbox instead of JetBrains Gateway
+       * 
+ * + * bool prefer_toolbox = 3 [json_name = "preferToolbox"]; + * @return This builder for chaining. + */ + public Builder clearPreferToolbox() { + bitField0_ = (bitField0_ & ~0x00000004); + preferToolbox_ = false; + onChanged(); + return this; + } + // @@protoc_insertion_point(builder_scope:gitpod.v1.EditorReference) } @@ -701,11 +799,12 @@ public io.gitpod.publicapi.v1.Editor.EditorReference getDefaultInstanceForType() descriptor; static { java.lang.String[] descriptorData = { - "\n\026gitpod/v1/editor.proto\022\tgitpod.v1\"?\n\017E" + + "\n\026gitpod/v1/editor.proto\022\tgitpod.v1\"f\n\017E" + "ditorReference\022\022\n\004name\030\001 \001(\tR\004name\022\030\n\007ve" + - "rsion\030\002 \001(\tR\007versionBQ\n\026io.gitpod.public" + - "api.v1Z7github.com/gitpod-io/gitpod/comp" + - "onents/public-api/go/v1b\006proto3" + "rsion\030\002 \001(\tR\007version\022%\n\016prefer_toolbox\030\003" + + " \001(\010R\rpreferToolboxBQ\n\026io.gitpod.publica" + + "pi.v1Z7github.com/gitpod-io/gitpod/compo" + + "nents/public-api/go/v1b\006proto3" }; descriptor = com.google.protobuf.Descriptors.FileDescriptor .internalBuildGeneratedFileFrom(descriptorData, @@ -716,7 +815,7 @@ public io.gitpod.publicapi.v1.Editor.EditorReference getDefaultInstanceForType() internal_static_gitpod_v1_EditorReference_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_gitpod_v1_EditorReference_descriptor, - new java.lang.String[] { "Name", "Version", }); + new java.lang.String[] { "Name", "Version", "PreferToolbox", }); descriptor.resolveAllFeaturesImmutable(); } diff --git a/components/public-api/typescript-common/fixtures/toUser_1.golden b/components/public-api/typescript-common/fixtures/toUser_1.golden index b1a2a2039b17a8..cc02a8c5254dcd 100644 --- a/components/public-api/typescript-common/fixtures/toUser_1.golden +++ b/components/public-api/typescript-common/fixtures/toUser_1.golden @@ -27,7 +27,8 @@ }, "editorSettings": { "name": "code", - "version": "latest" + "version": "latest", + "preferToolbox": true }, "dotfileRepo": "https://github.com/gitpod-samples/demo-dotfiles-with-gitpod", "workspaceClass": "XXXL", @@ -58,7 +59,8 @@ "workspaceClass": "XXXL", "editorSettings": { "name": "code", - "version": "stable" + "version": "stable", + "preferToolbox": false }, "region": "" } diff --git a/components/public-api/typescript-common/fixtures/toUser_1.json b/components/public-api/typescript-common/fixtures/toUser_1.json index 82abbd2646f889..edbbe783aa1a90 100644 --- a/components/public-api/typescript-common/fixtures/toUser_1.json +++ b/components/public-api/typescript-common/fixtures/toUser_1.json @@ -50,7 +50,8 @@ "ideSettings": { "settingVersion": "2.0", "defaultIde": "code", - "useLatestVersion": true + "useLatestVersion": true, + "preferToolbox": true }, "workspaceAutostartOptions": [ { diff --git a/components/public-api/typescript-common/fixtures/toWorkspace2_1.golden b/components/public-api/typescript-common/fixtures/toWorkspace2_1.golden index ee5b58ea0cd8b5..23da81a9f46dd9 100644 --- a/components/public-api/typescript-common/fixtures/toWorkspace2_1.golden +++ b/components/public-api/typescript-common/fixtures/toWorkspace2_1.golden @@ -56,7 +56,8 @@ "logUrl": "", "editor": { "name": "code", - "version": "latest" + "version": "latest", + "preferToolbox": false } }, "status": { diff --git a/components/public-api/typescript-common/fixtures/toWorkspace2_2.golden b/components/public-api/typescript-common/fixtures/toWorkspace2_2.golden index 80b3cf54e178d7..56c9eb72339eb8 100644 --- a/components/public-api/typescript-common/fixtures/toWorkspace2_2.golden +++ b/components/public-api/typescript-common/fixtures/toWorkspace2_2.golden @@ -51,7 +51,8 @@ "logUrl": "", "editor": { "name": "code", - "version": "stable" + "version": "stable", + "preferToolbox": false } }, "status": { diff --git a/components/public-api/typescript-common/fixtures/toWorkspace2_3.golden b/components/public-api/typescript-common/fixtures/toWorkspace2_3.golden index 8b7595493b102a..7fb4c088ec5014 100644 --- a/components/public-api/typescript-common/fixtures/toWorkspace2_3.golden +++ b/components/public-api/typescript-common/fixtures/toWorkspace2_3.golden @@ -49,7 +49,8 @@ "logUrl": "", "editor": { "name": "code", - "version": "stable" + "version": "stable", + "preferToolbox": false } }, "status": { diff --git a/components/public-api/typescript-common/fixtures/toWorkspace3_adminPage_1.golden b/components/public-api/typescript-common/fixtures/toWorkspace3_adminPage_1.golden index 1413490c2c02ec..23dd0c5d8d0525 100644 --- a/components/public-api/typescript-common/fixtures/toWorkspace3_adminPage_1.golden +++ b/components/public-api/typescript-common/fixtures/toWorkspace3_adminPage_1.golden @@ -62,7 +62,8 @@ "logUrl": "", "editor": { "name": "code", - "version": "stable" + "version": "stable", + "preferToolbox": false } }, "status": { diff --git a/components/public-api/typescript-common/fixtures/toWorkspaceSession_1.golden b/components/public-api/typescript-common/fixtures/toWorkspaceSession_1.golden index f18191b79e5c35..bb056fd7dc07ce 100644 --- a/components/public-api/typescript-common/fixtures/toWorkspaceSession_1.golden +++ b/components/public-api/typescript-common/fixtures/toWorkspaceSession_1.golden @@ -51,7 +51,8 @@ "logUrl": "", "editor": { "name": "code", - "version": "stable" + "version": "stable", + "preferToolbox": false } }, "status": { diff --git a/components/public-api/typescript-common/fixtures/toWorkspaceSession_2.golden b/components/public-api/typescript-common/fixtures/toWorkspaceSession_2.golden index f382a7ded2f566..2d501e49e209bb 100644 --- a/components/public-api/typescript-common/fixtures/toWorkspaceSession_2.golden +++ b/components/public-api/typescript-common/fixtures/toWorkspaceSession_2.golden @@ -51,7 +51,8 @@ "logUrl": "", "editor": { "name": "code", - "version": "stable" + "version": "stable", + "preferToolbox": false } }, "status": { diff --git a/components/public-api/typescript-common/fixtures/toWorkspaceSession_3.golden b/components/public-api/typescript-common/fixtures/toWorkspaceSession_3.golden index c6528f569c0e08..4f6b20fb39ac95 100644 --- a/components/public-api/typescript-common/fixtures/toWorkspaceSession_3.golden +++ b/components/public-api/typescript-common/fixtures/toWorkspaceSession_3.golden @@ -51,7 +51,8 @@ "logUrl": "", "editor": { "name": "code", - "version": "stable" + "version": "stable", + "preferToolbox": false } }, "status": { diff --git a/components/public-api/typescript-common/fixtures/toWorkspaceSession_4.golden b/components/public-api/typescript-common/fixtures/toWorkspaceSession_4.golden index 5c560b2bc1dcf0..5f84cc2b1d2e6a 100644 --- a/components/public-api/typescript-common/fixtures/toWorkspaceSession_4.golden +++ b/components/public-api/typescript-common/fixtures/toWorkspaceSession_4.golden @@ -51,7 +51,8 @@ "logUrl": "", "editor": { "name": "code", - "version": "stable" + "version": "stable", + "preferToolbox": false } }, "status": { diff --git a/components/public-api/typescript-common/src/public-api-converter.ts b/components/public-api/typescript-common/src/public-api-converter.ts index 0dd98e7e3d999c..c50c2fc852358f 100644 --- a/components/public-api/typescript-common/src/public-api-converter.ts +++ b/components/public-api/typescript-common/src/public-api-converter.ts @@ -449,6 +449,7 @@ export class PublicAPIConverter { const result = new EditorReference(); result.name = ideConfig.ide; result.version = ideConfig.useLatest ? "latest" : "stable"; + result.preferToolbox = ideConfig.preferToolbox ?? false return result; } @@ -1542,6 +1543,7 @@ export class PublicAPIConverter { return new EditorReference({ name: from.defaultIde, version: from.useLatestVersion ? "latest" : "stable", + preferToolbox: from.preferToolbox, }); } @@ -1552,6 +1554,7 @@ export class PublicAPIConverter { return { defaultIde: e.name, useLatestVersion: e.version === "latest", + preferToolbox: e.preferToolbox, }; } diff --git a/components/public-api/typescript/src/gitpod/v1/editor_pb.ts b/components/public-api/typescript/src/gitpod/v1/editor_pb.ts index a6d61a60972b0e..40b7ca0b0095e9 100644 --- a/components/public-api/typescript/src/gitpod/v1/editor_pb.ts +++ b/components/public-api/typescript/src/gitpod/v1/editor_pb.ts @@ -26,6 +26,14 @@ export class EditorReference extends Message { */ version = ""; + /** + * prefer_toolbox indicates whether the editor should be launched with the + * JetBrains Toolbox instead of JetBrains Gateway + * + * @generated from field: bool prefer_toolbox = 3; + */ + preferToolbox = false; + constructor(data?: PartialMessage) { super(); proto3.util.initPartial(data, this); @@ -36,6 +44,7 @@ export class EditorReference extends Message { static readonly fields: FieldList = proto3.util.newFieldList(() => [ { no: 1, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "version", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 3, name: "prefer_toolbox", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): EditorReference { diff --git a/components/server/src/api/workspace-service-api.ts b/components/server/src/api/workspace-service-api.ts index 9f60ef9584ad5e..cb1c1f58035eef 100644 --- a/components/server/src/api/workspace-service-api.ts +++ b/components/server/src/api/workspace-service-api.ts @@ -185,6 +185,7 @@ export class WorkspaceServiceAPI implements ServiceImpl { + return getExperimentsClientForBackend().getValueAsync("enable_experimental_jbtb", false, { + user: { id: userId }, + }); +} diff --git a/components/server/src/workspace/workspace-starter.ts b/components/server/src/workspace/workspace-starter.ts index fb7972bbf5c9f4..174baa9f023645 100644 --- a/components/server/src/workspace/workspace-starter.ts +++ b/components/server/src/workspace/workspace-starter.ts @@ -136,6 +136,7 @@ import { ctxIsAborted, runWithRequestContext, runWithSubjectId } from "../util/r import { SubjectId } from "../auth/subject-id"; import { ApplicationError, ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error"; import { IDESettingsVersion } from "@gitpod/gitpod-protocol/lib/ide-protocol"; +import { getFeatureFlagEnableExperimentalJBTB } from "../util/featureflags"; export interface StartWorkspaceOptions extends Omit { excludeFeatureFlags?: NamedWorkspaceFeatureFlag[]; @@ -305,6 +306,13 @@ export class WorkspaceStarter { if (lastValidWorkspaceInstance) { const ideConfig = lastValidWorkspaceInstance.configuration?.ideConfig; if (ideConfig?.ide) { + const enableExperimentalJBTB = await getFeatureFlagEnableExperimentalJBTB(user.id); + const preferToolbox = !enableExperimentalJBTB + ? false + : ideSettings?.preferToolbox ?? + user.additionalData?.ideSettings?.preferToolbox ?? + ideConfig.preferToolbox ?? + false; ideSettings = { ...ideSettings, defaultIde: ideConfig.ide, @@ -312,6 +320,7 @@ export class WorkspaceStarter { ideSettings?.useLatestVersion ?? user.additionalData?.ideSettings?.useLatestVersion ?? !!ideConfig.useLatest, + preferToolbox, }; } } @@ -909,9 +918,13 @@ export class WorkspaceStarter { }; if (ideConfig.ideSettings && ideConfig.ideSettings.trim() !== "") { try { + const enableExperimentalJBTB = await getFeatureFlagEnableExperimentalJBTB(user.id); const ideSettings: IDESettings = JSON.parse(ideConfig.ideSettings); configuration.ideConfig!.ide = ideSettings.defaultIde; configuration.ideConfig!.useLatest = !!ideSettings.useLatestVersion; + configuration.ideConfig!.preferToolbox = !enableExperimentalJBTB + ? false + : ideSettings.preferToolbox ?? false; } catch (error) { log.error({ userId: user.id, workspaceId: workspace.id }, "cannot parse ideSettings", error); } diff --git a/install/installer/pkg/components/ide-service/ide-configmap.json b/install/installer/pkg/components/ide-service/ide-configmap.json index e6ce709759870c..d3d691a8df43e3 100644 --- a/install/installer/pkg/components/ide-service/ide-configmap.json +++ b/install/installer/pkg/components/ide-service/ide-configmap.json @@ -688,6 +688,23 @@ "If you don't see an open dialog in your browser, make sure you have the JetBrains Gateway with Gitpod Plugin installed on your machine, and then click ${OPEN_LINK_LABEL} below." ] }, + "jetbrains-toolbox": { + "defaultDesktopIDE": "intellij", + "desktopIDEs": [ + "intellij", + "goland", + "pycharm", + "phpstorm", + "rubymine", + "webstorm", + "rider", + "clion", + "rustrover" + ], + "installationSteps": [ + "If you don't see an open dialog in your browser, make sure you have the JetBrains Toolbox with Gitpod Plugin installed on your machine, and then click ${OPEN_LINK_LABEL} below." + ] + }, "vscode": { "defaultDesktopIDE": "code-desktop", "desktopIDEs": [