Skip to content

Commit

Permalink
[server] Allow setting ws-class on project level
Browse files Browse the repository at this point in the history
  • Loading branch information
svenefftinge committed Nov 8, 2022
1 parent 1b48aa9 commit 5ea3bfd
Show file tree
Hide file tree
Showing 12 changed files with 87 additions and 66 deletions.
28 changes: 25 additions & 3 deletions components/dashboard/src/projects/ProjectSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* See License-AGPL.txt in the project root for license information.
*/

import { useContext } from "react";
import { useContext, useEffect, useState } from "react";
import { useLocation } from "react-router";
import { Project, ProjectSettings, Team } from "@gitpod/gitpod-protocol";
import CheckBox from "../components/CheckBox";
Expand All @@ -14,6 +14,8 @@ import { PageWithSubMenu } from "../components/PageWithSubMenu";
import PillLabel from "../components/PillLabel";
import { ProjectContext } from "./project-context";
import { FeatureFlagContext } from "../contexts/FeatureFlagContext";
import SelectWorkspaceClass from "../settings/selectClass";
import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode";

export function getProjectSettingsMenu(project?: Project, team?: Team) {
const teamOrUserSlug = !!team ? "t/" + team.slug : "projects";
Expand Down Expand Up @@ -48,6 +50,14 @@ export function ProjectSettingsPage(props: { project?: Project; children?: React
export default function () {
const { showPersistentVolumeClaimUI } = useContext(FeatureFlagContext);
const { project, setProject } = useContext(ProjectContext);
const [teamBillingMode, setTeamBillingMode] = useState<BillingMode | undefined>(undefined);
const { teams } = useContext(TeamsContext);
const team = getCurrentTeam(useLocation(), teams);
useEffect(() => {
if (team) {
getGitpodService().server.getBillingModeForTeam(team.id).then(setTeamBillingMode);
}
}, [team]);

if (!project) return null;

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

const setWorkspaceClass = async (value: string) => {
if (!project) {
return value;
}
const before = project.settings?.workspaceClasses?.regular;
updateProjectSettings({ workspaceClasses: { prebuild: value, regular: value } });
return before;
};

return (
<ProjectSettingsPage project={project}>
<h3>Prebuilds</h3>
Expand Down Expand Up @@ -142,8 +161,11 @@ export default function () {

{showPersistentVolumeClaimUI && (
<>
<br></br>
<h3 className="mt-12">Workspaces</h3>
<SelectWorkspaceClass
enabled={BillingMode.canSetWorkspaceClass(teamBillingMode)}
setWorkspaceClass={setWorkspaceClass}
/>
{!BillingMode.canSetWorkspaceClass(teamBillingMode) && <h3 className="mt-12">Workspaces</h3>}
<CheckBox
title={
<span>
Expand Down
19 changes: 18 additions & 1 deletion components/dashboard/src/settings/Preferences.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import SelectIDE from "./SelectIDE";
import SelectWorkspaceClass from "./selectClass";
import { PageWithSettingsSubMenu } from "./PageWithSettingsSubMenu";
import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode";
import { WorkspaceClasses } from "@gitpod/gitpod-protocol";

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

Expand Down Expand Up @@ -49,13 +50,29 @@ export default function Preferences() {
}
};

const setWorkspaceClass = async (value: string) => {
const additionalData = user?.additionalData || {};
const prevWorkspaceClass = additionalData?.workspaceClasses?.regular;
const workspaceClasses = (additionalData?.workspaceClasses || {}) as WorkspaceClasses;
workspaceClasses.regular = value;
workspaceClasses.prebuild = value;
additionalData.workspaceClasses = workspaceClasses;
if (value !== prevWorkspaceClass) {
await getGitpodService().server.updateLoggedInUser({ additionalData });
}
return prevWorkspaceClass;
};

return (
<div>
<PageWithSettingsSubMenu title="Preferences" subtitle="Configure user preferences.">
<h3>Editor</h3>
<p className="text-base text-gray-500 dark:text-gray-400">Choose the editor for opening workspaces.</p>
<SelectIDE location="preferences" />
<SelectWorkspaceClass enabled={BillingMode.canSetWorkspaceClass(userBillingMode)} />
<SelectWorkspaceClass
enabled={BillingMode.canSetWorkspaceClass(userBillingMode)}
setWorkspaceClass={setWorkspaceClass}
/>
<h3 className="mt-12">Theme</h3>
<p className="text-base text-gray-500 dark:text-gray-400">Early bird or night owl? Choose your side.</p>
<div className="mt-4 space-x-3 flex">
Expand Down
14 changes: 4 additions & 10 deletions components/dashboard/src/settings/selectClass.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,23 @@ import { useContext, useEffect, useState } from "react";
import { getGitpodService } from "../service/service";
import { UserContext } from "../user-context";
import { trackEvent } from "../Analytics";
import { WorkspaceClasses } from "@gitpod/gitpod-protocol";
import WorkspaceClass from "../components/WorkspaceClass";
import { SupportedWorkspaceClass } from "@gitpod/gitpod-protocol/lib/workspace-class";

interface SelectWorkspaceClassProps {
enabled: boolean;
setWorkspaceClass: (value: string) => Promise<string | undefined>;
}

export default function SelectWorkspaceClass(props: SelectWorkspaceClassProps) {
const { user } = useContext(UserContext);

const [workspaceClass, setWorkspaceClass] = useState<string>(user?.additionalData?.workspaceClasses?.regular || "");
const actuallySetWorkspaceClass = async (value: string) => {
const additionalData = user?.additionalData || {};
const prevWorkspaceClass = additionalData?.workspaceClasses?.regular || "";
const workspaceClasses = (additionalData?.workspaceClasses || {}) as WorkspaceClasses;
workspaceClasses.regular = value;
workspaceClasses.prebuild = value;
additionalData.workspaceClasses = workspaceClasses;
if (value !== prevWorkspaceClass) {
await getGitpodService().server.updateLoggedInUser({ additionalData });
const previousValue = await props.setWorkspaceClass(value);
if (previousValue !== value) {
trackEvent("workspace_class_changed", {
previous: prevWorkspaceClass,
previous: previousValue,
current: value,
});
setWorkspaceClass(value);
Expand Down
4 changes: 3 additions & 1 deletion components/gitpod-protocol/src/teams-projects-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* See License-AGPL.txt in the project root for license information.
*/

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

export interface Project {
Expand Down
5 changes: 0 additions & 5 deletions components/server/ee/src/container-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ import { ChargebeeService } from "./user/chargebee-service";
import { StripeService } from "./user/stripe-service";
import { EligibilityService } from "./user/eligibility-service";
import { AccountStatementProvider } from "./user/account-statement-provider";
import { WorkspaceStarterEE } from "./workspace/workspace-starter";
import { WorkspaceStarter } from "../../src/workspace/workspace-starter";
import { UserDeletionService } from "../../src/user/user-deletion-service";
import { BlockedUserFilter } from "../../src/auth/blocked-user-filter";
import { EMailDomainService, EMailDomainServiceImpl } from "./auth/email-domain-service";
Expand Down Expand Up @@ -105,9 +103,6 @@ export const productionEEContainerModule = new ContainerModule((bind, unbind, is
bind(UserDeletionServiceEE).toSelf().inSingletonScope();
rebind(UserDeletionService).to(UserDeletionServiceEE).inSingletonScope();

// workspace management
rebind(WorkspaceStarter).to(WorkspaceStarterEE).inSingletonScope();

// acounting
bind(AccountService).to(AccountServiceImpl).inSingletonScope();
bind(SubscriptionService).toSelf().inSingletonScope();
Expand Down
11 changes: 8 additions & 3 deletions components/server/ee/src/prebuilds/prebuild-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ export class PrebuildManager {
} else {
span.setTag("starting", true);
const projectEnvVars = await projectEnvVarsPromise;
await this.workspaceStarter.startWorkspace({ span }, workspace, user, [], projectEnvVars, {
await this.workspaceStarter.startWorkspace({ span }, workspace, user, project, [], projectEnvVars, {
excludeFeatureFlags: ["full_workspace_backup"],
});
}
Expand All @@ -273,7 +273,12 @@ export class PrebuildManager {
}
}

async retriggerPrebuild(ctx: TraceContext, user: User, workspaceId: string): Promise<StartPrebuildResult> {
async retriggerPrebuild(
ctx: TraceContext,
user: User,
project: Project | undefined,
workspaceId: string,
): Promise<StartPrebuildResult> {
const span = TraceContext.startSpan("retriggerPrebuild", ctx);
span.setTag("workspaceId", workspaceId);
try {
Expand All @@ -297,7 +302,7 @@ export class PrebuildManager {
if (workspace.projectId) {
projectEnvVars = await this.projectService.getProjectEnvironmentVariables(workspace.projectId);
}
await this.workspaceStarter.startWorkspace({ span }, workspace, user, [], projectEnvVars);
await this.workspaceStarter.startWorkspace({ span }, workspace, user, project, [], projectEnvVars);
return { prebuildId: prebuild.id, wsid: workspace.id, done: false };
} catch (err) {
TraceContext.setError({ span }, err);
Expand Down
5 changes: 4 additions & 1 deletion components/server/ee/src/workspace/gitpod-server-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1060,7 +1060,10 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
// queued for long than a minute? Let's retrigger
console.warn("Retriggering queued prebuild.", prebuiltWorkspace);
try {
await this.prebuildManager.retriggerPrebuild(ctx, user, workspaceID);
const project = prebuiltWorkspace.projectId
? await this.projectDB.findProjectById(prebuiltWorkspace.projectId)
: undefined;
await this.prebuildManager.retriggerPrebuild(ctx, user, project, workspaceID);
} catch (err) {
console.error(err);
}
Expand Down
39 changes: 0 additions & 39 deletions components/server/ee/src/workspace/workspace-starter.ts

This file was deleted.

8 changes: 7 additions & 1 deletion components/server/src/workspace/gitpod-server-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -718,7 +718,10 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
throw new ResponseError(ErrorCodes.PERMISSION_DENIED, "Cannot (re-)start a deleted workspace.");
}
const userEnvVars = this.userDB.getEnvVars(user.id);
let projectEnvVarsPromise = this.internalGetProjectEnvVars(workspace.projectId);
const projectEnvVarsPromise = this.internalGetProjectEnvVars(workspace.projectId);
const projectPromise = workspace.projectId
? this.projectDB.findProjectById(workspace.projectId)
: Promise.resolve(undefined);

await mayStartPromise;

Expand All @@ -727,6 +730,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
ctx,
workspace,
user,
await projectPromise,
await userEnvVars,
await projectEnvVarsPromise,
{
Expand Down Expand Up @@ -1199,6 +1203,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
ctx,
workspace,
user,
project,
await envVars,
await projectEnvVarsPromise,
);
Expand Down Expand Up @@ -2980,6 +2985,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
let user = this.checkAndBlockUser("getSupportedWorkspaceClasses");
let selectedClass = await WorkspaceClasses.getConfiguredOrUpgradeFromLegacy(
user,
undefined,
this.config.workspaceClasses,
this.entitlementService,
);
Expand Down
6 changes: 5 additions & 1 deletion components/server/src/workspace/workspace-classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import { WorkspaceDB } from "@gitpod/gitpod-db/lib";
import { User, Workspace } from "@gitpod/gitpod-protocol";
import { Project, User, Workspace } from "@gitpod/gitpod-protocol";
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing";
import { EntitlementService } from "../billing/entitlement-service";
Expand Down Expand Up @@ -159,9 +159,13 @@ export namespace WorkspaceClasses {
*/
export async function getConfiguredOrUpgradeFromLegacy(
user: User,
project: Project | undefined,
classes: WorkspaceClassesConfig,
entitlementService: EntitlementService,
): Promise<string> {
if (project?.settings?.workspaceClasses?.regular) {
return project?.settings?.workspaceClasses?.regular;
}
if (user.additionalData?.workspaceClasses?.regular) {
return user.additionalData?.workspaceClasses?.regular;
}
Expand Down
1 change: 1 addition & 0 deletions components/server/src/workspace/workspace-starter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ async function execute(builder: WorkspaceClassTestBuilder, expectedClass: string
workspace,
previousInstance,
user,
undefined,
entitlementService,
config,
workspaceDb,
Expand Down
13 changes: 12 additions & 1 deletion components/server/src/workspace/workspace-starter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import {
WithReferrerContext,
EnvVarWithValue,
BillingTier,
Project,
} from "@gitpod/gitpod-protocol";
import { IAnalyticsWriter } from "@gitpod/gitpod-protocol/lib/analytics";
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
Expand Down Expand Up @@ -204,6 +205,7 @@ export async function getWorkspaceClassForInstance(
workspace: Workspace,
previousInstance: WorkspaceInstance | undefined,
user: User,
project: Project | undefined,
entitlementService: EntitlementService,
config: WorkspaceClassesConfig,
workspaceDb: DBWithTracing<WorkspaceDB>,
Expand All @@ -217,17 +219,22 @@ export async function getWorkspaceClassForInstance(
if (prebuildClass) {
const userClass = await WorkspaceClasses.getConfiguredOrUpgradeFromLegacy(
user,
project,
config,
entitlementService,
);
workspaceClass = WorkspaceClasses.selectClassForRegular(prebuildClass, userClass, config);
} else if (project?.settings?.workspaceClasses?.regular) {
workspaceClass = project?.settings?.workspaceClasses?.regular;
} else if (user.additionalData?.workspaceClasses?.regular) {
workspaceClass = user.additionalData?.workspaceClasses?.regular;
}
}

if (workspace.type == "prebuild") {
if (user.additionalData?.workspaceClasses?.prebuild) {
if (project?.settings?.workspaceClasses?.prebuild) {
workspaceClass = project?.settings?.workspaceClasses?.prebuild;
} else if (user.additionalData?.workspaceClasses?.prebuild) {
workspaceClass = user.additionalData?.workspaceClasses?.prebuild;
}
}
Expand Down Expand Up @@ -283,6 +290,7 @@ export class WorkspaceStarter {
ctx: TraceContext,
workspace: Workspace,
user: User,
project: Project | undefined,
userEnvVars: UserEnvVar[],
projectEnvVars: ProjectEnvVar[],
options?: StartWorkspaceOptions,
Expand Down Expand Up @@ -360,6 +368,7 @@ export class WorkspaceStarter {
workspace,
lastValidWorkspaceInstance,
user,
project,
options.excludeFeatureFlags || [],
ideConfig,
),
Expand Down Expand Up @@ -768,6 +777,7 @@ export class WorkspaceStarter {
workspace: Workspace,
previousInstance: WorkspaceInstance | undefined,
user: User,
project: Project | undefined,
excludeFeatureFlags: NamedWorkspaceFeatureFlag[],
ideConfig: IDEConfig,
): Promise<WorkspaceInstance> {
Expand Down Expand Up @@ -874,6 +884,7 @@ export class WorkspaceStarter {
workspace,
previousInstance,
user,
project,
this.entitlementService,
this.config.workspaceClasses,
this.workspaceDb,
Expand Down

0 comments on commit 5ea3bfd

Please sign in to comment.