Skip to content

Commit

Permalink
Define create workspace proto
Browse files Browse the repository at this point in the history
  • Loading branch information
mustard-mh committed Nov 16, 2023
1 parent 4324fcb commit ae291ff
Show file tree
Hide file tree
Showing 28 changed files with 2,162 additions and 295 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,34 @@
* See License.AGPL.txt in the project root for license information.
*/

import { GitpodServer, WorkspaceCreationResult } from "@gitpod/gitpod-protocol";
import { useMutation } from "@tanstack/react-query";
import { getGitpodService } from "../../service/service";
import { useState } from "react";
import { StartWorkspaceError } from "../../start/StartPage";
import { workspaceClient } from "../../service/public-api";
import {
CreateAndStartWorkspaceRequest,
CreateAndStartWorkspaceResponse,
} from "@gitpod/public-api/lib/gitpod/v1/workspace_pb";
import { PartialMessage } from "@bufbuild/protobuf";
import { ConnectError } from "@connectrpc/connect";

export const useCreateWorkspaceMutation = () => {
const [isStarting, setIsStarting] = useState(false);
const mutation = useMutation<WorkspaceCreationResult, StartWorkspaceError, GitpodServer.CreateWorkspaceOptions>({
const mutation = useMutation<
CreateAndStartWorkspaceResponse,
ConnectError,
PartialMessage<CreateAndStartWorkspaceRequest>
>({
mutationFn: async (options) => {
return await getGitpodService().server.createWorkspace(options);
return await workspaceClient.createAndStartWorkspace(options);
},
onMutate: async (options: GitpodServer.CreateWorkspaceOptions) => {
onMutate: async (options: PartialMessage<CreateAndStartWorkspaceRequest>) => {
setIsStarting(true);
},
onError: (error) => {
setIsStarting(false);
},
onSuccess: (result) => {
if (result && result.createdWorkspaceId) {
if (result.workspace?.id) {
// successfully started a workspace, wait a bit before we allow to start another one
setTimeout(() => {
setIsStarting(false);
Expand All @@ -34,7 +42,7 @@ export const useCreateWorkspaceMutation = () => {
},
});
return {
createWorkspace: (options: GitpodServer.CreateWorkspaceOptions) => {
createWorkspace: (options: PartialMessage<CreateAndStartWorkspaceRequest>) => {
return mutation.mutateAsync(options);
},
// Can we use mutation.isLoading here instead?
Expand Down
47 changes: 47 additions & 0 deletions components/dashboard/src/service/json-rpc-workspace-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ import { CallOptions, Code, ConnectError, PromiseClient } from "@connectrpc/conn
import { PartialMessage } from "@bufbuild/protobuf";
import { WorkspaceService } from "@gitpod/public-api/lib/gitpod/v1/workspace_connect";
import {
CreateAndStartWorkspaceRequest,
CreateAndStartWorkspaceResponse,
GetWorkspaceRequest,
GetWorkspaceResponse,
StartWorkspaceRequest,
StartWorkspaceResponse,
WatchWorkspaceStatusRequest,
WatchWorkspaceStatusResponse,
ListWorkspacesRequest,
Expand Down Expand Up @@ -108,4 +112,47 @@ export class JsonRpcWorkspaceClient implements PromiseClient<typeof WorkspaceSer
response.pagination.total = resultTotal;
return response;
}

async createAndStartWorkspace(
request: PartialMessage<CreateAndStartWorkspaceRequest>,
_options?: CallOptions | undefined,
) {
if (request.source?.case !== "contextUrl") {
throw new ConnectError("not implemented", Code.Unimplemented);
}
if (!request.organizationId || !uuidValidate(request.organizationId)) {
throw new ConnectError("organizationId is required", Code.InvalidArgument);
}
if (!request.editor) {
throw new ConnectError("editor is required", Code.InvalidArgument);
}
const response = await getGitpodService().server.createWorkspace({
organizationId: request.organizationId,
ignoreRunningWorkspaceOnSameCommit: true,
contextUrl: request.source.value,
forceDefaultConfig: request.forceDefaultConfig,
workspaceClass: request.workspaceClass,
ideSettings: {
defaultIde: request.editor.name,
useLatestVersion: request.editor.version === "latest",
},
});
const workspace = await this.getWorkspace({ workspaceId: response.createdWorkspaceId });
const result = new CreateAndStartWorkspaceResponse();
result.workspace = workspace.workspace;
return result;
}

async startWorkspace(request: PartialMessage<StartWorkspaceRequest>, _options?: CallOptions | undefined) {
if (!request.workspaceId) {
throw new ConnectError("workspaceId is required", Code.InvalidArgument);
}
await getGitpodService().server.startWorkspace(request.workspaceId, {
forceDefaultImage: request.forceDefaultConfig,
});
const workspace = await this.getWorkspace({ workspaceId: request.workspaceId });
const result = new StartWorkspaceResponse();
result.workspace = workspace.workspace;
return result;
}
}
60 changes: 29 additions & 31 deletions components/dashboard/src/workspaces/CreateWorkspacePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,7 @@
* See License.AGPL.txt in the project root for license information.
*/

import {
AdditionalUserData,
CommitContext,
GitpodServer,
SuggestedRepository,
WithReferrerContext,
} from "@gitpod/gitpod-protocol";
import { AdditionalUserData, CommitContext, SuggestedRepository, WithReferrerContext } from "@gitpod/gitpod-protocol";
import { SelectAccountPayload } from "@gitpod/gitpod-protocol/lib/auth";
import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
import { Deferred } from "@gitpod/gitpod-protocol/lib/util/deferred";
Expand Down Expand Up @@ -45,6 +39,8 @@ import { settingsPathIntegrations } from "../user-settings/settings.routes";
import { WorkspaceEntry } from "./WorkspaceEntry";
import { AuthProviderType } from "@gitpod/public-api/lib/gitpod/v1/authprovider_pb";
import { WorkspacePhase_Phase } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb";
import { CreateAndStartWorkspaceRequest } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb";
import { PartialMessage } from "@bufbuild/protobuf";

export function CreateWorkspacePage() {
const { user, setUser } = useContext(UserContext);
Expand Down Expand Up @@ -175,12 +171,20 @@ export function CreateWorkspacePage() {
const [selectAccountError, setSelectAccountError] = useState<SelectAccountPayload | undefined>(undefined);

const createWorkspace = useCallback(
async (options?: Omit<GitpodServer.CreateWorkspaceOptions, "contextUrl" | "organizationId">) => {
async (options?: Omit<PartialMessage<CreateAndStartWorkspaceRequest>, "contextUrl" | "organizationId">) => {
// add options from search params
const opts = options || {};

// we already have shown running workspaces to the user
opts.ignoreRunningWorkspaceOnSameCommit = true;
if (!contextURL) {
return;
}

const organizationId = currentOrg?.id;
if (!organizationId) {
// We need an organizationId for this group of users
console.error("Skipping createWorkspace");
return;
}

// if user received an INVALID_GITPOD_YML yml for their contextURL they can choose to proceed using default configuration
if (workspaceContext.error?.code === ErrorCodes.INVALID_GITPOD_YML) {
Expand All @@ -190,22 +194,12 @@ export function CreateWorkspacePage() {
if (!opts.workspaceClass) {
opts.workspaceClass = selectedWsClass;
}
if (!opts.ideSettings) {
opts.ideSettings = {
defaultIde: selectedIde,
useLatestVersion: useLatestIde,
if (!opts.editor) {
opts.editor = {
name: selectedIde,
version: useLatestIde ? "latest" : undefined,
};
}
if (!contextURL) {
return;
}

const organizationId = currentOrg?.id;
if (!organizationId) {
// We need an organizationId for this group of users
console.error("Skipping createWorkspace");
return;
}

try {
if (createWorkspaceMutation.isStarting) {
Expand All @@ -214,18 +208,22 @@ export function CreateWorkspacePage() {
}
// we wait at least 5 secs
const timeout = new Promise((resolve) => setTimeout(resolve, 5000));

const result = await createWorkspaceMutation.createWorkspace({
contextUrl: contextURL,
organizationId,
projectId: selectedProjectID,
source: {
case: "contextUrl",
value: contextURL,
},
...opts,
organizationId,
configurationId: selectedProjectID,
});
await storeAutoStartOptions();
await timeout;
if (result.workspaceURL) {
window.location.href = result.workspaceURL;
} else if (result.createdWorkspaceId) {
history.push(`/start/#${result.createdWorkspaceId}`);
if (result.workspace?.status?.workspaceUrl) {
window.location.href = result.workspace.status.workspaceUrl;
} else if (result.workspace!.id) {
history.push(`/start/#${result.workspace!.id}`);
}
} catch (error) {
console.log(error);
Expand Down
24 changes: 24 additions & 0 deletions components/public-api/gitpod/v1/context.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
syntax = "proto3";

package gitpod.v1;

option go_package = "github.com/gitpod-io/gitpod/components/public-api/go/v1";

service ContextService {
// ParseContext parses the url and returns the context
rpc ParseContext(ParseContextRequest) returns (ParseContextResponse) {}
}

message ParseContextRequest {
string url = 1;
}

message ParseContextResponse {
Context context = 1;
}

message Context {
string title = 1;
string normalized_url = 2;
string ref = 3;
}
69 changes: 69 additions & 0 deletions components/public-api/gitpod/v1/workspace.proto
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ service WorkspaceService {

// ListWorkspaces returns a list of workspaces that match the query.
rpc ListWorkspaces(ListWorkspacesRequest) returns (ListWorkspacesResponse) {}

// CreateAndStartWorkspace creates a new workspace and starts it.
rpc CreateAndStartWorkspace(CreateAndStartWorkspaceRequest)
returns (CreateAndStartWorkspaceResponse) {}

// StartWorkspace starts an existing workspace.
rpc StartWorkspace(StartWorkspaceRequest) returns (StartWorkspaceResponse) {}
}

message GetWorkspaceRequest {
Expand Down Expand Up @@ -72,6 +79,68 @@ message ListWorkspacesResponse {
PaginationResponse pagination = 2;
}

message CreateAndStartWorkspaceRequest {

message Git {
// clone_url is the URL of the repository to clone
string clone_url = 1;

// ref is an alternatively symbolic. e.g. refs/tags/v1.0,
// empty string means the default branch of the repository
string ref = 2;

// create_local_branch is the branch you want to create based on provided
// clone_url and ref when workspace started
string create_local_branch = 3;
}

string organization_id = 1;

string configuration_id = 2;

// source describes the source refer of workspace.
oneof source {

// git describes the source refer of workspace
// Obtain available git using the ContextService.ParseContext operation if
// not sure about it.
Git git = 3;

string context_url = 4;
}

// additional_env_variables provide additional environment variables to the
// workspace.
// It will take precedence over environment variables provided by
// the user and the configuration
repeated WorkspaceEnvironmentVariable additional_env_variables = 5;

string region = 6;

string workspace_class = 7;

EditorReference editor = 8;

string name = 9;

bool pinned = 10;

// force_default_config indicates that the workspace should be created with
// the default configuration instead of the configuration provided in
// `.gitpod.yml` file
bool force_default_config = 11;
}

message CreateAndStartWorkspaceResponse { Workspace workspace = 1; }

message StartWorkspaceRequest {
string workspace_id = 1;

bool force_default_config = 2;
}

message StartWorkspaceResponse { Workspace workspace = 1; }

// +resource get workspace
message Workspace {
string id = 1;
Expand Down
Loading

0 comments on commit ae291ff

Please sign in to comment.