Skip to content

Commit a852892

Browse files
committed
part of json rpc implement
1 parent 2dd931e commit a852892

File tree

4 files changed

+172
-5
lines changed

4 files changed

+172
-5
lines changed

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

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
StopWorkspaceResponse,
2727
UpdateWorkspaceRequest,
2828
UpdateWorkspaceResponse,
29+
AdmissionLevel,
2930
} from "@gitpod/public-api/lib/gitpod/v1/workspace_pb";
3031
import { converter } from "./public-api";
3132
import { getGitpodService } from "./service";
@@ -172,27 +173,83 @@ export class JsonRpcWorkspaceClient implements PromiseClient<typeof WorkspaceSer
172173
request: PartialMessage<StopWorkspaceRequest>,
173174
_options?: CallOptions | undefined,
174175
): Promise<StopWorkspaceResponse> {
175-
throw new ApplicationError(ErrorCodes.UNIMPLEMENTED, "not implemented");
176+
if (!request.workspaceId) {
177+
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "workspaceId is required");
178+
}
179+
await getGitpodService().server.stopWorkspace(request.workspaceId);
180+
const result = new StopWorkspaceResponse();
181+
return result;
176182
}
177183

178184
async deleteWorkspace(
179185
request: PartialMessage<DeleteWorkspaceRequest>,
180186
_options?: CallOptions | undefined,
181187
): Promise<DeleteWorkspaceResponse> {
182-
throw new ApplicationError(ErrorCodes.UNIMPLEMENTED, "not implemented");
188+
if (!request.workspaceId) {
189+
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "workspaceId is required");
190+
}
191+
await getGitpodService().server.deleteWorkspace(request.workspaceId);
192+
const result = new DeleteWorkspaceResponse();
193+
return result;
183194
}
184195

185196
async updateWorkspace(
186197
request: PartialMessage<UpdateWorkspaceRequest>,
187198
_options?: CallOptions | undefined,
188199
): Promise<UpdateWorkspaceResponse> {
189-
throw new ApplicationError(ErrorCodes.UNIMPLEMENTED, "not implemented");
200+
if (!request.workspaceId) {
201+
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "workspaceId is required");
202+
}
203+
const server = getGitpodService().server;
204+
// tasks to wait for promise all with type defined
205+
const tasks: Array<Promise<any>> = [];
206+
207+
if (request.name) {
208+
tasks.push(server.setWorkspaceDescription(request.workspaceId, request.name));
209+
}
210+
if (request.admission) {
211+
if (request.admission === AdmissionLevel.OWNER_ONLY) {
212+
tasks.push(server.controlAdmission(request.workspaceId, "owner"));
213+
} else if (request.admission === AdmissionLevel.EVERYONE) {
214+
tasks.push(server.controlAdmission(request.workspaceId, "everyone"));
215+
}
216+
}
217+
if (request.pinned !== undefined) {
218+
tasks.push(server.updateWorkspaceUserPin(request.workspaceId, request.pinned ? "pin" : "unpin"));
219+
}
220+
if (Number(request.timeout?.seconds ?? 0) > 0) {
221+
// convert request.timeout into format like 3h or 3d
222+
const timeout = converter.toDurationString(request.timeout!);
223+
tasks.push(server.setWorkspaceTimeout(request.workspaceId, timeout));
224+
}
225+
226+
if (request.gitStatus) {
227+
tasks.push(
228+
server.updateGitStatus(request.workspaceId, {
229+
branch: request.gitStatus.branch!,
230+
latestCommit: request.gitStatus.latestCommit!,
231+
uncommitedFiles: request.gitStatus.uncommitedFiles!,
232+
totalUncommitedFiles: request.gitStatus.totalUncommitedFiles!,
233+
untrackedFiles: request.gitStatus.untrackedFiles!,
234+
totalUntrackedFiles: request.gitStatus.totalUntrackedFiles!,
235+
unpushedCommits: request.gitStatus.unpushedCommits!,
236+
totalUnpushedCommits: request.gitStatus.totalUnpushedCommits!,
237+
}),
238+
);
239+
}
240+
// TODO: Use all or allSettled? Because it does will update only parts of required changes
241+
await Promise.all(tasks);
242+
const result = new UpdateWorkspaceResponse();
243+
const workspace = await this.getWorkspace({ workspaceId: request.workspaceId });
244+
result.workspace = workspace.workspace;
245+
return result;
190246
}
191247

