Skip to content

Commit 6537ee7

Browse files
committed
Add feature flag
1 parent 5dec05c commit 6537ee7

File tree

6 files changed

+35
-5
lines changed

6 files changed

+35
-5
lines changed

components/dashboard/src/components/SelectIDEComponent.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
*/
66

77
import { IDEOption, IDEOptions } from "@gitpod/gitpod-protocol/lib/ide-protocol";
8-
import { useCallback, useEffect, useState } from "react";
8+
import { useCallback, useContext, useEffect, useState } from "react";
99
import { getGitpodService } from "../service/service";
1010
import { DropDown2, DropDown2Element } from "./DropDown2";
1111
import Editor from "../icons/Editor.svg";
12+
import { FeatureFlagContext } from "../contexts/FeatureFlagContext";
1213

1314
interface SelectIDEComponentProps {
1415
selectedIdeOption?: string;
@@ -17,8 +18,16 @@ interface SelectIDEComponentProps {
1718
setError?: (error?: string) => void;
1819
}
1920

21+
function filteredIdeOptions(ideOptions: IDEOptions, experimentalTurnedOn: boolean) {
22+
return IDEOptions.asArray(ideOptions)
23+
.filter((x) => !x.hidden)
24+
.filter((x) => (x.experimental ? experimentalTurnedOn : true));
25+
}
26+
2027
export default function SelectIDEComponent(props: SelectIDEComponentProps) {
2128
const [ideOptions, setIdeOptions] = useState<IDEOptions>();
29+
const { experimentalIdes } = useContext(FeatureFlagContext);
30+
2231
useEffect(() => {
2332
getGitpodService().server.getIDEOptions().then(setIdeOptions);
2433
}, []);
@@ -27,7 +36,7 @@ export default function SelectIDEComponent(props: SelectIDEComponentProps) {
2736
if (!ideOptions) {
2837
return [];
2938
}
30-
const options = IDEOptions.asArray(ideOptions);
39+
const options = filteredIdeOptions(ideOptions, experimentalIdes);
3140
const result: DropDown2Element[] = [];
3241
for (const ide of options.filter((ide) =>
3342
`${ide.label}${ide.title}${ide.notes}${ide.id}`.toLowerCase().includes(search.toLowerCase()),

components/dashboard/src/contexts/FeatureFlagContext.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const defaultFeatureFlags = {
2929
orgGitAuthProviders: false,
3030
switchToPAYG: false,
3131
newSignupFlow: false,
32+
experimentalIdes: false,
3233
};
3334

3435
const FeatureFlagContext = createContext<FeatureFlagsType>(defaultFeatureFlags);
@@ -48,6 +49,7 @@ const FeatureFlagContextProvider: React.FC = ({ children }) => {
4849
const [orgGitAuthProviders, setOrgGitAuthProviders] = useState<boolean>(false);
4950
const [switchToPAYG, setSwitchToPAYG] = useState<boolean>(false);
5051
const [newSignupFlow, setNewSignupFlow] = useState<boolean>(false);
52+
const [experimentalIdes, setExperimentalIdes] = useState<boolean>(false);
5153

5254
useEffect(() => {
5355
if (!user) return;
@@ -66,6 +68,7 @@ const FeatureFlagContextProvider: React.FC = ({ children }) => {
6668
orgGitAuthProviders: { defaultValue: false, setter: setOrgGitAuthProviders },
6769
switchToPAYG: { defaultValue: false, setter: setSwitchToPAYG },
6870
newSignupFlow: { defaultValue: false, setter: setNewSignupFlow },
71+
experimentalIdes: { defaultValue: false, setter: setExperimentalIdes },
6972
};
7073

7174
for (const [flagName, config] of Object.entries(featureFlags)) {
@@ -114,6 +117,7 @@ const FeatureFlagContextProvider: React.FC = ({ children }) => {
114117
orgGitAuthProviders,
115118
newSignupFlow,
116119
switchToPAYG,
120+
experimentalIdes,
117121
};
118122
}, [
119123
enablePersonalAccessTokens,
@@ -126,6 +130,7 @@ const FeatureFlagContextProvider: React.FC = ({ children }) => {
126130
startWithOptions,
127131
switchToPAYG,
128132
usePublicApiWorkspacesService,
133+
experimentalIdes,
129134
]);
130135

131136
return <FeatureFlagContext.Provider value={flags}>{children}</FeatureFlagContext.Provider>;

components/dashboard/src/user-settings/SelectIDE.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import SelectableCardSolid from "../components/SelectableCardSolid";
1111
import Tooltip from "../components/Tooltip";
1212
import { getGitpodService } from "../service/service";
1313
import { UserContext } from "../user-context";
14+
import { FeatureFlagContext } from "../contexts/FeatureFlagContext";
1415
import CheckBox from "../components/CheckBox";
1516
import { User } from "@gitpod/gitpod-protocol";
1617
import PillLabel from "../components/PillLabel";
@@ -48,6 +49,7 @@ export const updateUserIDEInfo = async (
4849

4950
export default function SelectIDE(props: SelectIDEProps) {
5051
const { user, setUser } = useContext(UserContext);
52+
const { experimentalIdes } = useContext(FeatureFlagContext);
5153

5254
// Only exec once when we access this component
5355
useEffect(() => {
@@ -81,7 +83,7 @@ export default function SelectIDE(props: SelectIDEProps) {
8183
})();
8284
}, []);
8385

84-
const allIdeOptions = ideOptions && orderedIdeOptions(ideOptions);
86+
const allIdeOptions = ideOptions && orderedIdeOptions(filteredIdeOptions(ideOptions, experimentalIdes));
8587

8688
return (
8789
<>
@@ -160,9 +162,9 @@ export default function SelectIDE(props: SelectIDEProps) {
160162
);
161163
}
162164

163-
function orderedIdeOptions(ideOptions: IDEOptions) {
165+
function orderedIdeOptions(entries: [string, IDEOption][]) {
164166
// TODO: Maybe convert orderKey to number before sort?
165-
return Object.entries(ideOptions.options)
167+
return entries
166168
.filter(([_, x]) => !x.hidden)
167169
.sort((a, b) => {
168170
const keyA = a[1].orderKey || a[0];
@@ -171,6 +173,12 @@ function orderedIdeOptions(ideOptions: IDEOptions) {
171173
});
172174
}
173175

176+
function filteredIdeOptions(ideOptions: IDEOptions, experimentalTurnedOn: boolean) {
177+
return Object.entries(ideOptions.options)
178+
.filter(([_, x]) => !x.hidden)
179+
.filter(([_, x]) => (x.experimental ? experimentalTurnedOn : true));
180+
}
181+
174182
function renderIdeOption(
175183
option: IDEOption,
176184
selected: boolean,

components/gitpod-protocol/src/ide-protocol.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ export interface IDEOption {
105105
*/
106106
hidden?: boolean;
107107

108+
/**
109+
* If `true` this IDE option is conditionally shown in the IDE preferences
110+
*/
111+
experimental?: boolean;
112+
108113
/**
109114
* The image ref to the IDE image.
110115
*/

components/ide-service-api/go/config/ideconfig.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ type IDEOption struct {
4444
Notes []string `json:"notes,omitempty"`
4545
// Hidden this IDE option is not visible in the IDE preferences.
4646
Hidden bool `json:"hidden,omitempty"`
47+
// Experimental this IDE option is to only be shown to some users
48+
Experimental bool `json:"experimental,omitempty"`
4749
// Image ref to the IDE image.
4850
Image string `json:"image"`
4951
// LatestImage ref to the IDE image, this image ref always resolve to digest.

install/installer/pkg/components/ide-service/ide_config_configmap.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ func ideConfigConfigmap(ctx *common.RenderContext) ([]runtime.Object, error) {
205205
Label: "Insiders",
206206
Image: ctx.ImageName(ctx.Config.Repository, ide.XtermIDEImage, "latest"),
207207
ResolveImageDigest: true,
208+
Experimental: true,
208209
},
209210
},
210211
DefaultIde: "code",

0 commit comments

Comments
 (0)