Skip to content

Commit 145584c

Browse files
committed
[papi] add watch workspace status api
1 parent 4c35db7 commit 145584c

File tree

13 files changed

+752
-236
lines changed

13 files changed

+752
-236
lines changed

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

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
* See License.AGPL.txt in the project root for license information.
55
*/
66

7-
import { Code, ConnectError, PromiseClient } from "@connectrpc/connect";
7+
import { CallOptions, Code, ConnectError, PromiseClient } from "@connectrpc/connect";
88
import { PartialMessage } from "@bufbuild/protobuf";
99
import { WorkspaceService } from "@gitpod/public-api/lib/gitpod/v1/workspace_connect";
1010
import { GetWorkspaceRequest, GetWorkspaceResponse } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb";
1111
import { converter } from "./public-api";
1212
import { getGitpodService } from "./service";
13+
import { generateAsyncGenerator } from "@gitpod/gitpod-protocol/lib/generate-async-generator";
14+
import { WorkspaceInstance } from "@gitpod/gitpod-protocol";
1315

1416
export class JsonRpcWorkspaceClient implements PromiseClient<typeof WorkspaceService> {
1517
async getWorkspace(request: PartialMessage<GetWorkspaceRequest>): Promise<GetWorkspaceResponse> {
@@ -22,4 +24,47 @@ export class JsonRpcWorkspaceClient implements PromiseClient<typeof WorkspaceSer
2224
result.item = workspace;
2325
return result;
2426
}
27+
28+
async *watchWorkspaceStatus(
29+
request: PartialMessage<WatchWorkspaceStatusRequest>,
30+
options?: CallOptions,
31+
): AsyncIterable<WatchWorkspaceStatusResponse> {
32+
if (!options?.signal) {
33+
throw new ConnectError("signal is required", Code.InvalidArgument);
34+
}
35+
const it = generateAsyncGenerator<WorkspaceInstance>(
36+
(sink) => {
37+
const dispose = getGitpodService().registerClient({
38+
onInstanceUpdate: (instance) => {
39+
sink.next(instance);
40+
},
41+
});
42+
return dispose.dispose;
43+
},
44+
{ signal: options.signal },
45+
);
46+
if (request.workspaceId) {
47+
const resp = await this.getWorkspace({ id: request.workspaceId });
48+
if (resp.item?.status) {
49+
const response = new WatchWorkspaceStatusResponse();
50+
response.status = resp.item.status;
51+
yield response;
52+
}
53+
}
54+
for await (const item of it) {
55+
if (!item) {
56+
continue;
57+
}
58+
if (request.workspaceId && item.workspaceId !== request.workspaceId) {
59+
continue;
60+
}
61+
const status = converter.toWorkspace(item).status;
62+
if (!status) {
63+
continue;
64+
}
65+
const response = new WatchWorkspaceStatusResponse();
66+
response.status = status;
67+
yield response;
68+
}
69+
}
2570
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* Copyright (c) 2023 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License.AGPL.txt in the project root for license information.
5+
*/
6+
7+
type Sink<T> = {
8+
next: (value: T | void) => void;
9+
};
10+
11+
/**
12+
* Generates an asynchronous generator that yields values based on the provided setup function.
13+
*
14+
* the setup function that takes a sink and returns a cleanup function.
15+
* setup sink object has a `next` method that accepts a value to be pushed to the generator.
16+
*/
17+
export async function* generateAsyncGenerator<T>(
18+
setup: (sink: Sink<T>) => () => void,
19+
opts: { signal: AbortSignal },
20+
): AsyncGenerator<T | void, void, unknown> {
21+
const queue: T[] = [];
22+
23+
let resolveNext: ((value: T | void) => void) | null = null;
24+
25+
const sink: Sink<T> = {
26+
next: (value: T | void) => {
27+
if (resolveNext) {
28+
resolveNext(value);
29+
resolveNext = null;
30+
} else {
31+
if (value) {
32+
queue.push(value);
33+
}
34+
}
35+
},
36+
};
37+
38+
let isStopped = false;
39+
opts.signal.addEventListener("abort", () => {
40+
isStopped = true;
41+
sink.next();
42+
});
43+
44+
const cleanup = setup(sink);
45+
46+
try {
47+
while (!isStopped) {
48+
if (queue.length) {
49+
yield queue.shift();
50+
} else {
51+
yield new Promise<T | void>((resolve) => {
52+
resolveNext = resolve;
53+
});
54+
}
55+
}
56+
// ignore error since code in `try` scope will not throw an error
57+
// unless caller use it.throw, then it will throw to itself
58+
} finally {
59+
cleanup();
60+
}
61+
}

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,29 @@ service WorkspaceService {
1212
// +return NOT_FOUND User does not have access to a workspace with the given
1313
// ID +return NOT_FOUND Workspace does not exist
1414
rpc GetWorkspace(GetWorkspaceRequest) returns (GetWorkspaceResponse) {}
15+
16+
// WatchWorkspaceStatus watchs the workspaces status changes
17+
//
18+
// ID +return NOT_FOUND Workspace does not exist
19+
rpc WatchWorkspaceStatus(WatchWorkspaceStatusRequest) returns (stream WatchWorkspaceStatusResponse) {}
1520
}
1621

1722
message GetWorkspaceRequest { string id = 1; }
1823

1924
message GetWorkspaceResponse { Workspace item = 1; }
2025

26+
message WatchWorkspaceStatusRequest {
27+
// workspace_id specifies the workspace to watch
28+
//
29+
// +optional if empty then watch all workspaces
30+
optional string workspace_id = 1;
31+
}
32+
33+
message WatchWorkspaceStatusResponse {
34+
// item is the workspace that was changed
35+
WorkspaceStatus status = 1;
36+
}
37+
2138
// +resource get workspace
2239
message Workspace {
2340
string id = 1;

components/public-api/go/v1/v1connect/workspace.connect.go

Lines changed: 29 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)