Skip to content

Commit da0c590

Browse files
Refine workspace API (#19138)
* Refine workspace API * Fix build * update proto * fix rebase * fix rebase - 2 * 1 * fix editor * Update doc --------- Co-authored-by: Huiwen <[email protected]>
1 parent 88d65c1 commit da0c590

26 files changed

+5645
-1648
lines changed

components/dashboard/src/data/setup.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import * as SSHClasses from "@gitpod/public-api/lib/gitpod/v1/ssh_pb";
3131
// This is used to version the cache
3232
// If data we cache changes in a non-backwards compatible way, increment this version
3333
// That will bust any previous cache versions a client may have stored
34-
const CACHE_VERSION = "10";
34+
const CACHE_VERSION = "11";
3535

3636
export function noPersistence(queryKey: QueryKey): QueryKey {
3737
return [...queryKey, "no-persistence"];

components/dashboard/src/data/workspaces/toggle-workspace-pinned-mutation.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
88
import { getGitpodService } from "../../service/service";
99
import { getListWorkspacesQueryKey, ListWorkspacesQueryResult } from "./list-workspaces-query";
1010
import { useCurrentOrg } from "../organizations/orgs-query";
11-
import { Workspace } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb";
11+
import { Workspace, WorkspaceMetadata } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb";
1212

1313
type ToggleWorkspacePinnedArgs = {
1414
workspaceId: string;
@@ -33,7 +33,10 @@ export const useToggleWorkspacedPinnedMutation = () => {
3333
return info;
3434
}
3535
const workspace = new Workspace(info);
36-
workspace.pinned = !workspace.pinned;
36+
if (!workspace.metadata) {
37+
workspace.metadata = new WorkspaceMetadata();
38+
}
39+
workspace.metadata.pinned = !workspace.metadata.pinned;
3740
return workspace;
3841
});
3942
});

components/dashboard/src/data/workspaces/toggle-workspace-shared-mutation.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
88
import { getGitpodService } from "../../service/service";
99
import { getListWorkspacesQueryKey, ListWorkspacesQueryResult } from "./list-workspaces-query";
1010
import { useCurrentOrg } from "../organizations/orgs-query";
11-
import { AdmissionLevel, Workspace } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb";
11+
import { AdmissionLevel, Workspace, WorkspaceSpec } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb";
1212

1313
type ToggleWorkspaceSharedArgs = {
1414
workspaceId: string;
@@ -44,9 +44,10 @@ export const useToggleWorkspaceSharedMutation = () => {
4444
}
4545

4646
const workspace = new Workspace(info);
47-
if (workspace.status) {
48-
workspace.status.admission = level;
47+
if (!workspace.spec) {
48+
workspace.spec = new WorkspaceSpec();
4949
}
50+
workspace.spec.admission = level;
5051
return workspace;
5152
});
5253
});

components/dashboard/src/data/workspaces/update-workspace-description-mutation.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
88
import { getGitpodService } from "../../service/service";
99
import { getListWorkspacesQueryKey, ListWorkspacesQueryResult } from "./list-workspaces-query";
1010
import { useCurrentOrg } from "../organizations/orgs-query";
11-
import { Workspace } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb";
11+
import { Workspace, WorkspaceMetadata } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb";
1212

1313
type UpdateWorkspaceDescriptionArgs = {
1414
workspaceId: string;
@@ -36,7 +36,10 @@ export const useUpdateWorkspaceDescriptionMutation = () => {
3636
// TODO: Once the update description response includes an updated record,
3737
// we can return that instead of having to know what to merge manually (same for other mutations)
3838
const workspace = new Workspace(info);
39-
workspace.name = newDescription;
39+
if (!workspace.metadata) {
40+
workspace.metadata = new WorkspaceMetadata();
41+
}
42+
workspace.metadata.name = newDescription;
4043
return workspace;
4144
});
4245
});

