Skip to content

Commit 8fe883a

Browse files
committed
[server] Allow setting ws-class on project level
1 parent 1b48aa9 commit 8fe883a

File tree

12 files changed

+91
-69
lines changed

12 files changed

+91
-69
lines changed

components/dashboard/src/projects/ProjectSettings.tsx

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* See License-AGPL.txt in the project root for license information.
55
*/
66

7-
import { useContext } from "react";
7+
import { useContext, useEffect, useState } from "react";
88
import { useLocation } from "react-router";
99
import { Project, ProjectSettings, Team } from "@gitpod/gitpod-protocol";
1010
import CheckBox from "../components/CheckBox";
@@ -14,6 +14,8 @@ import { PageWithSubMenu } from "../components/PageWithSubMenu";
1414
import PillLabel from "../components/PillLabel";
1515
import { ProjectContext } from "./project-context";
1616
import { FeatureFlagContext } from "../contexts/FeatureFlagContext";
17+
import SelectWorkspaceClass from "../settings/selectClass";
18+
import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode";
1719

1820
export function getProjectSettingsMenu(project?: Project, team?: Team) {
1921
const teamOrUserSlug = !!team ? "t/" + team.slug : "projects";
@@ -48,6 +50,14 @@ export function ProjectSettingsPage(props: { project?: Project; children?: React
4850
export default function () {
4951
const { showPersistentVolumeClaimUI } = useContext(FeatureFlagContext);
5052
const { project, setProject } = useContext(ProjectContext);
53+
const [teamBillingMode, setTeamBillingMode] = useState<BillingMode | undefined>(undefined);
54+
const { teams } = useContext(TeamsContext);
55+
const team = getCurrentTeam(useLocation(), teams);
56+
useEffect(() => {
57+
if (team) {
58+
getGitpodService().server.getBillingModeForTeam(team.id).then(setTeamBillingMode);
59+
}
60+
}, [team]);
5161

5262
if (!project) return null;
5363

@@ -59,6 +69,15 @@ export default function () {
5969
setProject({ ...project, settings: newSettings });
6070
};
6171

72+
const setWorkspaceClass = async (value: string) => {
73+
if (!project) {
74+
return value;
75+
}
76+
const before = project.settings?.workspaceClasses?.regular;
77+
updateProjectSettings({ workspaceClasses: { prebuild: value, regular: value } });
78+
return before;
79+
};
80+
6281
return (
6382
<ProjectSettingsPage project={project}>
6483
<h3>Prebuilds</h3>
@@ -142,8 +161,12 @@ export default function () {
142161

143162
{showPersistentVolumeClaimUI && (
144163
<>
145-
<br></br>
146-
<h3 className="mt-12">Workspaces</h3>
164+
<SelectWorkspaceClass
165+
workspaceClass={project.settings?.workspaceClasses?.regular}
166+
enabled={BillingMode.canSetWorkspaceClass(teamBillingMode)}
167+
setWorkspaceClass={setWorkspaceClass}
168+
/>
169+
{!BillingMode.canSetWorkspaceClass(teamBillingMode) && <h3 className="mt-12">Workspaces</h3>}
147170
<CheckBox
148171
title={
149172
<span>

components/dashboard/src/settings/Preferences.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import SelectIDE from "./SelectIDE";
1414
import SelectWorkspaceClass from "./selectClass";
1515
import { PageWithSettingsSubMenu } from "./PageWithSettingsSubMenu";
1616
import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode";
17+
import { WorkspaceClasses } from "@gitpod/gitpod-protocol";
1718

1819
type Theme = "light" | "dark" | "system";
1920

@@ -49,13 +50,30 @@ export default function Preferences() {
4950
}
5051
};
5152

53+
const setWorkspaceClass = async (value: string) => {
54+
const additionalData = user?.additionalData || {};
55+
const prevWorkspaceClass = additionalData?.workspaceClasses?.regular;
56+
const workspaceClasses = (additionalData?.workspaceClasses || {}) as WorkspaceClasses;
57+
workspaceClasses.regular = value;
58+
workspaceClasses.prebuild = value;
59+
additionalData.workspaceClasses = workspaceClasses;
60+
if (value !== prevWorkspaceClass) {
61+
await getGitpodService().server.updateLoggedInUser({ additionalData });
62+
}
63+
return prevWorkspaceClass;
64+
};
65+
5266
return (
5367
<div>
5468
<PageWithSettingsSubMenu title="Preferences" subtitle="Configure user preferences.">
5569
<h3>Editor</h3>
5670
<p className="text-base text-gray-500 dark:text-gray-400">Choose the editor for opening workspaces.</p>
5771
<SelectIDE location="preferences" />
58-
<SelectWorkspaceClass enabled={BillingMode.canSetWorkspaceClass(userBillingMode)} />
72+
<SelectWorkspaceClass
73+
workspaceClass={user?.additionalData?.workspaceClasses?.regular}
74+
enabled={BillingMode.canSetWorkspaceClass(userBillingMode)}
75+
setWorkspaceClass={setWorkspaceClass}
76+
/>
5977
<h3 className="mt-12">Theme</h3>
6078
<p className="text-base text-gray-500 dark:text-gray-400">Early bird or night owl? Choose your side.</p>
6179
<div className="mt-4 space-x-3 flex">

components/dashboard/src/settings/selectClass.tsx

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,22 @@ import { useContext, useEffect, useState } from "react";
88
import { getGitpodService } from "../service/service";
99
import { UserContext } from "../user-context";
1010
import { trackEvent } from "../Analytics";
11-
import { WorkspaceClasses } from "@gitpod/gitpod-protocol";
1211
import WorkspaceClass from "../components/WorkspaceClass";
1312
import { SupportedWorkspaceClass } from "@gitpod/gitpod-protocol/lib/workspace-class";
1413

1514
interface SelectWorkspaceClassProps {
1615
enabled: boolean;
16+
workspaceClass?: string;
17+
setWorkspaceClass: (value: string) => Promise<string | undefined>;
1718
}
1819

1920
export default function SelectWorkspaceClass(props: SelectWorkspaceClassProps) {
20-
const { user } = useContext(UserContext);
21-
22-
const [workspaceClass, setWorkspaceClass] = useState<string>(user?.additionalData?.workspaceClasses?.regular || "");
21+
const [workspaceClass, setWorkspaceClass] = useState<string | undefined>(props.workspaceClass);
2322
const actuallySetWorkspaceClass = async (value: string) => {
24-
const additionalData = user?.additionalData || {};
25-
const prevWorkspaceClass = additionalData?.workspaceClasses?.regular || "";
26-
const workspaceClasses = (additionalData?.workspaceClasses || {}) as WorkspaceClasses;
27-
workspaceClasses.regular = value;
28-
workspaceClasses.prebuild = value;
29-
additionalData.workspaceClasses = workspaceClasses;
30-
if (value !== prevWorkspaceClass) {
31-
await getGitpodService().server.updateLoggedInUser({ additionalData });
23+
const previousValue = await props.setWorkspaceClass(value);
24+
if (previousValue !== value) {
3225
trackEvent("workspace_class_changed", {
33-
previous: prevWorkspaceClass,
26+
previous: previousValue,
3427
current: value,
3528
});
3629
setWorkspaceClass(value);

components/gitpod-protocol/src/teams-projects-protocol.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* See License-AGPL.txt in the project root for license information.
55
*/
66

7-
import { PrebuiltWorkspaceState } from "./protocol";
7+
import { PrebuiltWorkspaceState, WorkspaceClasses } from "./protocol";
88
import { v4 as uuidv4 } from "uuid";
99
import { DeepPartial } from "./util/deep-partial";
1010
import { WebhookEvent } from "./webhook-event";
@@ -21,6 +21,8 @@ export interface ProjectSettings {
2121
allowUsingPreviousPrebuilds?: boolean;
2222
// how many commits in the commit history a prebuild is good (undefined and 0 means every commit is prebuilt)
2323
prebuildEveryNthCommit?: number;
24+
// preferred workspace classes
25+
workspaceClasses?: WorkspaceClasses;
2426
}
2527

2628
export interface Project {

components/server/ee/src/container-module.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@ import { ChargebeeService } from "./user/chargebee-service";
4646
import { StripeService } from "./user/stripe-service";
4747
import { EligibilityService } from "./user/eligibility-service";
4848
import { AccountStatementProvider } from "./user/account-statement-provider";
49-
import { WorkspaceStarterEE } from "./workspace/workspace-starter";
50-
import { WorkspaceStarter } from "../../src/workspace/workspace-starter";
5149
import { UserDeletionService } from "../../src/user/user-deletion-service";
5250
import { BlockedUserFilter } from "../../src/auth/blocked-user-filter";
5351
import { EMailDomainService, EMailDomainServiceImpl } from "./auth/email-domain-service";
@@ -105,9 +103,6 @@ export const productionEEContainerModule = new ContainerModule((bind, unbind, is
105103
bind(UserDeletionServiceEE).toSelf().inSingletonScope();
106104
rebind(UserDeletionService).to(UserDeletionServiceEE).inSingletonScope();
107105

108-
// workspace management
109-
rebind(WorkspaceStarter).to(WorkspaceStarterEE).inSingletonScope();
110-
111106
// acounting
112107
bind(AccountService).to(AccountServiceImpl).inSingletonScope();
113108
bind(SubscriptionService).toSelf().inSingletonScope();

components/server/ee/src/prebuilds/prebuild-manager.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ export class PrebuildManager {
259259
} else {
260260
span.setTag("starting", true);
261261
const projectEnvVars = await projectEnvVarsPromise;
262-
await this.workspaceStarter.startWorkspace({ span }, workspace, user, [], projectEnvVars, {
262+
await this.workspaceStarter.startWorkspace({ span }, workspace, user, project, [], projectEnvVars, {
263263
excludeFeatureFlags: ["full_workspace_backup"],
264264
});
265265
}
@@ -273,7 +273,12 @@ export class PrebuildManager {
273273
}
274274
}
275275

276-
async retriggerPrebuild(ctx: TraceContext, user: User, workspaceId: string): Promise<StartPrebuildResult> {
276+
async retriggerPrebuild(
277+
ctx: TraceContext,
278+
user: User,
279+
project: Project | undefined,
280+
workspaceId: string,
281+
): Promise<StartPrebuildResult> {
277282
const span = TraceContext.startSpan("retriggerPrebuild", ctx);
278283
span.setTag("workspaceId", workspaceId);
279284
try {
@@ -297,7 +302,7 @@ export class PrebuildManager {
297302
if (workspace.projectId) {
298303
projectEnvVars = await this.projectService.getProjectEnvironmentVariables(workspace.projectId);
299304
}
300-
await this.workspaceStarter.startWorkspace({ span }, workspace, user, [], projectEnvVars);
305+
await this.workspaceStarter.startWorkspace({ span }, workspace, user, project, [], projectEnvVars);
301306
return { prebuildId: prebuild.id, wsid: workspace.id, done: false };
302307
} catch (err) {
303308
TraceContext.setError({ span }, err);

components/server/ee/src/workspace/gitpod-server-impl.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1060,7 +1060,10 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
10601060
// queued for long than a minute? Let's retrigger
10611061
console.warn("Retriggering queued prebuild.", prebuiltWorkspace);
10621062
try {
1063-
await this.prebuildManager.retriggerPrebuild(ctx, user, workspaceID);
1063+
const project = prebuiltWorkspace.projectId
1064+
? await this.projectDB.findProjectById(prebuiltWorkspace.projectId)
1065+
: undefined;
1066+
await this.prebuildManager.retriggerPrebuild(ctx, user, project, workspaceID);
10641067
} catch (err) {
10651068
console.error(err);
10661069
}

components/server/ee/src/workspace/workspace-starter.ts

Lines changed: 0 additions & 39 deletions
This file was deleted.

components/server/src/workspace/gitpod-server-impl.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -718,7 +718,10 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
718718
throw new ResponseError(ErrorCodes.PERMISSION_DENIED, "Cannot (re-)start a deleted workspace.");
719719
}
720720
const userEnvVars = this.userDB.getEnvVars(user.id);
721-
let projectEnvVarsPromise = this.internalGetProjectEnvVars(workspace.projectId);
721+
const projectEnvVarsPromise = this.internalGetProjectEnvVars(workspace.projectId);
722+
const projectPromise = workspace.projectId
723+
? this.projectDB.findProjectById(workspace.projectId)
724+
: Promise.resolve(undefined);
722725

723726
await mayStartPromise;
724727

@@ -727,6 +730,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
727730
ctx,
728731
workspace,
729732
user,
733+
await projectPromise,
730734
await userEnvVars,
731735
await projectEnvVarsPromise,
732736
{
@@ -1199,6 +1203,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
11991203
ctx,
12001204
workspace,
12011205
user,
1206+
project,
12021207
await envVars,
12031208
await projectEnvVarsPromise,
12041209
);
@@ -2980,6 +2985,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
29802985
let user = this.checkAndBlockUser("getSupportedWorkspaceClasses");
29812986
let selectedClass = await WorkspaceClasses.getConfiguredOrUpgradeFromLegacy(
29822987
user,
2988+
undefined,
29832989
this.config.workspaceClasses,
29842990
this.entitlementService,
29852991
);

components/server/src/workspace/workspace-classes.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66

77
import { WorkspaceDB } from "@gitpod/gitpod-db/lib";
8-
import { User, Workspace } from "@gitpod/gitpod-protocol";
8+
import { Project, User, Workspace } from "@gitpod/gitpod-protocol";
99
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
1010
import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing";
1111
import { EntitlementService } from "../billing/entitlement-service";
@@ -159,9 +159,13 @@ export namespace WorkspaceClasses {
159159
*/
160160
export async function getConfiguredOrUpgradeFromLegacy(
161161
user: User,
162+
project: Project | undefined,
162163
classes: WorkspaceClassesConfig,
163164
entitlementService: EntitlementService,
164165
): Promise<string> {
166+
if (project?.settings?.workspaceClasses?.regular) {
167+
return project?.settings?.workspaceClasses?.regular;
168+
}
165169
if (user.additionalData?.workspaceClasses?.regular) {
166170
return user.additionalData?.workspaceClasses?.regular;
167171
}

components/server/src/workspace/workspace-starter.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ async function execute(builder: WorkspaceClassTestBuilder, expectedClass: string
326326
workspace,
327327
previousInstance,
328328
user,
329+
undefined,
329330
entitlementService,
330331
config,
331332
workspaceDb,

components/server/src/workspace/workspace-starter.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import {
6060
WithReferrerContext,
6161
EnvVarWithValue,
6262
BillingTier,
63+
Project,
6364
} from "@gitpod/gitpod-protocol";
6465
import { IAnalyticsWriter } from "@gitpod/gitpod-protocol/lib/analytics";
6566
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
@@ -204,6 +205,7 @@ export async function getWorkspaceClassForInstance(
204205
workspace: Workspace,
205206
previousInstance: WorkspaceInstance | undefined,
206207
user: User,
208+
project: Project | undefined,
207209
entitlementService: EntitlementService,
208210
config: WorkspaceClassesConfig,
209211
workspaceDb: DBWithTracing<WorkspaceDB>,
@@ -217,17 +219,22 @@ export async function getWorkspaceClassForInstance(
217219
if (prebuildClass) {
218220
const userClass = await WorkspaceClasses.getConfiguredOrUpgradeFromLegacy(
219221
user,
222+
project,
220223
config,
221224
entitlementService,
222225
);
223226
workspaceClass = WorkspaceClasses.selectClassForRegular(prebuildClass, userClass, config);
227+
} else if (project?.settings?.workspaceClasses?.regular) {
228+
workspaceClass = project?.settings?.workspaceClasses?.regular;
224229
} else if (user.additionalData?.workspaceClasses?.regular) {
225230
workspaceClass = user.additionalData?.workspaceClasses?.regular;
226231
}
227232
}
228233

229234
if (workspace.type == "prebuild") {
230-
if (user.additionalData?.workspaceClasses?.prebuild) {
235+
if (project?.settings?.workspaceClasses?.prebuild) {
236+
workspaceClass = project?.settings?.workspaceClasses?.prebuild;
237+
} else if (user.additionalData?.workspaceClasses?.prebuild) {
231238
workspaceClass = user.additionalData?.workspaceClasses?.prebuild;
232239
}
233240
}
@@ -283,6 +290,7 @@ export class WorkspaceStarter {
283290
ctx: TraceContext,
284291
workspace: Workspace,
285292
user: User,
293+
project: Project | undefined,
286294
userEnvVars: UserEnvVar[],
287295
projectEnvVars: ProjectEnvVar[],
288296
options?: StartWorkspaceOptions,
@@ -360,6 +368,7 @@ export class WorkspaceStarter {
360368
workspace,
361369
lastValidWorkspaceInstance,
362370
user,
371+
project,
363372
options.excludeFeatureFlags || [],
364373
ideConfig,
365374
),
@@ -768,6 +777,7 @@ export class WorkspaceStarter {
768777
workspace: Workspace,
769778
previousInstance: WorkspaceInstance | undefined,
770779
user: User,
780+
project: Project | undefined,
771781
excludeFeatureFlags: NamedWorkspaceFeatureFlag[],
772782
ideConfig: IDEConfig,
773783
): Promise<WorkspaceInstance> {
@@ -874,6 +884,7 @@ export class WorkspaceStarter {
874884
workspace,
875885
previousInstance,
876886
user,
887+
project,
877888
this.entitlementService,
878889
this.config.workspaceClasses,
879890
this.workspaceDb,

0 commit comments

Comments
 (0)