192248
async listWorkspaceClasses(
193249
request: PartialMessage<ListWorkspaceClassesRequest>,
194250
_options?: CallOptions | undefined,
195251
): Promise<ListWorkspaceClassesResponse> {
252+
// const list = await getGitpodService().server.getSupportedWorkspaceClasses();
196253
throw new ApplicationError(ErrorCodes.UNIMPLEMENTED, "not implemented");
197254
}
198255
}

components/gitpod-protocol/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"lint": "yarn eslint src/*.ts src/**/*.ts",
4444
"lint:fix": "yarn eslint src/*.ts src/**/*.ts --fix",
4545
"test": "mocha './**/*.spec.js' --exclude './node_modules/**' --exit",
46+
"test2": "mocha './**/public-api-converter.spec.js' --exclude './node_modules/**' --exit",
4647
"test:leeway": "yarn build && yarn test",
4748
"test-debug": "mocha --inspect-brk './**/*.spec.js' --exclude './node_modules/**' --exit",
4849
"watch": "leeway exec --package .:lib --transitive-dependencies --filter-type yarn --components --parallel -- tsc -w --preserveWatchOutput"

components/gitpod-protocol/src/public-api-converter.spec.ts

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* See License.AGPL.txt in the project root for license information.
66
*/
77

8-
import { Timestamp, toPlainMessage } from "@bufbuild/protobuf";
8+
import { Timestamp, toPlainMessage, Duration } from "@bufbuild/protobuf";
99
import {
1010
AdmissionLevel,
1111
WorkspaceEnvironmentVariable,
@@ -1374,4 +1374,62 @@ describe("PublicAPIConverter", () => {
13741374
expect(result).to.deep.equal(userEnvVar);
13751375
});
13761376
});
1377+
1378+
describe("toDuration", () => {
1379+
it("should convert with 0", () => {
1380+
expect(converter.toDuration("").seconds).to.equal(BigInt(0));
1381+
expect(converter.toDuration(" ").seconds).to.equal(BigInt(0));
1382+
expect(converter.toDuration("0").seconds).to.equal(BigInt(0));
1383+
expect(converter.toDuration("0ms").seconds).to.equal(BigInt(0));
1384+
expect(converter.toDuration("0s").seconds).to.equal(BigInt(0));
1385+
expect(converter.toDuration("0m").seconds).to.equal(BigInt(0));
1386+
expect(converter.toDuration("0h").seconds).to.equal(BigInt(0));
1387+
});
1388+
it("should convert with hours", () => {
1389+
expect(converter.toDuration("1h").seconds).to.equal(BigInt(3600));
1390+
expect(converter.toDuration("24h").seconds).to.equal(BigInt(3600 * 24));
1391+
expect(converter.toDuration("25h").seconds).to.equal(BigInt(3600 * 25));
1392+
});
1393+
it("should convert with minutes", () => {
1394+
expect(converter.toDuration("1m").seconds).to.equal(BigInt(60));
1395+
expect(converter.toDuration("120m").seconds).to.equal(BigInt(120 * 60));
1396+
});
1397+
it("should convert with seconds", () => {
1398+
expect(converter.toDuration("1s").seconds).to.equal(BigInt(1));
1399+
expect(converter.toDuration("120s").seconds).to.equal(BigInt(120));
1400+
});
1401+
it("should convert with mixed", () => {
1402+
expect(converter.toDuration("1h20m").seconds).to.equal(BigInt(3600 + 20 * 60));
1403+
expect(converter.toDuration("1h0m0s").seconds).to.equal(BigInt(3600));
1404+
expect(converter.toDuration("20m1h").seconds).to.equal(BigInt(3600 + 20 * 60));
1405+
expect(converter.toDuration("20m25h1s").seconds).to.equal(BigInt(25 * 3600 + 20 * 60 + 1));
1406+
});
1407+
});
1408+
1409+
describe("toDurationString", () => {
1410+
it("should convert with 0", () => {
1411+
expect(converter.toDurationString(new Duration())).to.equal("0");
1412+
expect(converter.toDurationString(new Duration({ seconds: BigInt(0) }))).to.equal("0");
1413+
expect(converter.toDurationString(new Duration({ nanos: 0 }))).to.equal("0");
1414+
});
1415+
it("should convert with hours", () => {
1416+
expect(converter.toDurationString(new Duration({ seconds: BigInt(3600) }))).to.equal("1h");
1417+
expect(converter.toDurationString(new Duration({ seconds: BigInt(3600 * 24) }))).to.equal("24h");
1418+
expect(converter.toDurationString(new Duration({ seconds: BigInt(3600 * 25) }))).to.equal("25h");
1419+
});
1420+
it("should convert with minutes", () => {
1421+
expect(converter.toDurationString(new Duration({ seconds: BigInt(60) }))).to.equal("1m");
1422+
expect(converter.toDurationString(new Duration({ seconds: BigInt(2 * 60 * 60) }))).to.equal("2h");
1423+
});
1424+
it("should convert with seconds", () => {
1425+
expect(converter.toDurationString(new Duration({ seconds: BigInt(1) }))).to.equal("1s");
1426+
expect(converter.toDurationString(new Duration({ seconds: BigInt(120) }))).to.equal("2m");
1427+
});
1428+
it("should convert with mixed", () => {
1429+
expect(converter.toDurationString(new Duration({ seconds: BigInt(3600 + 20 * 60) }))).to.equal("1h20m");
1430+
expect(converter.toDurationString(new Duration({ seconds: BigInt(25 * 3600 + 20 * 60 + 1) }))).to.equal(
1431+
"25h20m1s",
1432+
);
1433+
});
1434+
});
13771435
});