components/dashboard/src/service/json-rpc-workspace-client.ts

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ import {
2828
SendHeartBeatResponse,
2929
WorkspacePhase_Phase,
3030
GetWorkspaceDefaultImageResponse_Source,
31+
ParseContextURLRequest,
32+
ParseContextURLResponse,
33+
UpdateWorkspaceRequest,
34+
UpdateWorkspaceResponse,
3135
} from "@gitpod/public-api/lib/gitpod/v1/workspace_pb";
3236
import { converter } from "./public-api";
3337
import { getGitpodService } from "./service";
@@ -131,25 +135,25 @@ export class JsonRpcWorkspaceClient implements PromiseClient<typeof WorkspaceSer
131135
if (request.source?.case !== "contextUrl") {
132136
throw new ApplicationError(ErrorCodes.UNIMPLEMENTED, "not implemented");
133137
}
134-
if (!request.organizationId || !uuidValidate(request.organizationId)) {
138+
if (!request.metadata || !request.metadata.organizationId || !uuidValidate(request.metadata.organizationId)) {
135139
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "organizationId is required");
136140
}
137-
if (!request.editor) {
141+
if (!request.source.value.editor?.name) {
138142
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "editor is required");
139143
}
140-
if (!request.source.value) {
144+
if (!request.source.value.url) {
141145
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "source is required");
142146
}
143147
const response = await getGitpodService().server.createWorkspace({
144-
organizationId: request.organizationId,
148+
organizationId: request.metadata.organizationId,
145149
ignoreRunningWorkspaceOnSameCommit: true,
146-
contextUrl: request.source.value,
150+
contextUrl: request.source.value.url,
147151
forceDefaultConfig: request.forceDefaultConfig,
148-
workspaceClass: request.workspaceClass,
149-
projectId: request.configurationId,
152+
workspaceClass: request.source.value.workspaceClass,
153+
projectId: request.metadata.configurationId,
150154
ideSettings: {
151-
defaultIde: request.editor.name,
152-
useLatestVersion: request.editor.version === "latest",
155+
defaultIde: request.source.value.editor.name,
156+
useLatestVersion: request.source.value.editor.version === "latest",
153157
},
154158
});
155159
const workspace = await this.getWorkspace({ workspaceId: response.createdWorkspaceId });
@@ -240,4 +244,18 @@ export class JsonRpcWorkspaceClient implements PromiseClient<typeof WorkspaceSer
240244
result.editorCredentials = credentials;
241245
return result;
242246
}
247+
248+
async updateWorkspace(
249+
request: PartialMessage<UpdateWorkspaceRequest>,
250+
_options?: CallOptions | undefined,
251+
): Promise<UpdateWorkspaceResponse> {
252+
throw new ApplicationError(ErrorCodes.UNIMPLEMENTED, "not implemented");
253+
}
254+
255+
async parseContextURL(
256+
request: PartialMessage<ParseContextURLRequest>,
257+
_options?: CallOptions | undefined,
258+
): Promise<ParseContextURLResponse> {
259+
throw new ApplicationError(ErrorCodes.UNIMPLEMENTED, "not implemented");
260+
}
243261
}

components/dashboard/src/start/StartWorkspace.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
StartWorkspaceResponse,
2929
Workspace,
3030
WorkspacePhase_Phase,
31+
WorkspaceSpec_WorkspaceType,
3132
} from "@gitpod/public-api/lib/gitpod/v1/workspace_pb";
3233
import { PartialMessage } from "@bufbuild/protobuf";
3334

