Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fetch workspace classes from server #11571

Merged
merged 5 commits into from
Jul 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions components/dashboard/src/service/service-mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,61 @@ 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,
isDefault: true,
},
{
id: "g1-large",
category: "GENERAL PURPOSE",
displayName: "Large",
description: "Up to 8 vCPU, 16GB memory, 50GB disk",
powerups: 2,
isDefault: false,
},
];
},
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 };
55 changes: 32 additions & 23 deletions components/dashboard/src/settings/selectClass.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -18,12 +19,10 @@ interface SelectWorkspaceClassProps {
export default function SelectWorkspaceClass(props: SelectWorkspaceClassProps) {
const { user } = useContext(UserContext);

const [workspaceClass, setWorkspaceClass] = useState<string>(
user?.additionalData?.workspaceClasses?.regular || "g1-standard",
);
const [workspaceClass, setWorkspaceClass] = useState<string>(user?.additionalData?.workspaceClasses?.regular || "");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: On first load I was confused because no class was selected.

It would make sense to highlight the one with isDefault: true, though. Maybe by moving the WorkspacClasses code into gitpod-protocol, und re-using it here...? 🤔 Or we introduce an explicit API method: getDefaultWorkspaceClass 🤷

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

☝️ This can easily be a follow-up issue. 🧘

const actuallySetWorkspaceClass = async (value: string) => {
const additionalData = user?.additionalData || {};
const prevWorkspaceClass = additionalData?.workspaceClasses?.regular || "g1-standard";
const prevWorkspaceClass = additionalData?.workspaceClasses?.regular || "";
Copy link
Member

@geropl geropl Jul 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: The preview env classes read "Standard" and "Default", where Default is the bigger one which was somewhat confusing.
image

💡 We could re-use the SaaS names (+ maybe sizes?).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that is only because I edited the configmap to better illustrate what it would look like. The installer only installs the Default workspace class as Tarun has posted below.

const workspaceClasses = (additionalData?.workspaceClasses || {}) as WorkspaceClasses;
workspaceClasses.regular = value;
workspaceClasses.prebuild = value;
Expand All @@ -38,6 +37,21 @@ export default function SelectWorkspaceClass(props: SelectWorkspaceClassProps) {
}
};

const [supportedClasses, setSupportedClasses] = useState<SupportedWorkspaceClass[]>([]);

useEffect(() => {
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 <div></div>;
} else {
Expand All @@ -48,24 +62,19 @@ export default function SelectWorkspaceClass(props: SelectWorkspaceClassProps) {
Choose the workspace machine type for your workspaces.
</p>
<div className="mt-4 space-x-3 flex">
<WorkspaceClass
additionalStyles="w-80 h-32"
selected={workspaceClass === "g1-standard"}
onClick={() => actuallySetWorkspaceClass("g1-standard")}
category="GENERAL PURPOSE"
friendlyName="Standard"
description="Up to 4 vCPU, 8GB memory, 30GB disk"
powerUps={1}
/>
<WorkspaceClass
additionalStyles="w-80 h-32"
selected={workspaceClass === "g1-large"}
onClick={() => actuallySetWorkspaceClass("g1-large")}
category="GENERAL PURPOSE"
friendlyName="Large"
description="Up to 8 vCPU, 16GB memory, 50GB disk"
powerUps={2}
/>
{supportedClasses.map((c) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Now it becomes apparent that this config is far to hidden. But now we have everything in place to maybe show it on /workspaces / the "open workspace modal" / somewhere else for visible? 😇

☁️ Clearly out-of-scope here, just wanted to raise in this context.

/cc @gtsiolis

return (
<WorkspaceClass
additionalStyles="w-80 h-32"
selected={workspaceClass === c.id}
onClick={() => actuallySetWorkspaceClass(c.id)}
category={c.category}
friendlyName={c.displayName}
description={c.description}
powerUps={c.powerups}
/>
);
})}
</div>
</div>
);
Expand Down
3 changes: 3 additions & 0 deletions components/gitpod-protocol/src/gitpod-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -307,6 +308,8 @@ export interface GitpodServer extends JsonRpcServer<GitpodClient>, AdminServer,
* Frontend notifications
*/
getNotifications(): Promise<string[]>;

getSupportedWorkspaceClasses(): Promise<SupportedWorkspaceClass[]>;
}

export interface RateLimiterError {
Expand Down
14 changes: 14 additions & 0 deletions components/gitpod-protocol/src/workspace-class.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* 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;
isDefault: boolean;
}
1 change: 1 addition & 0 deletions components/server/src/auth/rate-limiter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
16 changes: 16 additions & 0 deletions components/server/src/workspace/gitpod-server-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -3042,6 +3043,21 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
return ideConfig.ideOptions;
}

async getSupportedWorkspaceClasses(ctx: TraceContext): Promise<SupportedWorkspaceClass[]> {
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,
isDefault: c.isDefault,
}));

return classes;
}

//#region gitpod.io concerns
//
async adminGetAccountStatement(ctx: TraceContext, userId: string): Promise<AccountStatement> {
Expand Down
9 changes: 9 additions & 0 deletions components/server/src/workspace/workspace-classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
9 changes: 8 additions & 1 deletion install/installer/pkg/components/server/configmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
},
Expand All @@ -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,
Expand Down
19 changes: 14 additions & 5 deletions install/installer/pkg/components/server/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -142,3 +145,9 @@ const (
NamedWorkspaceFeatureFlagFullWorkspaceBackup NamedWorkspaceFeatureFlag = "full_workspace_backup"
NamedWorkspaceFeatureFlagFixedResources NamedWorkspaceFeatureFlag = "fixed_resources"
)

type WorkspaceClassCategory string

const (
GeneralPurpose WorkspaceClassCategory = "GENERAL PURPOSE"
)
3 changes: 3 additions & 0 deletions install/installer/pkg/config/v1/experimental/experimental.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down