components/gitpod-protocol/src/public-api-converter.ts

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

7-
import { Timestamp, toPlainMessage } from "@bufbuild/protobuf";
7+
import { Timestamp, toPlainMessage, PartialMessage, Duration } from "@bufbuild/protobuf";
88
import { Code, ConnectError } from "@connectrpc/connect";
99
import {
1010
FailedPreconditionDetails,
@@ -910,4 +910,55 @@ export class PublicAPIConverter {
910910
result.lastUsedTime = Timestamp.fromDate(new Date(sshKey.lastUsedTime || sshKey.creationTime));
911911
return result;
912912
}
913+
914+
/**
915+
* Converts a duration to a string like "1h2m3s4ms"
916+
*
917+
* `Duration.nanos` is ignored
918+
* @returns a string like "1h2m3s", valid time units are `s`, `m`, `h`
919+
*/
920+
toDurationString(duration: PartialMessage<Duration>): string {
921+
const seconds = duration.seconds || 0;
922+
if (seconds === 0) {
923+
return "0";
924+
}
925+
const totalMilliseconds = Number(seconds) * 1000;
926+
927+
const hours = Math.floor(totalMilliseconds / 3600000);
928+
const remainingMillisecondsAfterHours = totalMilliseconds % 3600000;
929+
const minutes = Math.floor(remainingMillisecondsAfterHours / 60000);
930+
const remainingMillisecondsAfterMinutes = remainingMillisecondsAfterHours % 60000;
931+
const secondsResult = Math.floor(remainingMillisecondsAfterMinutes / 1000);
932+
933+
return `${hours > 0 ? hours + "h" : ""}${minutes > 0 ? minutes + "m" : ""}${
934+
secondsResult > 0 ? secondsResult + "s" : ""
935+
}`;
936+
}
937+
938+
/**
939+
* Converts a duration string like "1h2m3s" to a Duration
940+
*
941+
* @param durationString "1h2m3s" valid time units are `s`, `m`, `h`
942+
*/
943+
toDuration(durationString: string): Duration {
944+
const units = new Map([
945+
["h", 3600],
946+
["m", 60],
947+
["s", 1],
948+
]);
949+
const regex = /(\d+(?:\.\d+)?)([hmsµµs]+)/g;
950+
let totalSeconds = 0;
951+
let match: RegExpExecArray | null;
952+
953+
while ((match = regex.exec(durationString)) !== null) {
954+
const value = parseFloat(match[1]);
955+
const unit = match[2];
956+
totalSeconds += value * (units.get(unit) || 0);
957+
}
958+
959+
return new Duration({
960+
seconds: BigInt(Math.floor(totalSeconds)),
961+
nanos: (totalSeconds - Math.floor(totalSeconds)) * 1e9,
962+
});
963+
}
913964
}

0 commit comments

Comments
 (0)