@@ -169,7 +170,7 @@ export default class StartWorkspace extends React.Component<StartWorkspaceProps,
169170
componentDidUpdate(prevPros: StartWorkspaceProps, prevState: StartWorkspaceState) {
170171
const newPhase = this.state?.workspace?.status?.phase?.name;
171172
const oldPhase = prevState.workspace?.status?.phase?.name;
172-
const type = !!this.state.workspace?.prebuild ? "prebuild" : "regular";
173+
const type = this.state.workspace?.spec?.type === WorkspaceSpec_WorkspaceType.PREBUILD ? "prebuild" : "regular";
173174
if (newPhase !== oldPhase) {
174175
getGitpodService().server.trackEvent({
175176
event: "status_rendered",
@@ -373,10 +374,10 @@ export default class StartWorkspace extends React.Component<StartWorkspaceProps,
373374
if (
374375
!error &&
375376
workspace.status.phase?.name === WorkspacePhase_Phase.STOPPED &&
376-
!!this.state.workspace?.prebuild
377+
this.state.workspace?.spec?.type === WorkspaceSpec_WorkspaceType.PREBUILD
377378
) {
378379
// here we want to point to the original context, w/o any modifiers "workspace" was started with (as this might have been a manually triggered prebuild!)
379-
const contextURL = this.state.workspace.contextUrl;
380+
const contextURL = this.state.workspace.metadata?.originalContextUrl;
380381
if (contextURL) {
381382
this.redirectTo(gitpodHostUrl.withContext(contextURL.toString()).toString());
382383
} else {
@@ -457,15 +458,20 @@ export default class StartWorkspace extends React.Component<StartWorkspaceProps,
457458

458459
render() {
459460
const { error } = this.state;
460-
const isPrebuild = this.state.workspace?.prebuild;
461-
const withPrebuild = !!this.state.workspace?.prebuildId;
461+
const isPrebuild = this.state.workspace?.spec?.type === WorkspaceSpec_WorkspaceType.PREBUILD;
462+
let withPrebuild = false;
463+
for (const initializer of this.state.workspace?.spec?.initializer?.specs ?? []) {
464+
if (initializer.spec.case === "prebuild") {
465+
withPrebuild = !!initializer.spec.value.prebuildId;
466+
}
467+
}
462468
let phase: StartPhase | undefined = StartPhase.Preparing;
463469
let title = undefined;
464470
let isStoppingOrStoppedPhase = false;
465471
let isError = error ? true : false;
466472
let statusMessage = !!error ? undefined : <p className="text-base text-gray-400">Preparing workspace …</p>;
467-
const contextURL = this.state.workspace?.contextUrl;
468-
const useLatest = this.state.workspace?.editor?.version === "latest";
473+
const contextURL = this.state.workspace?.metadata?.originalContextUrl;
474+
const useLatest = this.state.workspace?.spec?.editor?.version === "latest";
469475

470476
switch (this.state?.workspace?.status?.phase?.name) {
471477
// unknown indicates an issue within the system in that it cannot determine the actual phase of

components/dashboard/src/workspaces/CreateWorkspacePage.tsx

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import { SelectAccountModal } from "../user-settings/SelectAccountModal";
3737
import { settingsPathIntegrations } from "../user-settings/settings.routes";
3838
import { WorkspaceEntry } from "./WorkspaceEntry";
3939
import { AuthProviderType } from "@gitpod/public-api/lib/gitpod/v1/authprovider_pb";
40-
import { WorkspacePhase_Phase } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb";
40+
import { WorkspaceMetadata, WorkspacePhase_Phase } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb";
4141
import { Button } from "@podkit/buttons/Button";
4242
import { LoadingButton } from "@podkit/buttons/LoadingButton";
4343
import { CreateAndStartWorkspaceRequest } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb";
@@ -172,7 +172,14 @@ export function CreateWorkspacePage() {
172172
const [selectAccountError, setSelectAccountError] = useState<SelectAccountPayload | undefined>(undefined);
173173

174174
const createWorkspace = useCallback(
175-
async (options?: Omit<PartialMessage<CreateAndStartWorkspaceRequest>, "contextUrl" | "organizationId">) => {
175+
/**
176+
* options will omit
177+
* - source.url
178+
* - source.workspaceClass
179+
* - metadata.organizationId
180+
* - metadata.configurationId
181+
*/
182+
async (options?: PartialMessage<CreateAndStartWorkspaceRequest>) => {
176183
// add options from search params
177184
const opts = options || {};
178185

@@ -192,16 +199,6 @@ export function CreateWorkspacePage() {
192199
opts.forceDefaultConfig = true;
193200
}
194201

195-
if (!opts.workspaceClass) {
196-
opts.workspaceClass = selectedWsClass;
197-
}
198-
if (!opts.editor) {
199-
opts.editor = {
200-
name: selectedIde,
201-
version: useLatestIde ? "latest" : undefined,
202-
};
203-
}
204-
205202
try {
206203
if (createWorkspaceMutation.isStarting) {
207204
console.log("Skipping duplicate createWorkspace call.");
@@ -210,14 +207,28 @@ export function CreateWorkspacePage() {
210207
// we wait at least 5 secs
211208
const timeout = new Promise((resolve) => setTimeout(resolve, 5000));
212209

210+
if (!opts.metadata) {
211+
opts.metadata = new WorkspaceMetadata();
212+
}
213+
opts.metadata.organizationId = organizationId;
214+
opts.metadata.configurationId = selectedProjectID;
215+
213216
const result = await createWorkspaceMutation.createWorkspace({
214217
source: {
215218
case: "contextUrl",
216-
value: contextURL,
219+
value: {
220+
url: contextURL,
221+
workspaceClass: selectedWsClass,
222+
editor:
223+
opts.source?.case === "contextUrl" && opts.source.value.editor
224+
? opts.source.value.editor
225+
: {
226+
name: selectedIde,
227+
version: useLatestIde ? "latest" : undefined,
228+
},
229+
},
217230
},
218231
...opts,
219-
organizationId,
220-
configurationId: selectedProjectID,
221232
});
222233
await storeAutoStartOptions();
223234
await timeout;

components/dashboard/src/workspaces/DeleteWorkspaceModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export const DeleteWorkspaceModal: FunctionComponent<Props> = ({ workspace, onCl
3333
areYouSureText="Are you sure you want to delete this workspace?"
3434
children={{
3535
name: workspace.id,
36-
description: workspace.name,
36+
description: workspace.metadata?.name,
3737
}}
3838
buttonText="Delete Workspace"
3939
warningText={deleteWorkspace.isError ? "There was a problem deleting your workspace." : undefined}

components/dashboard/src/workspaces/RenameWorkspaceModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ type Props = {
1616
};
1717
export const RenameWorkspaceModal: FunctionComponent<Props> = ({ workspace, onClose }) => {
1818
const [errorMessage, setErrorMessage] = useState("");
19-
const [description, setDescription] = useState(workspace.name || "");
19+
const [description, setDescription] = useState(workspace.metadata?.name || "");
2020
const updateDescription = useUpdateWorkspaceDescriptionMutation();
2121

2222
const updateWorkspaceDescription = useCallback(async () => {

components/dashboard/src/workspaces/WorkspaceEntry.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ export const WorkspaceEntry: FunctionComponent<Props> = ({ info, shortVersion })
2727
const workspace = info;
2828
const currentBranch = gitStatus?.branch || "<unknown>";
2929
const project = getProjectPath(workspace);
30-
const normalizedContextUrl = workspace.contextUrl;
31-
const normalizedContextUrlDescription = workspace.contextUrl; // Instead of showing nothing, we prefer to show the raw content instead
30+
const normalizedContextUrl = workspace.metadata!.originalContextUrl;
31+
const normalizedContextUrlDescription = workspace.metadata!.originalContextUrl; // Instead of showing nothing, we prefer to show the raw content instead
3232

3333
const changeMenuState = (state: boolean) => {
3434
setMenuActive(state);
@@ -69,7 +69,7 @@ export const WorkspaceEntry: FunctionComponent<Props> = ({ info, shortVersion })
6969
<>
7070
<ItemField className="w-4/12 flex flex-col my-auto">
7171
<div className="text-gray-500 dark:text-gray-400 overflow-ellipsis truncate">
72-
{workspace.name}
72+
{workspace.metadata!.name}
7373
</div>
7474
<a href={normalizedContextUrl}>
7575
<div className="text-sm text-gray-400 dark:text-gray-500 overflow-ellipsis truncate hover:text-blue-600 dark:hover:text-blue-400">
@@ -105,5 +105,5 @@ export const WorkspaceEntry: FunctionComponent<Props> = ({ info, shortVersion })
105105

106106
export function getProjectPath(ws: Workspace) {
107107
// TODO: Remove and call papi ContextService
108-
return ws.contextUrl.replace("https://", "");
108+
return ws.metadata!.originalContextUrl.replace("https://", "");
109109
}

components/dashboard/src/workspaces/WorkspaceOverflowMenu.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,13 @@ export const WorkspaceEntryOverflowMenu: FunctionComponent<WorkspaceEntryOverflo
6060

6161
const toggleShared = useCallback(() => {
6262
const newLevel =
63-
workspace.status?.admission === AdmissionLevel.EVERYONE
64-
? AdmissionLevel.OWNER_ONLY
65-
: AdmissionLevel.EVERYONE;
63+
workspace.spec?.admission === AdmissionLevel.EVERYONE ? AdmissionLevel.OWNER_ONLY : AdmissionLevel.EVERYONE;
6664

6765
toggleWorkspaceShared.mutate({
6866
workspaceId: workspace.id,
6967
level: newLevel,
7068
});
71-
}, [toggleWorkspaceShared, workspace.id, workspace.status?.admission]);
69+
}, [toggleWorkspaceShared, workspace.id, workspace.spec?.admission]);
7270

7371
const togglePinned = useCallback(() => {
7472
toggleWorkspacePinned.mutate({
@@ -129,12 +127,12 @@ export const WorkspaceEntryOverflowMenu: FunctionComponent<WorkspaceEntryOverflo
129127
menuEntries.push(
130128
{
131129
title: "Share",
132-
active: workspace.status?.admission === AdmissionLevel.EVERYONE,
130+
active: workspace.spec?.admission === AdmissionLevel.EVERYONE,
133131
onClick: toggleShared,
134132
},
135133
{
136134
title: "Pin",
137-
active: !!workspace.pinned,
135+
active: !!workspace.metadata?.pinned,
138136
separator: true,
139137
onClick: togglePinned,
140138
},

components/dashboard/src/workspaces/Workspaces.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,18 @@ const WorkspacesPage: FunctionComponent = () => {
4747
const { filteredActiveWorkspaces, filteredInactiveWorkspaces } = useMemo(() => {
4848
const filteredActiveWorkspaces = activeWorkspaces.filter(
4949
(info) =>
50-
`${info.name}${info.id}${info.contextUrl}${info.status?.gitStatus?.cloneUrl}${info.status?.gitStatus?.branch}`
50+
`${info.metadata!.name}${info.id}${info.metadata!.originalContextUrl}${
51+
info.status?.gitStatus?.cloneUrl
52+
}${info.status?.gitStatus?.branch}`
5153
.toLowerCase()
5254
.indexOf(searchTerm.toLowerCase()) !== -1,
5355
);
5456

5557
const filteredInactiveWorkspaces = inactiveWorkspaces.filter(
5658
(info) =>
57-
`${info.name}${info.id}${info.contextUrl}${info.status?.gitStatus?.cloneUrl}${info.status?.gitStatus?.branch}`
59+
`${info.metadata!.name}${info.id}${info.metadata!.originalContextUrl}${
60+
info.status?.gitStatus?.cloneUrl
61+
}${info.status?.gitStatus?.branch}`
5862
.toLowerCase()
5963
.indexOf(searchTerm.toLowerCase()) !== -1,
6064
);
@@ -201,5 +205,5 @@ function isWorkspaceActive(info: Workspace): boolean {
201205
const twentyfourHoursAgo = hoursBefore(new Date().toISOString(), 24);
202206

203207
const isStopped = info.status?.phase?.name === WorkspacePhase_Phase.STOPPED;
204-
return info.pinned || !isStopped || isDateSmallerOrEqual(twentyfourHoursAgo, lastSessionStart);
208+
return info.metadata!.pinned || !isStopped || isDateSmallerOrEqual(twentyfourHoursAgo, lastSessionStart);
205209
}

components/public-api/gitpod/v1/envvar.proto

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,10 @@ message ResolveWorkspaceEnvironmentVariablesRequest {
142142
}
143143

144144
message ResolveWorkspaceEnvironmentVariablesResponse {
145-
message EnvironmentVariable {
146-
string name = 1;
147-
string value = 2;
148-
}
149145
repeated EnvironmentVariable environment_variables = 1;
150146
}
147+
148+
message EnvironmentVariable {
149+
string name = 1;
150+
string value = 2;
151+
}

0 commit comments

Comments
 (0)