diff --git a/components/dashboard/src/components/SelectIDEComponent.tsx b/components/dashboard/src/components/SelectIDEComponent.tsx index 9c953de377ebfe..7967eae91850e8 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()), @@ -48,7 +57,7 @@ export default function SelectIDEComponent(props: SelectIDEComponentProps) { } return result; }, - [ideOptions, props.useLatest], + [experimentalIdes, ideOptions, props.useLatest], ); const internalOnSelectionChange = (id: string) => { const { ide, useLatest } = parseId(id); diff --git a/components/dashboard/src/contexts/FeatureFlagContext.tsx b/components/dashboard/src/contexts/FeatureFlagContext.tsx index 8d204900e9d6bb..589365349082e8 100644 --- a/components/dashboard/src/contexts/FeatureFlagContext.tsx +++ b/components/dashboard/src/contexts/FeatureFlagContext.tsx @@ -31,6 +31,7 @@ const defaultFeatureFlags = { userGitAuthProviders: false, newSignupFlow: false, linkedinConnectionForOnboarding: false, + experimentalIdes: false, }; const FeatureFlagContext = createContext(defaultFeatureFlags); @@ -51,6 +52,7 @@ const FeatureFlagContextProvider: React.FC = ({ children }) => { const [userGitAuthProviders, setUserGitAuthProviders] = useState(false); const [newSignupFlow, setNewSignupFlow] = useState(false); const [linkedinConnectionForOnboarding, setLinkedinConnectionForOnboarding] = 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 }, newSignupFlow: { defaultValue: false, setter: setNewSignupFlow }, linkedinConnectionForOnboarding: { defaultValue: false, setter: setLinkedinConnectionForOnboarding }, + experimentalIdes: { defaultValue: false, setter: setExperimentalIdes }, }; for (const [flagName, config] of Object.entries(featureFlags)) { @@ -120,6 +123,7 @@ const FeatureFlagContextProvider: React.FC = ({ children }) => { userGitAuthProviders, newSignupFlow, linkedinConnectionForOnboarding, + experimentalIdes, }; }, [ enablePersonalAccessTokens, @@ -133,6 +137,7 @@ const FeatureFlagContextProvider: React.FC = ({ children }) => { startWithOptions, 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-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/install/installer/pkg/components/ide-service/ide_config_configmap.go b/install/installer/pkg/components/ide-service/ide_config_configmap.go index 613ab59c4e82d8..0bf868d7ccd8d3 100644 --- a/install/installer/pkg/components/ide-service/ide_config_configmap.go +++ b/install/installer/pkg/components/ide-service/ide_config_configmap.go @@ -88,6 +88,7 @@ func ideConfigConfigmap(ctx *common.RenderContext) ([]runtime.Object, error) { ImageLayers: []string{codeWebExtensionImage, codeHelperImage}, LatestImage: resolveLatestImage(ide.CodeIDEImage, "nightly", ctx.VersionManifest.Components.Workspace.CodeImage), LatestImageLayers: []string{codeWebExtensionImage, codeHelperImage}, + Experimental: true, }, codeDesktop: { OrderKey: "02",