Skip to content

Commit 8c84609

Browse files
committed
[projects] Add Prebuild Events
1 parent d8720f9 commit 8c84609

File tree

15 files changed

+312
-32
lines changed

15 files changed

+312
-32
lines changed

components/dashboard/src/projects/Prebuilds.tsx

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ export default function () {
2626
const match = useRouteMatch<{ team: string, resource: string }>("/:team/:resource");
2727
const projectName = match?.params?.resource;
2828

29-
// @ts-ignore
3029
const [project, setProject] = useState<Project | undefined>();
3130
const [defaultBranch, setDefaultBranch] = useState<string | undefined>();
3231

@@ -35,6 +34,33 @@ export default function () {
3534

3635
const [prebuilds, setPrebuilds] = useState<PrebuildInfo[]>([]);
3736

37+
useEffect(() => {
38+
if (!project) {
39+
return;
40+
}
41+
const registration = getGitpodService().registerClient({
42+
onPrebuildUpdate: (update) => {
43+
if (!project) {
44+
return;
45+
}
46+
if (update.prebuildInfo.projectId === project.id) {
47+
// TODO(at) create a proper model to update the state
48+
setPrebuilds((prev) => {
49+
const existingPrebuild = prev.find(p => p.id === update.prebuildInfo.id);
50+
if (existingPrebuild) {
51+
existingPrebuild.status = update.status;
52+
return [...prev];
53+
}
54+
return [update.prebuildInfo, ...prev];
55+
})
56+
}
57+
}
58+
})
59+
return () => {
60+
registration.dispose();
61+
}
62+
}, [ project ]);
63+
3864
useEffect(() => {
3965
if (!teams) {
4066
return;
@@ -44,21 +70,23 @@ export default function () {
4470
? await getGitpodService().server.getTeamProjects(team.id)
4571
: await getGitpodService().server.getUserProjects());
4672

47-
const project = projectName && projects.find(p => p.name === projectName);
48-
if (project) {
49-
setProject(project);
73+
const newProject = projectName && projects.find(p => p.name === projectName);
74+
if (newProject) {
75+
setProject(newProject);
5076

51-
const prebuilds = await getGitpodService().server.findPrebuilds({ projectId: project.id });
77+
const prebuilds = await getGitpodService().server.findPrebuilds({ projectId: newProject.id });
5278
setPrebuilds(prebuilds);
5379

54-
const details = await getGitpodService().server.getProjectOverview(project.id);
80+
const details = await getGitpodService().server.getProjectOverview(newProject.id);
5581
if (details?.branches) {
5682
setDefaultBranch(details.branches.find(b => b.isDefault)?.name);
5783
}
5884
}
5985
})();
6086
}, [ teams ]);
6187

88+
89+
6290
const prebuildContextMenu = (p: PrebuildInfo) => {
6391
const running = p.status === "building";
6492
const entries: ContextMenuEntry[] = [];
@@ -104,8 +132,6 @@ export default function () {
104132
return true;
105133
}
106134

107-
const filteredPrebuilds = prebuilds.filter(filter);
108-
109135
const openPrebuild = (pb: PrebuildInfo) => {
110136
history.push(`/${!!team ? team.slug : 'projects'}/${projectName}/${pb.id}`);
111137
}
@@ -149,7 +175,7 @@ export default function () {
149175
<ItemFieldContextMenu />
150176
</ItemField>
151177
</Item>
152-
{filteredPrebuilds.map((p: PrebuildInfo) => <Item className="grid grid-cols-3">
178+
{prebuilds.filter(filter).map((p: PrebuildInfo) => <Item key={`prebuild-${p.id}`} className="grid grid-cols-3">
153179
<ItemField className="flex items-center">
154180
<div className="cursor-pointer" onClick={() => openPrebuild(p)}>
155181
<div className="text-base text-gray-900 dark:text-gray-50 font-medium uppercase mb-1">
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* Copyright (c) 2021 Gitpod GmbH. All rights reserved.
3+
* Licensed under the Gitpod Enterprise Source Code License,
4+
* See License.enterprise.txt in the project root folder.
5+
*/
6+
7+
import { Entity, Column, PrimaryColumn } from "typeorm";
8+
import { PrebuildInfo } from "@gitpod/gitpod-protocol";
9+
10+
import { TypeORM } from "../../typeorm/typeorm";
11+
12+
@Entity()
13+
export class DBPrebuildInfo {
14+
15+
@PrimaryColumn(TypeORM.UUID_COLUMN_TYPE)
16+
prebuildId: string;
17+
18+
@Column({
19+
type: 'simple-json',
20+
transformer: (() => {
21+
return {
22+
to(value: any): any {
23+
return JSON.stringify(value);
24+
},
25+
from(value: any): any {
26+
try {
27+
const obj = JSON.parse(value);
28+
return PrebuildInfo.is(obj) ? obj : undefined;
29+
} catch (error) {
30+
}
31+
}
32+
};
33+
})()
34+
})
35+
info: PrebuildInfo;
36+
37+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Copyright (c) 2021 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+
import {MigrationInterface, QueryRunner} from "typeorm";
8+
9+
export class AddPrebuildInfo1628160315471 implements MigrationInterface {
10+
11+
public async up(queryRunner: QueryRunner): Promise<any> {
12+
await queryRunner.query("CREATE TABLE IF NOT EXISTS `d_b_prebuild_info` ( `prebuildId` char(36) NOT NULL, `info` text NOT NULL, `deleted` tinyint(4) NOT NULL DEFAULT '0', `_lastModified` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), PRIMARY KEY (`prebuildId`), KEY `ind_dbsync` (`_lastModified`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
13+
}
14+
15+
public async down(queryRunner: QueryRunner): Promise<any> {
16+
}
17+
18+
}

components/gitpod-db/src/typeorm/workspace-db-impl.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import { injectable, inject } from "inversify";
88
import { Repository, EntityManager, DeepPartial, UpdateQueryBuilder } from "typeorm";
99
import { MaybeWorkspace, MaybeWorkspaceInstance, WorkspaceDB, FindWorkspacesOptions, PrebuiltUpdatableAndWorkspace, WorkspaceInstanceSessionWithWorkspace, PrebuildWithWorkspace, WorkspaceAndOwner, WorkspacePortsAuthData, WorkspaceOwnerAndSoftDeleted } from "../workspace-db";
10-
import { Workspace, WorkspaceInstance, WorkspaceInfo, WorkspaceInstanceUser, WhitelistedRepository, Snapshot, LayoutData, PrebuiltWorkspace, RunningWorkspaceInfo, PrebuiltWorkspaceUpdatable, WorkspaceAndInstance, WorkspaceType } from "@gitpod/gitpod-protocol";
10+
import { Workspace, WorkspaceInstance, WorkspaceInfo, WorkspaceInstanceUser, WhitelistedRepository, Snapshot, LayoutData, PrebuiltWorkspace, RunningWorkspaceInfo, PrebuiltWorkspaceUpdatable, WorkspaceAndInstance, WorkspaceType, PrebuildInfo } from "@gitpod/gitpod-protocol";
1111
import { TypeORM } from "./typeorm";
1212
import { DBWorkspace } from "./entity/db-workspace";
1313
import { DBWorkspaceInstance } from "./entity/db-workspace-instance";
@@ -19,6 +19,7 @@ import { log } from '@gitpod/gitpod-protocol/lib/util/logging';
1919
import { DBPrebuiltWorkspace } from "./entity/db-prebuilt-workspace";
2020
import { DBPrebuiltWorkspaceUpdatable } from "./entity/db-prebuilt-workspace-updatable";
2121
import { BUILTIN_WORKSPACE_PROBE_USER_NAME } from "../user-db";
22+
import { DBPrebuildInfo } from "./entity/db-prebuild-info-entry";
2223

2324
type RawTo<T> = (instance: WorkspaceInstance, ws: Workspace) => T;
2425
interface OrderBy {
@@ -55,6 +56,10 @@ export abstract class AbstractTypeORMWorkspaceDBImpl implements WorkspaceDB {
5556
return await (await this.getManager()).getRepository<DBPrebuiltWorkspace>(DBPrebuiltWorkspace);
5657
}
5758

59+
protected async getPrebuildInfoRepo(): Promise<Repository<DBPrebuildInfo>> {
60+
return await (await this.getManager()).getRepository<DBPrebuildInfo>(DBPrebuildInfo);
61+
}
62+
5863
protected async getPrebuiltWorkspaceUpdatableRepo(): Promise<Repository<DBPrebuiltWorkspaceUpdatable>> {
5964
return await (await this.getManager()).getRepository<DBPrebuiltWorkspaceUpdatable>(DBPrebuiltWorkspaceUpdatable);
6065
}
@@ -830,7 +835,7 @@ export abstract class AbstractTypeORMWorkspaceDBImpl implements WorkspaceDB {
830835
const repo = await this.getPrebuiltWorkspaceRepo();
831836

832837
const query = repo.createQueryBuilder('pws')
833-
.orderBy('pws.creationTime', 'ASC')
838+
.orderBy('pws.creationTime', 'DESC')
834839
.innerJoinAndMapOne('pws.workspace', DBWorkspace, 'ws', 'pws.buildWorkspaceId = ws.id')
835840
.andWhere('pws.projectId = :projectId', { projectId });
836841

@@ -849,13 +854,31 @@ export abstract class AbstractTypeORMWorkspaceDBImpl implements WorkspaceDB {
849854
const repo = await this.getPrebuiltWorkspaceRepo();
850855

851856
const query = repo.createQueryBuilder('pws')
852-
.orderBy('pws.creationTime', 'ASC')
857+
.orderBy('pws.creationTime', 'DESC')
853858
.innerJoinAndMapOne('pws.workspace', DBWorkspace, 'ws', 'pws.buildWorkspaceId = ws.id')
854859
.andWhere('pws.id = :id', { id });
855860

856861
return query.getOne();
857862
}
858863

864+
async storePrebuildInfo(prebuildInfo: PrebuildInfo): Promise<void> {
865+
const repo = await this.getPrebuildInfoRepo();
866+
await repo.save({
867+
prebuildId: prebuildInfo.id,
868+
info: prebuildInfo
869+
});
870+
}
871+
872+
async findPrebuildInfo(prebuildId: string): Promise<PrebuildInfo | undefined>{
873+
const repo = await this.getPrebuildInfoRepo();
874+
875+
const query = repo.createQueryBuilder('pi')
876+
.andWhere('pi.prebuildId = :prebuildId', { prebuildId });
877+
878+
const entry = await query.getOne();
879+
return entry?.info;
880+
}
881+
859882
}
860883

861884
@injectable()

components/gitpod-db/src/workspace-db.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import { DeepPartial } from 'typeorm';
88

9-
import { Workspace, WorkspaceInfo, WorkspaceInstance, WorkspaceInstanceUser, WhitelistedRepository, Snapshot, LayoutData, PrebuiltWorkspace, PrebuiltWorkspaceUpdatable, RunningWorkspaceInfo, WorkspaceAndInstance, WorkspaceType } from '@gitpod/gitpod-protocol';
9+
import { Workspace, WorkspaceInfo, WorkspaceInstance, WorkspaceInstanceUser, WhitelistedRepository, Snapshot, LayoutData, PrebuiltWorkspace, PrebuiltWorkspaceUpdatable, RunningWorkspaceInfo, WorkspaceAndInstance, WorkspaceType, PrebuildInfo } from '@gitpod/gitpod-protocol';
1010

1111
export type MaybeWorkspace = Workspace | undefined;
1212
export type MaybeWorkspaceInstance = WorkspaceInstance | undefined;
@@ -115,4 +115,7 @@ export interface WorkspaceDB {
115115

116116
findPrebuiltWorkspacesByProject(projectId: string, branch?: string, limit?: number): Promise<PrebuiltWorkspace[]>;
117117
findPrebuiltWorkspacesById(prebuildId: string): Promise<PrebuiltWorkspace | undefined>;
118+
119+
storePrebuildInfo(prebuildInfo: PrebuildInfo): Promise<void>;
120+
findPrebuildInfo(prebuildId: string): Promise<PrebuildInfo | undefined>;
118121
}

components/gitpod-protocol/src/gitpod-service.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
} from './protocol';
1414
import {
1515
Team, TeamMemberInfo,
16-
TeamMembershipInvite, Project, PrebuildInfo, TeamMemberRole
16+
TeamMembershipInvite, Project, PrebuildInfo, TeamMemberRole, PrebuildUpdate
1717
} from './teams-projects-protocol';
1818
import { JsonRpcProxy, JsonRpcServer } from './messaging/proxy-factory';
1919
import { Disposable, CancellationTokenSource } from 'vscode-jsonrpc';
@@ -34,6 +34,8 @@ export interface GitpodClient {
3434
onInstanceUpdate(instance: WorkspaceInstance): void;
3535
onWorkspaceImageBuildLogs: WorkspaceImageBuild.LogCallback;
3636

37+
onPrebuildUpdate(update: PrebuildUpdate): void;
38+
3739
onCreditAlert(creditAlert: CreditAlert): void;
3840

3941
//#region propagating reconnection to iframe
@@ -376,6 +378,18 @@ export class GitpodCompositeClient<Client extends GitpodClient> implements Gitpo
376378
}
377379
}
378380

381+
onPrebuildUpdate(update: PrebuildUpdate): void {
382+
for (const client of this.clients) {
383+
if (client.onPrebuildUpdate) {
384+
try {
385+
client.onPrebuildUpdate(update);
386+
} catch (error) {
387+
console.error(error)
388+
}
389+
}
390+
}
391+
}
392+
379393
onWorkspaceImageBuildLogs(info: WorkspaceImageBuild.StateInfo, content: WorkspaceImageBuild.LogContent | undefined): void {
380394
for (const client of this.clients) {
381395
if (client.onWorkspaceImageBuildLogs) {

components/gitpod-protocol/src/teams-projects-protocol.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,24 @@ export namespace Project {
5454
}
5555
}
5656

57+
export interface PrebuildUpdate {
58+
prebuildInfo: PrebuildInfo;
59+
status: PrebuiltWorkspaceState;
60+
}
61+
5762
export interface PrebuildInfo {
5863
id: string;
59-
teamId: string;
64+
buildWorkspaceId: string;
65+
66+
teamId?: string;
67+
userId?: string;
68+
69+
projectId: string;
6070
projectName: string;
71+
6172
cloneUrl: string;
6273
branch: string;
6374
branchPrebuildNumber: string;
64-
buildWorkspaceId: string;
6575

6676
startedAt: string;
6777
startedBy: string;
@@ -75,6 +85,11 @@ export interface PrebuildInfo {
7585
changeUrl?: string;
7686
changeHash: string;
7787
}
88+
export namespace PrebuildInfo {
89+
export function is(data?: any): data is PrebuildInfo {
90+
return typeof data === "object" && ["id", "buildWorkspaceId", "projectId", "branch"].every(p => p in data);
91+
}
92+
}
7893

7994
export interface Team {
8095
id: string;

components/server/ee/src/workspace/gitpod-server-impl.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import { injectable, inject } from "inversify";
88
import { GitpodServerImpl } from "../../../src/workspace/gitpod-server-impl";
99
import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing";
10-
import { GitpodServer, GitpodClient, AdminGetListRequest, User, AdminGetListResult, Permission, AdminBlockUserRequest, AdminModifyRoleOrPermissionRequest, RoleOrPermission, AdminModifyPermanentWorkspaceFeatureFlagRequest, UserFeatureSettings, AdminGetWorkspacesRequest, WorkspaceAndInstance, GetWorkspaceTimeoutResult, WorkspaceTimeoutDuration, WorkspaceTimeoutValues, SetWorkspaceTimeoutResult, WorkspaceContext, CreateWorkspaceMode, WorkspaceCreationResult, PrebuiltWorkspaceContext, CommitContext, PrebuiltWorkspace, PermissionName, WorkspaceInstance, EduEmailDomain, ProviderRepository, Queue } from "@gitpod/gitpod-protocol";
10+
import { GitpodServer, GitpodClient, AdminGetListRequest, User, AdminGetListResult, Permission, AdminBlockUserRequest, AdminModifyRoleOrPermissionRequest, RoleOrPermission, AdminModifyPermanentWorkspaceFeatureFlagRequest, UserFeatureSettings, AdminGetWorkspacesRequest, WorkspaceAndInstance, GetWorkspaceTimeoutResult, WorkspaceTimeoutDuration, WorkspaceTimeoutValues, SetWorkspaceTimeoutResult, WorkspaceContext, CreateWorkspaceMode, WorkspaceCreationResult, PrebuiltWorkspaceContext, CommitContext, PrebuiltWorkspace, PermissionName, WorkspaceInstance, EduEmailDomain, ProviderRepository, Queue, PrebuildUpdate } from "@gitpod/gitpod-protocol";
1111
import { ResponseError } from "vscode-jsonrpc";
1212
import { TakeSnapshotRequest, AdmissionLevel, ControlAdmissionRequest, StopWorkspacePolicy, DescribeWorkspaceRequest, SetTimeoutRequest } from "@gitpod/ws-manager/lib";
1313
import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
@@ -76,6 +76,37 @@ export class GitpodServerEEImpl extends GitpodServerImpl<GitpodClient, GitpodSer
7676
initialize(client: GitpodClient | undefined, clientRegion: string | undefined, user: User, accessGuard: ResourceAccessGuard): void {
7777
super.initialize(client, clientRegion, user, accessGuard);
7878
this.listenToCreditAlerts();
79+
this.listenForPrebuildUpdates();
80+
}
81+
82+
protected async listenForPrebuildUpdates() {
83+
// 'registering for prebuild updates for all projects this user has access to
84+
const projects = await this.getAccessiibleProjects();
85+
for (const projectId of projects) {
86+
this.disposables.push(this.messageBusIntegration.listenForPrebuildUpdates(
87+
(ctx: TraceContext, update: PrebuildUpdate) => {
88+
this.client?.onPrebuildUpdate(update);
89+
},
90+
projectId
91+
));
92+
}
93+
94+
// TODO(at) we need to keep the list of accessible project up to date
95+
}
96+
97+
protected async getAccessiibleProjects() {
98+
if (!this.user) {
99+
return [];
100+
}
101+
102+
// update all project this user has access to
103+
const allProjects: string[] = [];
104+
const teams = await this.teamDB.findTeamsByUser(this.user.id);
105+
for (const team of teams) {
106+
allProjects.push(...(await this.projectsService.getTeamProjects(team.id)).map(p => p.id));
107+
}
108+
allProjects.push(...(await this.projectsService.getUserProjects(this.user.id)).map(p => p.id));
109+
return allProjects;
79110
}
80111

81112
/**

0 commit comments

Comments
 (0)