From 4db80e793cc7d97efb2d6bd4cc6d70ff96efc5db Mon Sep 17 00:00:00 2001 From: Thomas Schubart Date: Fri, 22 Jul 2022 11:49:06 +0000 Subject: [PATCH 1/5] [server] Add endpoint for retrieving workspace classes --- components/gitpod-protocol/src/gitpod-service.ts | 3 +++ components/gitpod-protocol/src/workspace-class.ts | 13 +++++++++++++ components/server/src/auth/rate-limiter.ts | 1 + .../server/src/workspace/gitpod-server-impl.ts | 15 +++++++++++++++ .../server/src/workspace/workspace-classes.ts | 9 +++++++++ 5 files changed, 41 insertions(+) create mode 100644 components/gitpod-protocol/src/workspace-class.ts diff --git a/components/gitpod-protocol/src/gitpod-service.ts b/components/gitpod-protocol/src/gitpod-service.ts index 8be6f5339a45de..f5d12018c6fa48 100644 --- a/components/gitpod-protocol/src/gitpod-service.ts +++ b/components/gitpod-protocol/src/gitpod-service.ts @@ -62,6 +62,7 @@ import { IDEServer } from "./ide-protocol"; import { InstallationAdminSettings, TelemetryData } from "./installation-admin-protocol"; import { Currency } from "./plans"; import { BillableSession } from "./usage"; +import { SupportedWorkspaceClass } from "./workspace-class"; export interface GitpodClient { onInstanceUpdate(instance: WorkspaceInstance): void; @@ -307,6 +308,8 @@ export interface GitpodServer extends JsonRpcServer, AdminServer, * Frontend notifications */ getNotifications(): Promise; + + getSupportedWorkspaceClasses(): Promise; } export interface RateLimiterError { diff --git a/components/gitpod-protocol/src/workspace-class.ts b/components/gitpod-protocol/src/workspace-class.ts new file mode 100644 index 00000000000000..68bca3ec8a79b8 --- /dev/null +++ b/components/gitpod-protocol/src/workspace-class.ts @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2022 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License-AGPL.txt in the project root for license information. + */ + +export interface SupportedWorkspaceClass { + id: string; + category: string; + displayName: string; + description: string; + powerups: number; +} diff --git a/components/server/src/auth/rate-limiter.ts b/components/server/src/auth/rate-limiter.ts index ba0416f691bd22..b4bb8e3bff1391 100644 --- a/components/server/src/auth/rate-limiter.ts +++ b/components/server/src/auth/rate-limiter.ts @@ -220,6 +220,7 @@ function getConfig(config: RateLimiterConfig): RateLimiterConfig { getSpendingLimitForTeam: { group: "default", points: 1 }, setSpendingLimitForTeam: { group: "default", points: 1 }, getNotifications: { group: "default", points: 1 }, + getSupportedWorkspaceClasses: { group: "default", points: 1 }, }; return { diff --git a/components/server/src/workspace/gitpod-server-impl.ts b/components/server/src/workspace/gitpod-server-impl.ts index 7767c15a332d01..2f5db15d7f2037 100644 --- a/components/server/src/workspace/gitpod-server-impl.ts +++ b/components/server/src/workspace/gitpod-server-impl.ts @@ -115,6 +115,7 @@ import { RemotePageMessage, RemoteTrackMessage, } from "@gitpod/gitpod-protocol/lib/analytics"; +import { SupportedWorkspaceClass } from "@gitpod/gitpod-protocol/lib/workspace-class"; import { ImageBuilderClientProvider, LogsRequest } from "@gitpod/image-builder/lib"; import { WorkspaceManagerClientProvider } from "@gitpod/ws-manager/lib/client-provider"; import { @@ -3042,6 +3043,20 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { return ideConfig.ideOptions; } + async getSupportedWorkspaceClasses(ctx: TraceContext): Promise { + let classes = this.config.workspaceClasses + .filter((c) => !c.deprecated) + .map((c) => ({ + id: c.id, + category: c.category, + displayName: c.displayName, + description: c.description, + powerups: c.powerups, + })); + + return classes; + } + //#region gitpod.io concerns // async adminGetAccountStatement(ctx: TraceContext, userId: string): Promise { diff --git a/components/server/src/workspace/workspace-classes.ts b/components/server/src/workspace/workspace-classes.ts index ebbaa6c75dd25e..d8bf42d754d9d8 100644 --- a/components/server/src/workspace/workspace-classes.ts +++ b/components/server/src/workspace/workspace-classes.ts @@ -15,9 +15,18 @@ export interface WorkspaceClassConfig { // Is the "default" class. The config is validated to only every have exactly _one_ default class. isDefault: boolean; + // Identifies which category this class belongs to e.g. general purpose + category: string; + // The string we display to users in the UI displayName: string; + // The description for the workspace class + description: string; + + // The "power level" of the workspace class + powerups: number; + // Whether or not to: // - offer users this Workspace class for selection // - use this class to start workspaces with. If a user has a class marked like this configured and starts a workspace they get the default class instead. From 1e7514e949331ec0dbace0e394de85bfb26ac26e Mon Sep 17 00:00:00 2001 From: Thomas Schubart Date: Fri, 22 Jul 2022 12:42:52 +0000 Subject: [PATCH 2/5] [dashboard] Get classes from server --- .../dashboard/src/settings/selectClass.tsx | 51 ++++++++++--------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/components/dashboard/src/settings/selectClass.tsx b/components/dashboard/src/settings/selectClass.tsx index 17ec3f0223aabc..dd67bac8692609 100644 --- a/components/dashboard/src/settings/selectClass.tsx +++ b/components/dashboard/src/settings/selectClass.tsx @@ -4,12 +4,13 @@ * See License-AGPL.txt in the project root for license information. */ -import { useContext, useState } from "react"; +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; @@ -18,12 +19,10 @@ interface SelectWorkspaceClassProps { export default function SelectWorkspaceClass(props: SelectWorkspaceClassProps) { const { user } = useContext(UserContext); - const [workspaceClass, setWorkspaceClass] = useState( - user?.additionalData?.workspaceClasses?.regular || "g1-standard", - ); + const [workspaceClass, setWorkspaceClass] = useState(user?.additionalData?.workspaceClasses?.regular || ""); const actuallySetWorkspaceClass = async (value: string) => { const additionalData = user?.additionalData || {}; - const prevWorkspaceClass = additionalData?.workspaceClasses?.regular || "g1-standard"; + const prevWorkspaceClass = additionalData?.workspaceClasses?.regular || ""; const workspaceClasses = (additionalData?.workspaceClasses || {}) as WorkspaceClasses; workspaceClasses.regular = value; workspaceClasses.prebuild = value; @@ -38,6 +37,17 @@ export default function SelectWorkspaceClass(props: SelectWorkspaceClassProps) { } }; + const [supportedClasses, setSupportedClasses] = useState([]); + + useEffect(() => { + const fetchClasses = async () => { + const classes = await getGitpodService().server.getSupportedWorkspaceClasses(); + setSupportedClasses(classes); + }; + + fetchClasses().catch(console.error); + }, []); + if (!props.enabled) { return
; } else { @@ -48,24 +58,19 @@ export default function SelectWorkspaceClass(props: SelectWorkspaceClassProps) { Choose the workspace machine type for your workspaces.

- actuallySetWorkspaceClass("g1-standard")} - category="GENERAL PURPOSE" - friendlyName="Standard" - description="Up to 4 vCPU, 8GB memory, 30GB disk" - powerUps={1} - /> - actuallySetWorkspaceClass("g1-large")} - category="GENERAL PURPOSE" - friendlyName="Large" - description="Up to 8 vCPU, 16GB memory, 50GB disk" - powerUps={2} - /> + {supportedClasses.map((c) => { + return ( + actuallySetWorkspaceClass(c.id)} + category={c.category} + friendlyName={c.displayName} + description={c.description} + powerUps={c.powerups} + /> + ); + })}
); From 1299ba8ed4d7c4a7a0b78556468db2158884433b Mon Sep 17 00:00:00 2001 From: Thomas Schubart Date: Fri, 22 Jul 2022 12:54:54 +0000 Subject: [PATCH 3/5] [installer] Extend workspace class info --- .../pkg/components/server/configmap.go | 9 ++++++++- .../installer/pkg/components/server/types.go | 19 ++++++++++++++----- .../config/v1/experimental/experimental.go | 3 +++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/install/installer/pkg/components/server/configmap.go b/install/installer/pkg/components/server/configmap.go index db0d8054f012f2..c960fb63c36d2a 100644 --- a/install/installer/pkg/components/server/configmap.go +++ b/install/installer/pkg/components/server/configmap.go @@ -8,6 +8,7 @@ import ( "fmt" "net" "strconv" + "strings" "github.com/gitpod-io/gitpod/installer/pkg/common" "github.com/gitpod-io/gitpod/installer/pkg/components/usage" @@ -137,7 +138,10 @@ func configmap(ctx *common.RenderContext) ([]runtime.Object, error) { workspaceClasses := []WorkspaceClass{ { Id: config.DefaultWorkspaceClass, - DisplayName: config.DefaultWorkspaceClass, + Category: GeneralPurpose, + DisplayName: strings.Title(config.DefaultWorkspaceClass), + Description: "Default workspace class", + PowerUps: 1, IsDefault: true, Deprecated: false, }, @@ -148,7 +152,10 @@ func configmap(ctx *common.RenderContext) ([]runtime.Object, error) { for _, cl := range cfg.WebApp.WorkspaceClasses { class := WorkspaceClass{ Id: cl.Id, + Category: WorkspaceClassCategory(cl.Category), DisplayName: cl.DisplayName, + Description: cl.Description, + PowerUps: cl.PowerUps, IsDefault: cl.IsDefault, Deprecated: cl.Deprecated, Marker: cl.Marker, diff --git a/install/installer/pkg/components/server/types.go b/install/installer/pkg/components/server/types.go index b5612c96347ccb..ee634a99714ae0 100644 --- a/install/installer/pkg/components/server/types.go +++ b/install/installer/pkg/components/server/types.go @@ -129,11 +129,14 @@ type WorkspaceDefaults struct { } type WorkspaceClass struct { - Id string `json:"id"` - DisplayName string `json:"displayName"` - IsDefault bool `json:"isDefault"` - Deprecated bool `json:"deprecated"` - Marker map[string]bool `json:"marker,omitempty"` + Id string `json:"id"` + Category WorkspaceClassCategory `json:"category"` + DisplayName string `json:"displayName"` + Description string `json:"description"` + PowerUps uint32 `json:"powerups"` + IsDefault bool `json:"isDefault"` + Deprecated bool `json:"deprecated"` + Marker map[string]bool `json:"marker,omitempty"` } type NamedWorkspaceFeatureFlag string @@ -142,3 +145,9 @@ const ( NamedWorkspaceFeatureFlagFullWorkspaceBackup NamedWorkspaceFeatureFlag = "full_workspace_backup" NamedWorkspaceFeatureFlagFixedResources NamedWorkspaceFeatureFlag = "fixed_resources" ) + +type WorkspaceClassCategory string + +const ( + GeneralPurpose WorkspaceClassCategory = "GENERAL PURPOSE" +) diff --git a/install/installer/pkg/config/v1/experimental/experimental.go b/install/installer/pkg/config/v1/experimental/experimental.go index ac6c3e25055c9f..864a3d78768e55 100644 --- a/install/installer/pkg/config/v1/experimental/experimental.go +++ b/install/installer/pkg/config/v1/experimental/experimental.go @@ -209,7 +209,10 @@ type UsageConfig struct { type WebAppWorkspaceClass struct { Id string `json:"id"` + Category string `json:"category"` DisplayName string `json:"displayName"` + Description string `json:"description"` + PowerUps uint32 `json:"powerups"` IsDefault bool `json:"isDefault"` Deprecated bool `json:"deprecated"` Marker map[string]bool `json:"marker,omitempty"` From 29778a56208013441ae03e17ba3ba707345af8f7 Mon Sep 17 00:00:00 2001 From: Thomas Schubart Date: Fri, 22 Jul 2022 15:17:07 +0000 Subject: [PATCH 4/5] [dashboard] Update service mocks --- .../dashboard/src/service/service-mock.ts | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/components/dashboard/src/service/service-mock.ts b/components/dashboard/src/service/service-mock.ts index d416d04d04409e..ac7841ed8c1639 100644 --- a/components/dashboard/src/service/service-mock.ts +++ b/components/dashboard/src/service/service-mock.ts @@ -210,6 +210,59 @@ const gitpodServiceMock = createServiceMock({ onDidCloseConnection: Event.None, trackEvent: async (event) => {}, trackLocation: async (event) => {}, + getSupportedWorkspaceClasses: async () => { + return [ + { + id: "g1-standard", + category: "GENERAL PURPOSE", + displayName: "Standard", + description: "Up to 4 vCPU, 8GB memory, 30GB disk", + powerups: 1, + }, + { + id: "g1-large", + category: "GENERAL PURPOSE", + displayName: "Large", + description: "Up to 8 vCPU, 16GB memory, 50GB disk", + powerups: 2, + }, + ]; + }, + getShowPaymentUI: async () => { + return false; + }, + getClientRegion: async () => { + return "europe-west-1"; + }, + isStudent: async () => { + return false; + }, + isChargebeeCustomer: async () => { + return false; + }, + getSuggestedContextURLs: async () => { + return []; + }, + getIDEOptions: async () => { + return { + defaultDesktopIde: "code-desktop", + defaultIde: "code", + options: { + code: { + title: "VS Code", + type: "browser", + logo: "", + image: "eu.gcr.io/gitpod-core-dev/build/ide/code:commit-050c611f28564c6c7b1e58db470f07997dfb4730", + }, + "code-desktop": { + title: "VS Code", + type: "desktop", + logo: "", + image: "eu.gcr.io/gitpod-core-dev/build/ide/code-desktop:commit-9b29fc94cc1f0c776ef74f60dc3a7ce68d41bdbe", + }, + }, + }; + }, }); export { gitpodServiceMock }; From 15875d248c04a492860b5b6d5c0e9aaff69bcb9c Mon Sep 17 00:00:00 2001 From: Thomas Schubart Date: Mon, 25 Jul 2022 11:53:29 +0000 Subject: [PATCH 5/5] [dashboard] Select default class if nothing is selected --- components/dashboard/src/service/service-mock.ts | 2 ++ components/dashboard/src/settings/selectClass.tsx | 6 +++++- components/gitpod-protocol/src/workspace-class.ts | 1 + components/server/src/workspace/gitpod-server-impl.ts | 1 + 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/components/dashboard/src/service/service-mock.ts b/components/dashboard/src/service/service-mock.ts index ac7841ed8c1639..22d395d16c5705 100644 --- a/components/dashboard/src/service/service-mock.ts +++ b/components/dashboard/src/service/service-mock.ts @@ -218,6 +218,7 @@ const gitpodServiceMock = createServiceMock({ displayName: "Standard", description: "Up to 4 vCPU, 8GB memory, 30GB disk", powerups: 1, + isDefault: true, }, { id: "g1-large", @@ -225,6 +226,7 @@ const gitpodServiceMock = createServiceMock({ displayName: "Large", description: "Up to 8 vCPU, 16GB memory, 50GB disk", powerups: 2, + isDefault: false, }, ]; }, diff --git a/components/dashboard/src/settings/selectClass.tsx b/components/dashboard/src/settings/selectClass.tsx index dd67bac8692609..5bc5988a78525d 100644 --- a/components/dashboard/src/settings/selectClass.tsx +++ b/components/dashboard/src/settings/selectClass.tsx @@ -43,10 +43,14 @@ export default function SelectWorkspaceClass(props: SelectWorkspaceClassProps) { const fetchClasses = async () => { const classes = await getGitpodService().server.getSupportedWorkspaceClasses(); setSupportedClasses(classes); + + if (!workspaceClass) { + setWorkspaceClass(supportedClasses.find((c) => c.isDefault)?.id || ""); + } }; fetchClasses().catch(console.error); - }, []); + }); if (!props.enabled) { return
; diff --git a/components/gitpod-protocol/src/workspace-class.ts b/components/gitpod-protocol/src/workspace-class.ts index 68bca3ec8a79b8..3128db9da1060b 100644 --- a/components/gitpod-protocol/src/workspace-class.ts +++ b/components/gitpod-protocol/src/workspace-class.ts @@ -10,4 +10,5 @@ export interface SupportedWorkspaceClass { displayName: string; description: string; powerups: number; + isDefault: boolean; } diff --git a/components/server/src/workspace/gitpod-server-impl.ts b/components/server/src/workspace/gitpod-server-impl.ts index 2f5db15d7f2037..eddc1b81770d7d 100644 --- a/components/server/src/workspace/gitpod-server-impl.ts +++ b/components/server/src/workspace/gitpod-server-impl.ts @@ -3052,6 +3052,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { displayName: c.displayName, description: c.description, powerups: c.powerups, + isDefault: c.isDefault, })); return classes;