diff --git a/components/dashboard/src/components/SelectIDEComponent.tsx b/components/dashboard/src/components/SelectIDEComponent.tsx index 9c953de377ebfe..6a6fcec5bb2b04 100644 --- a/components/dashboard/src/components/SelectIDEComponent.tsx +++ b/components/dashboard/src/components/SelectIDEComponent.tsx @@ -5,10 +5,11 @@ */ import { IDEOption, IDEOptions } from "@gitpod/gitpod-protocol/lib/ide-protocol"; -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useContext, useEffect, useState } from "react"; import { getGitpodService } from "../service/service"; import { DropDown2, DropDown2Element } from "./DropDown2"; import Editor from "../icons/Editor.svg"; +import { FeatureFlagContext } from "../contexts/FeatureFlagContext"; interface SelectIDEComponentProps { selectedIdeOption?: string; @@ -17,8 +18,16 @@ interface SelectIDEComponentProps { setError?: (error?: string) => void; } +function filteredIdeOptions(ideOptions: IDEOptions, experimentalTurnedOn: boolean) { + return IDEOptions.asArray(ideOptions) + .filter((x) => !x.hidden) + .filter((x) => (x.experimental ? experimentalTurnedOn : true)); +} + export default function SelectIDEComponent(props: SelectIDEComponentProps) { const [ideOptions, setIdeOptions] = useState(); + const { experimentalIdes } = useContext(FeatureFlagContext); + useEffect(() => { getGitpodService().server.getIDEOptions().then(setIdeOptions); }, []); @@ -27,7 +36,7 @@ export default function SelectIDEComponent(props: SelectIDEComponentProps) { if (!ideOptions) { return []; } - const options = IDEOptions.asArray(ideOptions); + const options = filteredIdeOptions(ideOptions, experimentalIdes); const result: DropDown2Element[] = []; for (const ide of options.filter((ide) => `${ide.label}${ide.title}${ide.notes}${ide.id}`.toLowerCase().includes(search.toLowerCase()), diff --git a/components/dashboard/src/contexts/FeatureFlagContext.tsx b/components/dashboard/src/contexts/FeatureFlagContext.tsx index 0b304ffb1bfc12..bf9793eb8b4275 100644 --- a/components/dashboard/src/contexts/FeatureFlagContext.tsx +++ b/components/dashboard/src/contexts/FeatureFlagContext.tsx @@ -31,6 +31,7 @@ const defaultFeatureFlags = { userGitAuthProviders: false, switchToPAYG: false, newSignupFlow: false, + experimentalIdes: false, }; const FeatureFlagContext = createContext(defaultFeatureFlags); @@ -51,6 +52,7 @@ const FeatureFlagContextProvider: React.FC = ({ children }) => { const [userGitAuthProviders, setUserGitAuthProviders] = useState(false); const [switchToPAYG, setSwitchToPAYG] = useState(false); const [newSignupFlow, setNewSignupFlow] = useState(false); + const [experimentalIdes, setExperimentalIdes] = useState(false); useEffect(() => { if (!user) return; @@ -71,6 +73,7 @@ const FeatureFlagContextProvider: React.FC = ({ children }) => { userGitAuthProviders: { defaultValue: false, setter: setUserGitAuthProviders }, switchToPAYG: { defaultValue: false, setter: setSwitchToPAYG }, newSignupFlow: { defaultValue: false, setter: setNewSignupFlow }, + experimentalIdes: { defaultValue: false, setter: setExperimentalIdes }, }; for (const [flagName, config] of Object.entries(featureFlags)) { @@ -120,6 +123,7 @@ const FeatureFlagContextProvider: React.FC = ({ children }) => { userGitAuthProviders, newSignupFlow, switchToPAYG, + experimentalIdes, }; }, [ enablePersonalAccessTokens, @@ -133,6 +137,7 @@ const FeatureFlagContextProvider: React.FC = ({ children }) => { switchToPAYG, usePublicApiWorkspacesService, userGitAuthProviders, + experimentalIdes, ]); return {children}; diff --git a/components/gitpod-protocol/src/ide-protocol.ts b/components/gitpod-protocol/src/ide-protocol.ts index 8c8685b80e679b..2653a6fa755213 100644 --- a/components/gitpod-protocol/src/ide-protocol.ts +++ b/components/gitpod-protocol/src/ide-protocol.ts @@ -105,6 +105,11 @@ export interface IDEOption { */ hidden?: boolean; + /** + * If `true` this IDE option is conditionally shown in the IDE preferences + */ + experimental?: boolean; + /** * The image ref to the IDE image. */ diff --git a/components/ide-proxy/static/image/ide-logo/terminal.svg b/components/ide-proxy/static/image/ide-logo/terminal.svg new file mode 100644 index 00000000000000..7992255eefa918 --- /dev/null +++ b/components/ide-proxy/static/image/ide-logo/terminal.svg @@ -0,0 +1,4 @@ + + + diff --git a/components/ide-service-api/go/config/ideconfig.go b/components/ide-service-api/go/config/ideconfig.go index 020e8234ed7d8a..3fd91782aebd23 100644 --- a/components/ide-service-api/go/config/ideconfig.go +++ b/components/ide-service-api/go/config/ideconfig.go @@ -44,6 +44,8 @@ type IDEOption struct { Notes []string `json:"notes,omitempty"` // Hidden this IDE option is not visible in the IDE preferences. Hidden bool `json:"hidden,omitempty"` + // Experimental this IDE option is to only be shown to some users + Experimental bool `json:"experimental,omitempty"` // Image ref to the IDE image. Image string `json:"image"` // LatestImage ref to the IDE image, this image ref always resolve to digest. diff --git a/components/supervisor/frontend/src/shared/loading-frame.ts b/components/supervisor/frontend/src/shared/loading-frame.ts index 22686fbb8b50c5..5f31729a764443 100644 --- a/components/supervisor/frontend/src/shared/loading-frame.ts +++ b/components/supervisor/frontend/src/shared/loading-frame.ts @@ -16,7 +16,7 @@ export function load(): Promise<{ frame.src = startUrl.toString(); frame.style.visibility = "visible"; frame.className = "gitpod-frame loading"; - document.body.appendChild(frame); + document.body.prepend(frame); frame.onload = () => { const frontendDashboardServiceClient = new FrontendDashboardServiceClient(frame.contentWindow!); diff --git a/install/installer/pkg/components/blobserve/configmap.go b/install/installer/pkg/components/blobserve/configmap.go index 96ee1615352c6c..e02c21afffd73f 100644 --- a/install/installer/pkg/components/blobserve/configmap.go +++ b/install/installer/pkg/components/blobserve/configmap.go @@ -99,6 +99,10 @@ func configmap(ctx *common.RenderContext) ([]runtime.Object, error) { PrePull: []string{}, Workdir: "/.supervisor/frontend", }, + ctx.RepoName(ctx.Config.Repository, ide.XtermIDEImage): { + PrePull: []string{}, + Workdir: "/ide", + }, }, BlobSpace: blobserve_config.BlobSpace{ Location: "/mnt/cache/blobserve", diff --git a/install/installer/pkg/components/ide-service/ide_config_configmap.go b/install/installer/pkg/components/ide-service/ide_config_configmap.go index 613ab59c4e82d8..f7699173a5c18a 100644 --- a/install/installer/pkg/components/ide-service/ide_config_configmap.go +++ b/install/installer/pkg/components/ide-service/ide_config_configmap.go @@ -34,6 +34,7 @@ func ideConfigConfigmap(ctx *common.RenderContext) ([]runtime.Object, error) { webstorm := "webstorm" rider := "rider" clion := "clion" + xterm := "xterm" resolveLatestImage := func(name string, tag string, bundledLatest versions.Versioned) string { resolveLatest := true @@ -195,6 +196,16 @@ func ideConfigConfigmap(ctx *common.RenderContext) ([]runtime.Object, error) { ImageLayers: []string{jbPluginImage, jbLauncherImage}, LatestImageLayers: []string{jbPluginLatestImage, jbLauncherImage}, }, + xterm: { + OrderKey: "12", + Title: "Terminal", + Type: ide_config.IDETypeBrowser, + Logo: getIdeLogoPath("terminal"), + Label: "Insiders", + Image: ctx.ImageName(ctx.Config.Repository, ide.XtermIDEImage, "latest"), + ResolveImageDigest: true, + Experimental: true, + }, }, DefaultIde: "code", DefaultDesktopIde: codeDesktop, diff --git a/install/installer/pkg/components/workspace/ide/constants.go b/install/installer/pkg/components/workspace/ide/constants.go index 09084155a1263c..9bdb2569ea0086 100644 --- a/install/installer/pkg/components/workspace/ide/constants.go +++ b/install/installer/pkg/components/workspace/ide/constants.go @@ -12,6 +12,7 @@ const ( CodeWebExtensionVersion = "commit-3b076b91039c47404d98c4a3198edf2982e24af7" // gitpod-web extension version comes from https://github.com/gitpod-io/gitpod-code CodeDesktopIDEImage = "ide/code-desktop" CodeDesktopInsidersIDEImage = "ide/code-desktop-insiders" + XtermIDEImage = "ide/xterm-web" IntelliJDesktopIDEImage = "ide/intellij" GoLandDesktopIdeImage = "ide/goland" PyCharmDesktopIdeImage = "ide/pycharm"