viewAllPrebuilds(p)}>
+ {lastPrebuilds.get(p.id)
? (
diff --git a/components/dashboard/src/projects/render-utils.tsx b/components/dashboard/src/projects/render-utils.tsx
new file mode 100644
index 00000000000000..b1768339031fca
--- /dev/null
+++ b/components/dashboard/src/projects/render-utils.tsx
@@ -0,0 +1,15 @@
+/**
+ * Copyright (c) 2021 Gitpod GmbH. All rights reserved.
+ * Licensed under the GNU Affero General Public License (AGPL).
+ * See License-AGPL.txt in the project root for license information.
+ */
+
+
+export function toRemoteURL(cloneURL: string) {
+ return cloneURL.replace("https://", "").replace(".git", "");
+}
+
+export function shortCommitMessage(message: string) {
+ const firstLine = message.split("/n")[0];
+ return firstLine.length > 50 ? firstLine.substring(0, 45) + " …" : firstLine;
+}
diff --git a/components/dashboard/src/service/service-mock.ts b/components/dashboard/src/service/service-mock.ts
index 998c5a9aaa689d..30c5a27186a09e 100644
--- a/components/dashboard/src/service/service-mock.ts
+++ b/components/dashboard/src/service/service-mock.ts
@@ -4,29 +4,151 @@
* See License-AGPL.txt in the project root for license information.
*/
-import { createServiceMock, Event } from "@gitpod/gitpod-protocol";
+import { createServiceMock, Event, Project, Team, User } from "@gitpod/gitpod-protocol";
+const u1: User = {
+ "id": "1234",
+ "creationDate": "2018-05-01T07:00:00.000Z",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/10137?v=4",
+ "name": "gp-test",
+ "fullName": "Alex",
+ "allowsMarketingCommunication": true,
+ "identities": [
+ {
+ "authProviderId": "Public-GitHub",
+ "authId": "1234",
+ "authName": "GitpodTester",
+ "primaryEmail": "tester@gitpod.io",
+ }
+ ],
+ rolesOrPermissions: ["teams-and-projects"],
+ additionalData: {
+ whatsNewSeen: {
+ "April-2021": "true",
+ "June-2021": "true",
+ }
+ }
+}
+const t1 = new Date(Date.now() - 123533).toISOString();
+const team1: Team = {
+ id: "team1",
+ name: "ACME",
+ slug: "ACME",
+ creationTime: t1
+}
+const pr1: Project = {
+ appInstallationId: "app1",
+ cloneUrl: "https://github.com/AlexTugarev/txt.git",
+ creationTime: t1,
+ id: "pr1",
+ name: "TXT",
+ teamId: "team1",
+}
const gitpodServiceMock = createServiceMock({
getLoggedInUser: async () => {
+ return u1;
+ },
+ updateLoggedInUser: async (user: User) => {
+ for (const attribute in user) {
+ // @ts-ignore
+ u1[attribute] = user[attribute];
+ }
+ return u1;
+ },
+ getTeams: async () => {
+ return [team1]
+ },
+ getTeamMembers: async (teamId) => {
+ return [{
+ memberSince: t1,
+ role: "owner",
+ userId: u1.id,
+ avatarUrl: u1.avatarUrl,
+ fullName: u1.fullName,
+ primaryEmail: "alex@gitpod.io",
+ }]
+ },
+ getGenericInvite: async () => {
return {
- "id": "1234",
- "creationDate": "2018-05-01T07:00:00.000Z",
- "avatarUrl": "https://avatars.githubusercontent.com/u/37021919?s=60&v=4",
- "name": "gp-test",
- "fullName": "Gitpod Tester",
- "allowsMarketingCommunication": true,
- "identities": [
- {
- "authProviderId": "Public-GitHub",
- "authId": "1234",
- "authName": "GitpodTester",
- "primaryEmail": "tester@gitpod.io",
- }
- ]
+ id: "000",
+ creationTime: t1,
+ invalidationTime: t1,
+ role: "member",
+ teamId: "team1",
}
},
+ getProjects: async () => {
+ return [pr1]
+ },
+ getProjectOverview: async () => {
+ return {
+ branches: [{
+ name: "main",
+ url: "branchUrl",
+ changeDate: t1,
+ changeAuthor: u1.fullName!,
+ changeAuthorAvatar: u1.avatarUrl,
+ changeHash: "2C0FFE",
+ changeTitle: "[Comp] Add new functionality for",
+ isDefault: true,
+ status: "available",
+ }]
+ }
+ },
+ findPrebuilds: async (p) => {
+ const { projectName, teamId } = p;
+ return [{
+ id: "pb1",
+ branch: "main",
+ buildWorkspaceId: "123",
+ branchPrebuildNumber: "123342",
+ projectName,
+ teamId,
+ cloneUrl: pr1.cloneUrl,
+ startedAt: t1,
+ startedBy: u1.id,
+ startedByAvatar: u1.avatarUrl,
+ status: "available",
+ changeTitle: "[Comp] Add new functionality for",
+ changeDate: t1,
+ changeAuthor: u1.fullName!,
+ changeAuthorAvatar: u1.avatarUrl,
+ changePR: "4647",
+ changeUrl: "https://github.com/gitpod-io/gitpod/pull/4738",
+ changeHash: "2C0FFE"
+ }, {
+ id: "pb1",
+ branch: "foo/bar",
+ buildWorkspaceId: "1234",
+ branchPrebuildNumber: "3",
+ projectName,
+ teamId,
+ cloneUrl: pr1.cloneUrl,
+ startedAt: t1,
+ startedBy: u1.id,
+ startedByAvatar: u1.avatarUrl,
+ status: "aborted",
+ changeTitle: "Fix Bug Nr 1",
+ changeDate: t1,
+ changeAuthor: u1.fullName!,
+ changeAuthorAvatar: u1.avatarUrl,
+ changePR: "4245",
+ changeUrl: "https://github.com/gitpod-io/gitpod/pull/4738",
+ changeHash: "1C0FFE"
+ }
+ ]
+ },
+ getProviderRepositoriesForUser: async () => {
+ return []
+ },
+ getWorkspaces: async () => {
+ return []
+ },
+ getFeaturedRepositories: async () => {
+ return []
+ },
getAuthProviders: async () => {
return [{
"authProviderId": "Public-GitHub",
@@ -55,15 +177,15 @@ const gitpodServiceMock = createServiceMock({
"host": "testing.doptig.com/gitlab",
"type": "GitLab",
"oauth": {
- "authorizationUrl": "https://testing.doptig.com/gitlab/oauth/authorize",
- "tokenUrl": "https://testing.doptig.com/gitlab/oauth/token",
- "settingsUrl": "https://testing.doptig.com/gitlab/-/profile/applications",
- "callBackUrl": "https://gitpod-staging.com/auth/testing.doptig.com/gitlab/callback",
- "clientId": "clientid-123",
- "clientSecret": "redacted"
+ "authorizationUrl": "https://testing.doptig.com/gitlab/oauth/authorize",
+ "tokenUrl": "https://testing.doptig.com/gitlab/oauth/token",
+ "settingsUrl": "https://testing.doptig.com/gitlab/-/profile/applications",
+ "callBackUrl": "https://gitpod-staging.com/auth/testing.doptig.com/gitlab/callback",
+ "clientId": "clientid-123",
+ "clientSecret": "redacted"
},
"deleted": false
- }]
+ }]
},
onDidOpenConnection: Event.None,
onDidCloseConnection: Event.None,
diff --git a/components/dashboard/src/service/service.tsx b/components/dashboard/src/service/service.tsx
index bf33a1dbb90d94..1fc1584b913456 100644
--- a/components/dashboard/src/service/service.tsx
+++ b/components/dashboard/src/service/service.tsx
@@ -10,9 +10,11 @@ import { createWindowMessageConnection } from '@gitpod/gitpod-protocol/lib/messa
import { JsonRpcProxyFactory } from '@gitpod/gitpod-protocol/lib/messaging/proxy-factory';
import { GitpodHostUrl } from '@gitpod/gitpod-protocol/lib/util/gitpod-host-url';
import { log } from '@gitpod/gitpod-protocol/lib/util/logging';
+import { gitpodServiceMock } from './service-mock';
export const gitpodHostUrl = new GitpodHostUrl(window.location.toString());
+// @ts-ignore
function createGitpodService
() {
if (window.top !== window.self && process.env.NODE_ENV === 'production') {
const connection = createWindowMessageConnection('gitpodServer', window.parent, '*');
@@ -51,7 +53,14 @@ function createGitpodService() {
function getGitpodService(): GitpodService {
const w = window as any;
const _gp = w._gp || (w._gp = {});
- const service = _gp.gitpodService || (_gp.gitpodService = createGitpodService());
+ const createService = () => {
+ if (window.location.search.includes("mock")) {
+ return gitpodServiceMock;
+ } else {
+ return createGitpodService();
+ }
+ }
+ const service = _gp.gitpodService || (_gp.gitpodService = createService());
return service;
}
diff --git a/components/dashboard/src/start/CreateWorkspace.tsx b/components/dashboard/src/start/CreateWorkspace.tsx
index e05c83c04735bf..9928813fe81764 100644
--- a/components/dashboard/src/start/CreateWorkspace.tsx
+++ b/components/dashboard/src/start/CreateWorkspace.tsx
@@ -247,7 +247,6 @@ function RepositoryNotFoundView(p: { error: StartWorkspaceError }) {
const [statusMessage, setStatusMessage] = useState();
useEffect(() => {
(async () => {
- const service = getGitpodService();
const { host, owner, repoName, userIsOwner, userScopes, lastUpdate } = p.error.data;
console.log('host', host);
console.log('owner', owner);
@@ -256,12 +255,12 @@ function RepositoryNotFoundView(p: { error: StartWorkspaceError }) {
console.log('userScopes', userScopes);
console.log('lastUpdate', lastUpdate);
- if ((await service.server.mayAccessPrivateRepo()) === false) {
+ if ((await getGitpodService().server.mayAccessPrivateRepo()) === false) {
setStatusMessage();
return;
}
- const authProvider = (await service.server.getAuthProviders()).find(p => p.host === host);
+ const authProvider = (await getGitpodService().server.getAuthProviders()).find(p => p.host === host);
if (!authProvider) {
return;
}
@@ -334,7 +333,6 @@ interface RunningPrebuildViewProps {
function RunningPrebuildView(props: RunningPrebuildViewProps) {
const logsEmitter = new EventEmitter();
- const service = getGitpodService();
let pollTimeout: NodeJS.Timeout | undefined;
let prebuildDoneTriggered: boolean = false;
@@ -345,7 +343,7 @@ function RunningPrebuildView(props: RunningPrebuildViewProps) {
return true;
}
- const done = await service.server.isPrebuildDone(props.runningPrebuild.prebuildID);
+ const done = await getGitpodService().server.isPrebuildDone(props.runningPrebuild.prebuildID);
if (done) {
// note: this treats "done" as "available" which is not equivalent.
// This works because the backend ignores prebuilds which are not "available", and happily starts a workspace as if there was no prebuild at all.
@@ -361,7 +359,7 @@ function RunningPrebuildView(props: RunningPrebuildViewProps) {
pollTimeout = setTimeout(pollIsPrebuildDone, 10000);
};
- const disposables = watchHeadlessLogs(service.server, props.runningPrebuild.instanceID, (chunk) => logsEmitter.emit('logs', chunk), checkIsPrebuildDone);
+ const disposables = watchHeadlessLogs(props.runningPrebuild.instanceID, (chunk) => logsEmitter.emit('logs', chunk), checkIsPrebuildDone);
return function cleanup() {
clearTimeout(pollTimeout!);
disposables.dispose();
diff --git a/components/dashboard/src/start/StartWorkspace.tsx b/components/dashboard/src/start/StartWorkspace.tsx
index f5e47bca0e5e13..08b9781a64ff80 100644
--- a/components/dashboard/src/start/StartWorkspace.tsx
+++ b/components/dashboard/src/start/StartWorkspace.tsx
@@ -314,11 +314,10 @@ function ImageBuildView(props: ImageBuildViewProps) {
const logsEmitter = new EventEmitter();
useEffect(() => {
- const service = getGitpodService();
- const watchBuild = () => service.server.watchWorkspaceImageBuildLogs(props.workspaceId);
+ const watchBuild = () => getGitpodService().server.watchWorkspaceImageBuildLogs(props.workspaceId);
watchBuild();
- const toDispose = service.registerClient({
+ const toDispose = getGitpodService().registerClient({
notifyDidOpenConnection: () => watchBuild(),
onWorkspaceImageBuildLogs: (info: WorkspaceImageBuild.StateInfo, content?: WorkspaceImageBuild.LogContent) => {
if (!content) {
@@ -345,8 +344,7 @@ function HeadlessWorkspaceView(props: { instanceId: string }) {
const logsEmitter = new EventEmitter();
useEffect(() => {
- const service = getGitpodService();
- const disposables = watchHeadlessLogs(service.server, props.instanceId, (chunk) => logsEmitter.emit('logs', chunk), async () => { return false; });
+ const disposables = watchHeadlessLogs(props.instanceId, (chunk) => logsEmitter.emit('logs', chunk), async () => { return false; });
return function cleanup() {
disposables.dispose();
};
diff --git a/components/gitpod-db/src/project-db.ts b/components/gitpod-db/src/project-db.ts
index 4fbf9df8b2d15c..35a4fbb60913ef 100644
--- a/components/gitpod-db/src/project-db.ts
+++ b/components/gitpod-db/src/project-db.ts
@@ -10,8 +10,11 @@ export const ProjectDB = Symbol('ProjectDB');
export interface ProjectDB {
findProjectById(projectId: string): Promise;
findProjectByCloneUrl(cloneUrl: string): Promise;
+ findProjectsByCloneUrl(cloneUrls: string[]): Promise;
+ findProjectByTeamAndName(teamId: string, projectName: string): Promise;
findProjectByInstallationId(installationId: string): Promise;
findProjectsByTeam(teamId: string): Promise;
storeProject(project: Project): Promise;
setProjectConfiguration(projectId: string, config: ProjectConfig): Promise;
-}
\ No newline at end of file
+ markDeleted(projectId: string): Promise;
+}
diff --git a/components/gitpod-db/src/typeorm/entity/db-project.ts b/components/gitpod-db/src/typeorm/entity/db-project.ts
index 101f5d68d67b05..3f1368a038062b 100644
--- a/components/gitpod-db/src/typeorm/entity/db-project.ts
+++ b/components/gitpod-db/src/typeorm/entity/db-project.ts
@@ -36,4 +36,7 @@ export class DBProject {
// This column triggers the db-sync deletion mechanism. It's not intended for public consumption.
@Column()
deleted: boolean;
+
+ @Column()
+ markedDeleted: boolean;
}
\ No newline at end of file
diff --git a/components/gitpod-db/src/typeorm/migration/1626881279438-AddMarkedDeletedToProject.ts b/components/gitpod-db/src/typeorm/migration/1626881279438-AddMarkedDeletedToProject.ts
new file mode 100644
index 00000000000000..ea8c69b4531942
--- /dev/null
+++ b/components/gitpod-db/src/typeorm/migration/1626881279438-AddMarkedDeletedToProject.ts
@@ -0,0 +1,21 @@
+/**
+ * Copyright (c) 2021 Gitpod GmbH. All rights reserved.
+ * Licensed under the GNU Affero General Public License (AGPL).
+ * See License-AGPL.txt in the project root for license information.
+ */
+
+import {MigrationInterface, QueryRunner} from "typeorm";
+import { columnExists } from "./helper/helper";
+
+export class AddMarkedDeletedToProject1626881279438 implements MigrationInterface {
+
+ public async up(queryRunner: QueryRunner): Promise {
+ if (!(await columnExists(queryRunner, "d_b_project", "markedDeleted"))) {
+ await queryRunner.query("ALTER TABLE d_b_project ADD COLUMN `markedDeleted` tinyint(4) NOT NULL DEFAULT '0'");
+ }
+ }
+
+ public async down(queryRunner: QueryRunner): Promise {
+ }
+
+}
diff --git a/components/gitpod-db/src/typeorm/project-db-impl.ts b/components/gitpod-db/src/typeorm/project-db-impl.ts
index 9581b004a5526d..029fed4aaf2305 100644
--- a/components/gitpod-db/src/typeorm/project-db-impl.ts
+++ b/components/gitpod-db/src/typeorm/project-db-impl.ts
@@ -25,22 +25,39 @@ export class ProjectDBImpl implements ProjectDB {
public async findProjectById(projectId: string): Promise {
const repo = await this.getRepo();
- return repo.findOne({ id: projectId });
+ return repo.findOne({ id: projectId, markedDeleted: false });
}
public async findProjectByCloneUrl(cloneUrl: string): Promise {
const repo = await this.getRepo();
- return repo.findOne({ cloneUrl });
+ return repo.findOne({ cloneUrl, markedDeleted: false });
+ }
+
+ public async findProjectsByCloneUrl(cloneUrls: string[]): Promise {
+ if (cloneUrls.length === 0) {
+ return [];
+ }
+ const repo = await this.getRepo();
+ const q = repo.createQueryBuilder("project")
+ .where("project.markedDeleted = false")
+ .andWhere(`project.cloneUrl in (${ cloneUrls.map(u => `'${u}'`).join(", ") })`)
+ const result = await q.getMany();
+ return result;
+ }
+
+ public async findProjectByTeamAndName(teamId: string, projectName: string): Promise {
+ const projects = await this.findProjectsByTeam(teamId);
+ return projects.find(p => p.name === projectName);
}
public async findProjectByInstallationId(appInstallationId: string): Promise {
const repo = await this.getRepo();
- return repo.findOne({ appInstallationId });
+ return repo.findOne({ appInstallationId, markedDeleted: false });
}
public async findProjectsByTeam(teamId: string): Promise {
const repo = await this.getRepo();
- return repo.find({ teamId });
+ return repo.find({ teamId, markedDeleted: false });
}
public async storeProject(project: Project): Promise {
@@ -57,4 +74,13 @@ export class ProjectDBImpl implements ProjectDB {
project.config = config;
await repo.save(project);
}
+
+ public async markDeleted(projectId: string): Promise {
+ const repo = await this.getRepo();
+ const project = await repo.findOne({ id: projectId });
+ if (project) {
+ project.markedDeleted = true;
+ await repo.save(project);
+ }
+ }
}
diff --git a/components/gitpod-db/src/typeorm/workspace-db-impl.ts b/components/gitpod-db/src/typeorm/workspace-db-impl.ts
index 028daafb76006c..3a53525e491ae4 100644
--- a/components/gitpod-db/src/typeorm/workspace-db-impl.ts
+++ b/components/gitpod-db/src/typeorm/workspace-db-impl.ts
@@ -134,7 +134,7 @@ export abstract class AbstractTypeORMWorkspaceDBImpl implements WorkspaceDB {
* thus GREATEST gives us the highest (newest) timestamp on the running instance which correlates to the last activity on that workspace
*/
const repo = await this.getWorkspaceRepo();
- let qb = repo
+ const qb = repo
.createQueryBuilder('ws')
// We need to put the subquery into the join condition (ON) here to be able to reference `ws.id` which is
// not possible in a subquery on JOIN (e.g. 'LEFT JOIN (SELECT ... WHERE i.workspaceId = ws.id)')
@@ -152,20 +152,23 @@ export abstract class AbstractTypeORMWorkspaceDBImpl implements WorkspaceDB {
.from(DBWorkspaceInstance, 'i2')
.where('i2.phasePersisted = "running"');
}, 'wsiRunning', 'ws.id = wsiRunning.workspaceId')
- .where('ws.ownerId = :userId', options)
+ .where('ws.ownerId = :userId', { userId: options.userId })
.andWhere('ws.softDeleted IS NULL')
.andWhere('ws.deleted != TRUE')
.orderBy('wsiRunning.workspaceId', 'DESC')
.addOrderBy('GREATEST(ws.creationTime, wsi.creationTime, wsi.startedTime, wsi.stoppedTime)', 'DESC')
.limit(options.limit || 10);
if (options.searchString) {
- qb = qb.andWhere("ws.description LIKE :searchString", {searchString: `%${options.searchString}%`});
+ qb.andWhere("ws.description LIKE :searchString", {searchString: `%${options.searchString}%`});
}
if (!options.includeHeadless) {
- qb = qb.andWhere("ws.type = 'regular'");
+ qb.andWhere("ws.type = 'regular'");
}
if (options.pinnedOnly) {
- qb = qb.andWhere("ws.pinned = true");
+ qb.andWhere("ws.pinned = true");
+ }
+ if (options.projectId) {
+ qb.andWhere('ws.projectId = :projectId', { projectId: options.projectId })
}
const rawResults = await qb.getMany() as any as (Workspace & { latestInstance?: WorkspaceInstance })[]; // see leftJoinAndMapOne above
@@ -819,18 +822,36 @@ export abstract class AbstractTypeORMWorkspaceDBImpl implements WorkspaceDB {
return (res);
}
- async findPrebuiltWorkspacesByProject(projectId: string): Promise {
+ async findPrebuiltWorkspacesByProject(projectId: string, branch?: string, limit?: number): Promise {
const repo = await this.getPrebuiltWorkspaceRepo();
const query = repo.createQueryBuilder('pws')
.orderBy('pws.creationTime', 'ASC')
.innerJoinAndMapOne('pws.workspace', DBWorkspace, 'ws', 'pws.buildWorkspaceId = ws.id')
- .andWhere('pws.projectId = :projectId', { projectId })
+ .andWhere('pws.projectId = :projectId', { projectId });
+
+ if (branch) {
+ query.andWhere('pws.branch = :branch', { branch });
+ }
+ if (limit) {
+ query.limit(limit);
+ }
const res = await query.getMany();
return res;
}
+ async findPrebuiltWorkspacesById(id: string): Promise {
+ const repo = await this.getPrebuiltWorkspaceRepo();
+
+ const query = repo.createQueryBuilder('pws')
+ .orderBy('pws.creationTime', 'ASC')
+ .innerJoinAndMapOne('pws.workspace', DBWorkspace, 'ws', 'pws.buildWorkspaceId = ws.id')
+ .andWhere('pws.id = :id', { id });
+
+ return query.getOne();
+ }
+
}
@injectable()
diff --git a/components/gitpod-db/src/workspace-db.ts b/components/gitpod-db/src/workspace-db.ts
index eac79674ac24a8..2026b104e47168 100644
--- a/components/gitpod-db/src/workspace-db.ts
+++ b/components/gitpod-db/src/workspace-db.ts
@@ -13,6 +13,7 @@ export type MaybeWorkspaceInstance = WorkspaceInstance | undefined;
export interface FindWorkspacesOptions {
userId: string
+ projectId?: string
limit?: number
searchString?: string
includeHeadless?: boolean
@@ -112,5 +113,6 @@ export interface WorkspaceDB {
hardDeleteWorkspace(workspaceID: string): Promise;
- findPrebuiltWorkspacesByProject(projectId: string): Promise;
+ findPrebuiltWorkspacesByProject(projectId: string, branch?: string, limit?: number): Promise;
+ findPrebuiltWorkspacesById(prebuildId: string): Promise;
}
diff --git a/components/gitpod-protocol/src/gitpod-service.ts b/components/gitpod-protocol/src/gitpod-service.ts
index 68bbd85ff5eb91..7177cfd5c16925 100644
--- a/components/gitpod-protocol/src/gitpod-service.ts
+++ b/components/gitpod-protocol/src/gitpod-service.ts
@@ -13,7 +13,7 @@ import {
} from './protocol';
import {
Team, TeamMemberInfo,
- TeamMembershipInvite, Project, ProjectInfo, PrebuildInfo, TeamMemberRole
+ TeamMembershipInvite, Project, PrebuildInfo, TeamMemberRole
} from './teams-projects-protocol';
import { JsonRpcProxy, JsonRpcServer } from './messaging/proxy-factory';
import { Disposable, CancellationTokenSource } from 'vscode-jsonrpc';
@@ -126,8 +126,11 @@ export interface GitpodServer extends JsonRpcServer, AdminServer,
// Projects
getProviderRepositoriesForUser(params: GetProviderRepositoriesParams): Promise;
createProject(params: CreateProjectParams): Promise;
- getProjects(teamId: string): Promise;
- getPrebuilds(teamId: string, projectId: string): Promise;
+ deleteProject(projectId: string): Promise;
+ getProjects(teamId: string): Promise;
+ getProjectOverview(teamId: string, projectName: string): Promise;
+ findPrebuilds(params: FindPrebuildsParams): Promise;
+ triggerPrebuild(projectId: string, branch: string): Promise;
setProjectConfiguration(projectId: string, configString: string): Promise;
fetchProjectRepositoryConfiguration(projectId: string): Promise;
@@ -231,6 +234,13 @@ export interface CreateProjectParams {
teamId: string;
appInstallationId: string;
}
+export interface FindPrebuildsParams {
+ teamId: string;
+ projectName: string;
+ branch?: string;
+ latest?: boolean;
+ prebuildId?: string;
+}
export interface GetProviderRepositoriesParams {
provider: string;
hints?: { installationId: string } | object;
@@ -243,6 +253,8 @@ export interface ProviderRepository {
updatedAt: string;
installationId?: number;
installationUpdatedAt?: string;
+
+ inUse?: boolean;
}
export const WorkspaceTimeoutValues = ["30m", "60m", "180m"] as const;
@@ -288,6 +300,7 @@ export namespace GitpodServer {
limit?: number;
searchString?: string;
pinnedOnly?: boolean;
+ projectId?: string;
}
export interface GetAccountStatementOptions {
date?: string;
diff --git a/components/gitpod-protocol/src/protocol.ts b/components/gitpod-protocol/src/protocol.ts
index 66afe2cb7c3db0..2fa537a4b2bc7c 100644
--- a/components/gitpod-protocol/src/protocol.ts
+++ b/components/gitpod-protocol/src/protocol.ts
@@ -1008,6 +1008,18 @@ export interface Repository {
parent: Repository
}
}
+export interface Branch {
+ name: string;
+ commit: CommitInfo;
+}
+
+export interface CommitInfo {
+ author: string;
+ sha: string;
+ commitMessage: string;
+ authorAvatarUrl?: string;
+ authorDate?: string;
+}
export namespace Repository {
export function fullRepoName(repo: Repository): string {
diff --git a/components/gitpod-protocol/src/teams-projects-protocol.ts b/components/gitpod-protocol/src/teams-projects-protocol.ts
index 37b2f9e84867ac..f149aef348db5e 100644
--- a/components/gitpod-protocol/src/teams-projects-protocol.ts
+++ b/components/gitpod-protocol/src/teams-projects-protocol.ts
@@ -21,6 +21,7 @@ export interface Project {
creationTime: string;
/** This is a flag that triggers the HARD DELETION of this entity */
deleted?: boolean;
+ markedDeleted?: boolean;
}
export namespace Project {
@@ -31,21 +32,47 @@ export namespace Project {
creationTime: new Date().toISOString()
};
}
-}
-export interface ProjectInfo extends Project {
- lastPrebuild?: PrebuildInfo;
+ export interface Overview {
+ branches: BranchDetails[]
+ }
+
+ export interface BranchDetails {
+ name: string;
+ url: string;
+ isDefault: boolean;
+
+ // Latest commit
+ changeTitle: string;
+ changeDate?: string;
+ changeAuthor?: string;
+ changeAuthorAvatar?: string;
+ changePR?: string;
+ changeUrl?: string;
+ changeHash: string;
+ }
}
export interface PrebuildInfo {
id: string;
teamId: string;
- project: string;
+ projectName: string;
cloneUrl: string;
branch: string;
+ branchPrebuildNumber: string;
+ buildWorkspaceId: string;
+
startedAt: string;
startedBy: string;
+ startedByAvatar?: string;
status: PrebuiltWorkspaceState;
+ changeTitle: string;
+ changeDate: string;
+ changeAuthor: string;
+ changeAuthorAvatar?: string;
+ changePR?: string;
+ changeUrl?: string;
+ changeHash: string;
}
export interface Team {
diff --git a/components/licensor/typescript/ee/src/index.ts b/components/licensor/typescript/ee/src/index.ts
index f547939a3bca26..8c39805045c6b4 100644
--- a/components/licensor/typescript/ee/src/index.ts
+++ b/components/licensor/typescript/ee/src/index.ts
@@ -5,7 +5,7 @@
*/
import { injectable, inject, postConstruct } from 'inversify';
-import { init, Instance, dispose, isEnabled, hasEnoughSeats, canUsePrebuild, inspect, validate } from "./nativemodule";
+import { init, Instance, dispose, inspect, validate } from "./nativemodule";
import { Feature, LicensePayload } from './api';
export const LicenseKeySource = Symbol("LicenseKeySource");
@@ -38,27 +38,30 @@ export class LicenseEvaluator {
public async reloadLicense() {
this.dispose()
- await this.init()
+ // await this.init()
}
public validate(): { msg?: string, valid: boolean } {
- const v = validate(this.instanceID);
- if (v.valid) {
+ // const v = validate(this.instanceID);
+ // if (v.valid) {
return { valid: true };
- }
- return { msg: v.msg, valid: false };
+ // }
+ // return { msg: v.msg, valid: false };
}
public isEnabled(feature: Feature): boolean {
- return isEnabled(this.instanceID, feature);
+ // return isEnabled(this.instanceID, feature);
+ return true;
}
public hasEnoughSeats(seats: number): boolean {
- return hasEnoughSeats(this.instanceID, seats);
+ // return hasEnoughSeats(this.instanceID, seats);
+ return true;
}
public canUsePrebuild(totalPrebuildSecondsSpent: number): boolean {
- return canUsePrebuild(this.instanceID, totalPrebuildSecondsSpent);
+ // return canUsePrebuild(this.instanceID, totalPrebuildSecondsSpent);
+ return true;
}
public inspect(): LicensePayload {
diff --git a/components/server/ee/src/workspace/gitpod-server-impl.ts b/components/server/ee/src/workspace/gitpod-server-impl.ts
index bdcd165c832ab6..7b13d00ea9be23 100644
--- a/components/server/ee/src/workspace/gitpod-server-impl.ts
+++ b/components/server/ee/src/workspace/gitpod-server-impl.ts
@@ -7,7 +7,7 @@
import { injectable, inject } from "inversify";
import { GitpodServerImpl } from "../../../src/workspace/gitpod-server-impl";
import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing";
-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, PrebuildInfo, Queue } from "@gitpod/gitpod-protocol";
+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";
import { ResponseError } from "vscode-jsonrpc";
import { TakeSnapshotRequest, AdmissionLevel, ControlAdmissionRequest, StopWorkspacePolicy, DescribeWorkspaceRequest, SetTimeoutRequest } from "@gitpod/ws-manager/lib";
import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
@@ -1451,34 +1451,39 @@ export class GitpodServerEEImpl extends GitpodServerImpl r.cloneUrl);
+ const projects = await this.projectDB.findProjectsByCloneUrl(cloneUrls);
+
+ const cloneUrlsInUse = new Set(projects.map(p => p.cloneUrl));
+ repositories.forEach(r => { r.inUse = cloneUrlsInUse.has(r.cloneUrl) });
+
return repositories;
}
- public async getPrebuilds(teamId: string, projectName: string): Promise {
- this.checkAndBlockUser("getPrebuilds");
- const span = opentracing.globalTracer().startSpan("getPrebuilds");
- span.setTag("teamId", teamId);
- span.setTag("projectName", projectName);
- const result: PrebuildInfo[] = [];
-
- const project = (await this.projectDB.findProjectsByTeam(teamId)).find(p => p.name === projectName);
- if (project) {
- const pwss = await this.workspaceDb.trace({ span }).findPrebuiltWorkspacesByProject(project.id);
+ async triggerPrebuild(projectId: string, branch: string): Promise {
+ const user = this.checkAndBlockUser("triggerPrebuild");
- for (const pws of pwss) {
- result.push({
- id: pws.id,
- startedAt: pws.creationTime,
- startedBy: "UNKNOWN",
- teamId,
- project: projectName,
- branch: pws.branch || "unknown",
- cloneUrl: pws.cloneURL,
- status: pws.state
- });
- }
+ const project = await this.projectsService.getProject(projectId);
+ if (!project) {
+ return;
}
+ await this.guardTeamOperation(project.teamId, "get");
- return result;
+ const span = opentracing.globalTracer().startSpan("triggerPrebuild");
+ span.setTag("userId", user.id);
+
+ const contextURL = `${project.cloneUrl.replace(".git", "")}/tree/${branch}`; // just a quick hack!
+
+ const context = await this.contextParser.handle({ span }, user, contextURL) as CommitContext;
+
+ await this.prebuildManager.startPrebuild({ span }, {
+ contextURL,
+ cloneURL: project.cloneUrl,
+ commit: context.revision,
+ user,
+ branch,
+ project
+ });
}
}
diff --git a/components/server/src/auth/rate-limiter.ts b/components/server/src/auth/rate-limiter.ts
index bbaa1c20b68201..394bc6634233f5 100644
--- a/components/server/src/auth/rate-limiter.ts
+++ b/components/server/src/auth/rate-limiter.ts
@@ -91,7 +91,10 @@ function readConfig(): RateLimiterConfig {
"getProviderRepositoriesForUser": { group: "default", points: 1 },
"createProject": { group: "default", points: 1 },
"getProjects": { group: "default", points: 1 },
- "getPrebuilds": { group: "default", points: 1 },
+ "deleteProject": { group: "default", points: 1 },
+ "findPrebuilds": { group: "default", points: 1 },
+ "getProjectOverview": { group: "default", points: 1 },
+ "triggerPrebuild": { group: "default", points: 1 },
"setProjectConfiguration": { group: "default", points: 1 },
"fetchProjectRepositoryConfiguration": { group: "default", points: 1 },
"getContentBlobUploadUrl": { group: "default", points: 1 },
@@ -131,9 +134,6 @@ function readConfig(): RateLimiterConfig {
accesHeadlessLogs: { group: "default", points: 1 },
- /**
- * gitpod.io concerns
- */
"adminAddStudentEmailDomain": { group: "default", points: 1 },
"adminGetAccountStatement": { group: "default", points: 1 },
"adminIsStudent": { group: "default", points: 1 },
diff --git a/components/server/src/bitbucket/bitbucket-repository-provider.ts b/components/server/src/bitbucket/bitbucket-repository-provider.ts
index 728274fbe8721d..6d0f2020e92a40 100644
--- a/components/server/src/bitbucket/bitbucket-repository-provider.ts
+++ b/components/server/src/bitbucket/bitbucket-repository-provider.ts
@@ -4,7 +4,7 @@
* See License-AGPL.txt in the project root for license information.
*/
-import { Repository, User } from "@gitpod/gitpod-protocol";
+import { Branch, CommitInfo, Repository, User } from "@gitpod/gitpod-protocol";
import { inject, injectable } from 'inversify';
import { parseRepoUrl } from '../repohost/repo-url';
import { RepositoryProvider } from '../repohost/repository-provider';
@@ -25,4 +25,14 @@ export class BitbucketRepositoryProvider implements RepositoryProvider {
const webUrl = repo.links!.html!.href;
return { host, owner, name, cloneUrl, description, avatarUrl, webUrl };
}
+
+ async getBranches(user: User, owner: string, repo: string): Promise {
+ // todo
+ return [];
+ }
+
+ async getCommitInfo(user: User, owner: string, repo: string, ref: string): Promise {
+ // todo
+ return undefined;
+ }
}
diff --git a/components/server/src/container-module.ts b/components/server/src/container-module.ts
index 3942da61184b73..209b1ff3d1c6ea 100644
--- a/components/server/src/container-module.ts
+++ b/components/server/src/container-module.ts
@@ -79,6 +79,7 @@ import { HeadlessLogService } from './workspace/headless-log-service';
import { HeadlessLogController } from './workspace/headless-log-controller';
import { IAnalyticsWriter } from '@gitpod/gitpod-protocol/lib/analytics';
import { HeadlessLogServiceClient } from '@gitpod/content-service/lib/headless-log_grpc_pb';
+import { ProjectsService } from './projects/projects-service';
export const productionContainerModule = new ContainerModule((bind, unbind, isBound, rebind) => {
bind(Env).toSelf().inSingletonScope();
@@ -205,4 +206,6 @@ export const productionContainerModule = new ContainerModule((bind, unbind, isBo
bind(HeadlessLogService).toSelf().inSingletonScope();
bind(HeadlessLogController).toSelf().inSingletonScope();
+
+ bind(ProjectsService).toSelf().inSingletonScope();
});
diff --git a/components/server/src/github/api.ts b/components/server/src/github/api.ts
index a5ed379ddf5109..584979079cb2b2 100644
--- a/components/server/src/github/api.ts
+++ b/components/server/src/github/api.ts
@@ -9,7 +9,7 @@ import { Octokit, RestEndpointMethodTypes } from "@octokit/rest"
import { OctokitResponse } from "@octokit/types"
import { OctokitOptions } from "@octokit/core/dist-types/types"
-import { User } from "@gitpod/gitpod-protocol"
+import { Branch, CommitInfo, User } from "@gitpod/gitpod-protocol"
import { injectable, inject } from 'inversify';
import { log } from '@gitpod/gitpod-protocol/lib/util/logging';
import { GitHubScope } from './scopes';
@@ -241,11 +241,42 @@ export class GitHubRestApi {
}
public async getRepository(user: User, params: RestEndpointMethodTypes["repos"]["get"]["parameters"]): Promise {
- const key = `getRepository:${params.owner}/${params.owner}:${user.id}`
+ const key = `getRepository:${params.owner}/${params.owner}:${user.id}`;
const response = await this.runWithCache(key, user, (api) => api.repos.get(params));
return response.data;
}
+ public async getBranches(user: User, params: RestEndpointMethodTypes["repos"]["listBranches"]["parameters"]): Promise {
+ const key = `getBranches:${params.owner}/${params.owner}:${user.id}`;
+ const listBranchesResponse = (await this.runWithCache(key, user, (api) => api.repos.listBranches(params))) as RestEndpointMethodTypes["repos"]["listBranches"]["response"];
+
+ const result: Branch[] = [];
+
+ for (const branch of listBranchesResponse.data) {
+ const { commit: { sha } } = branch;
+ const commit = await this.getCommit(user, { ...params, ref: sha });
+ result.push({
+ name: branch.name,
+ commit
+ });
+ }
+
+ return result;
+ }
+
+ public async getCommit(user: User, params: RestEndpointMethodTypes["repos"]["getCommit"]["parameters"]): Promise {
+ const key = `getCommit:${params.owner}/${params.owner}/${params.ref}:${user.id}`;
+ const getCommitResponse = (await this.runWithCache(key, user, (api) => api.repos.getCommit(params))) as RestEndpointMethodTypes["repos"]["getCommit"]["response"];
+ const { sha, commit, author } = getCommitResponse.data;
+ return {
+ sha,
+ author: commit.author?.name || "nobody",
+ authorAvatarUrl: author?.avatar_url,
+ authorDate: commit.author?.date,
+ commitMessage: commit.message,
+ }
+ }
+
}
export interface GitHubResult extends OctokitResponse { }
@@ -327,7 +358,7 @@ export interface BranchRef {
name: string
commit: CommitRef
protected: boolean
- protection_url: string
+ protection_url?: string
}
export interface CommitDetails {
diff --git a/components/server/src/github/github-repository-provider.ts b/components/server/src/github/github-repository-provider.ts
index e7cafdd26654f5..b7a3b0b31d56bc 100644
--- a/components/server/src/github/github-repository-provider.ts
+++ b/components/server/src/github/github-repository-provider.ts
@@ -10,18 +10,30 @@ import { User, Repository } from "@gitpod/gitpod-protocol"
import { GitHubRestApi } from "./api";
import { RepositoryProvider } from '../repohost/repository-provider';
import { parseRepoUrl } from '../repohost/repo-url';
+import { Branch, CommitInfo } from '@gitpod/gitpod-protocol/src/protocol';
@injectable()
export class GithubRepositoryProvider implements RepositoryProvider {
@inject(GitHubRestApi) protected readonly github: GitHubRestApi;
- async getRepo(user: User, owner: string, name: string): Promise {
- const repository = await this.github.getRepository(user, { owner, repo: name });
+ async getRepo(user: User, owner: string, repo: string): Promise {
+ const repository = await this.github.getRepository(user, { owner, repo });
const cloneUrl = repository.clone_url;
const host = parseRepoUrl(cloneUrl)!.host;
const description = repository.description;
const avatarUrl = repository.owner.avatar_url;
const webUrl = repository.html_url;
- return { host, owner, name, cloneUrl, description, avatarUrl, webUrl };
+ const defaultBranch = repository.default_branch;
+ return { host, owner, name: repo, cloneUrl, description, avatarUrl, webUrl, defaultBranch };
+ }
+
+ async getBranches(user: User, owner: string, repo: string): Promise {
+ const branches = await this.github.getBranches(user, { repo, owner });
+ return branches;
+ }
+
+ async getCommitInfo(user: User, owner: string, repo: string, ref: string): Promise {
+ const commit = await this.github.getCommit(user, { repo, owner, ref });
+ return commit;
}
}
diff --git a/components/server/src/gitlab/gitlab-repository-provider.ts b/components/server/src/gitlab/gitlab-repository-provider.ts
index 525a16020a1d07..5fe272b2832db0 100644
--- a/components/server/src/gitlab/gitlab-repository-provider.ts
+++ b/components/server/src/gitlab/gitlab-repository-provider.ts
@@ -6,7 +6,7 @@
import { injectable, inject } from 'inversify';
-import { User, Repository } from "@gitpod/gitpod-protocol"
+import { User, Repository, Branch, CommitInfo } from "@gitpod/gitpod-protocol"
import { GitLabApi, GitLab } from "./api";
import { RepositoryProvider } from '../repohost/repository-provider';
import { parseRepoUrl } from '../repohost/repo-url';
@@ -29,4 +29,14 @@ export class GitlabRepositoryProvider implements RepositoryProvider {
const webUrl = response.web_url;
return { host, owner, name, cloneUrl, description, avatarUrl, webUrl };
}
+
+ async getBranches(user: User, owner: string, repo: string): Promise {
+ // todo
+ return [];
+ }
+
+ async getCommitInfo(user: User, owner: string, repo: string, ref: string): Promise {
+ // todo
+ return undefined;
+ }
}
diff --git a/components/server/src/projects/projects-service.ts b/components/server/src/projects/projects-service.ts
new file mode 100644
index 00000000000000..8a0e5dce6ae88c
--- /dev/null
+++ b/components/server/src/projects/projects-service.ts
@@ -0,0 +1,162 @@
+/**
+ * Copyright (c) 2021 Gitpod GmbH. All rights reserved.
+ * Licensed under the GNU Affero General Public License (AGPL).
+ * See License-AGPL.txt in the project root for license information.
+ */
+
+import { inject, injectable } from "inversify";
+import { DBWithTracing, ProjectDB, TeamDB, TracedWorkspaceDB, WorkspaceDB } from "@gitpod/gitpod-db/lib";
+import { Branch, CommitInfo, CreateProjectParams, FindPrebuildsParams, PrebuildInfo, PrebuiltWorkspace, Project, User } from "@gitpod/gitpod-protocol";
+import { HostContextProvider } from "../auth/host-context-provider";
+import { parseRepoUrl } from "../repohost";
+
+@injectable()
+export class ProjectsService {
+
+ @inject(ProjectDB) protected readonly projectDB: ProjectDB;
+ @inject(TeamDB) protected readonly teamDB: TeamDB;
+ @inject(TracedWorkspaceDB) protected readonly workspaceDb: DBWithTracing;
+ @inject(HostContextProvider) protected readonly hostContextProvider: HostContextProvider;
+
+ async getProject(projectId: string): Promise {
+ return this.projectDB.findProjectById(projectId);
+ }
+
+ async getProjects(teamId: string): Promise {
+ const projects = await this.projectDB.findProjectsByTeam(teamId);
+ return projects;
+ }
+
+ async getProjectOverview(user: User, teamId: string, projectName: string): Promise {
+ const project = await this.projectDB.findProjectByTeamAndName(teamId, projectName);
+ if (!project) {
+ return undefined;
+ }
+ const branches = await this.getBranchDetails(user, project);
+ return { branches };
+ }
+
+ protected getRepositoryProvider(project: Project) {
+ const parsedUrl = parseRepoUrl(project.cloneUrl);
+ const repositoryProvider = parsedUrl && this.hostContextProvider.get(parsedUrl.host)?.services?.repositoryProvider;
+ return repositoryProvider;
+ }
+
+ async getBranchDetails(user: User, project: Project): Promise {
+ const parsedUrl = parseRepoUrl(project.cloneUrl);
+ if (!parsedUrl) {
+ return [];
+ }
+ const { owner, repo } = parsedUrl;
+ const repositoryProvider = this.getRepositoryProvider(project);
+ if (!repositoryProvider) {
+ return [];
+ }
+ const repository = await repositoryProvider.getRepo(user, owner, repo);
+ const branches = await repositoryProvider.getBranches(user, owner, repo);
+
+ const result: Project.BranchDetails[] = [];
+ for (const branch of branches) {
+ const { name, commit } = branch;
+ result.push({
+ name,
+ url: `${repository.webUrl}/tree/${branch.name}`, // todo: compute in repositoryProvider
+ changeAuthor: commit.author,
+ changeDate: commit.authorDate,
+ changeHash: commit.sha,
+ changeTitle: commit.commitMessage,
+ changeAuthorAvatar: commit.authorAvatarUrl,
+ isDefault: repository.defaultBranch === branch.name,
+ changePR: "changePR", // todo: compute in repositoryProvider
+ changeUrl: "changeUrl", // todo: compute in repositoryProvider
+ });
+ }
+ return result;
+ }
+
+ async createProject({ name, cloneUrl, teamId, appInstallationId }: CreateProjectParams): Promise {
+ return this.projectDB.storeProject(Project.create({ name, cloneUrl, teamId, appInstallationId }));
+ }
+
+ async deleteProject(projectId: string): Promise {
+ return this.projectDB.markDeleted(projectId);
+ }
+
+ protected async getLastPrebuild(project: Project, branch: Branch): Promise {
+ const prebuilds = await this.workspaceDb.trace({}).findPrebuiltWorkspacesByProject(project.id, branch?.name);
+ const prebuild = prebuilds[prebuilds.length - 1];
+ if (!prebuild) {
+ return undefined;
+ }
+ return await this.toPrebuildInfo(project, prebuild, branch.commit);
+ }
+
+ async findPrebuilds(user: User, params: FindPrebuildsParams): Promise {
+ const { teamId, projectName, prebuildId } = params;
+ const project = await this.projectDB.findProjectByTeamAndName(teamId, projectName);
+ if (!project) {
+ return [];
+ }
+ const parsedUrl = parseRepoUrl(project.cloneUrl);
+ if (!parsedUrl) {
+ return [];
+ }
+ const { owner, repo, host } = parsedUrl;
+ const repositoryProvider = this.hostContextProvider.get(host)?.services?.repositoryProvider;
+ if (!repositoryProvider) {
+ return [];
+ }
+
+ let prebuilds: PrebuiltWorkspace[] = [];
+ const result: PrebuildInfo[] = [];
+
+ if (prebuildId) {
+ const pbws = await this.workspaceDb.trace({}).findPrebuiltWorkspacesById(prebuildId);
+ if (pbws) {
+ prebuilds.push(pbws);
+ }
+ } else {
+ const limit = params.latest ? 1 : undefined;
+ let branch = params.branch;
+ if (limit && !branch) {
+ const repository = await repositoryProvider.getRepo(user, owner, repo);
+ branch = repository.defaultBranch;
+ }
+ prebuilds = await this.workspaceDb.trace({}).findPrebuiltWorkspacesByProject(project.id, branch, limit);
+ }
+
+ for (const prebuild of prebuilds) {
+ const commit = await repositoryProvider?.getCommitInfo(user, owner, repo, prebuild.commit);
+ if (commit) {
+ result.push(await this.toPrebuildInfo(project, prebuild, commit));
+ }
+ }
+ return result;
+ }
+
+ protected async toPrebuildInfo(project: Project, prebuild: PrebuiltWorkspace, commit: CommitInfo): Promise {
+ const { teamId, name: projectName } = project;
+
+ return {
+ id: prebuild.id,
+ buildWorkspaceId: prebuild.buildWorkspaceId,
+ startedAt: prebuild.creationTime,
+ startedBy: "", // TODO
+ startedByAvatar: "", // TODO
+ teamId,
+ projectName,
+ branch: prebuild.branch || "unknown",
+ cloneUrl: prebuild.cloneURL,
+ status: prebuild.state,
+ changeAuthor: commit.author,
+ changeAuthorAvatar: commit.authorAvatarUrl,
+ changeDate: commit.authorDate || "",
+ changeHash: commit.sha,
+ changeTitle: commit.commitMessage,
+ // changePR
+ // changeUrl
+ branchPrebuildNumber: "42"
+ };
+ }
+
+}
diff --git a/components/server/src/repohost/repository-provider.ts b/components/server/src/repohost/repository-provider.ts
index 6ee6a0b7efa1e5..6f8b1c5f0bb50c 100644
--- a/components/server/src/repohost/repository-provider.ts
+++ b/components/server/src/repohost/repository-provider.ts
@@ -5,9 +5,11 @@
*/
-import { Repository, User } from "@gitpod/gitpod-protocol"
+import { Branch, CommitInfo, Repository, User } from "@gitpod/gitpod-protocol"
export const RepositoryProvider = Symbol('RepositoryProvider');
export interface RepositoryProvider {
getRepo(user: User, owner: string, repo: string): Promise;
+ getBranches(user: User, owner: string, repo: string): Promise;
+ getCommitInfo(user: User, owner: string, repo: string, ref: string): Promise
}
\ No newline at end of file
diff --git a/components/server/src/workspace/gitpod-server-impl.ts b/components/server/src/workspace/gitpod-server-impl.ts
index 46622c5b0e388d..6e8ec8bc0167e7 100644
--- a/components/server/src/workspace/gitpod-server-impl.ts
+++ b/components/server/src/workspace/gitpod-server-impl.ts
@@ -7,7 +7,7 @@
import { BlobServiceClient } from "@gitpod/content-service/lib/blobs_grpc_pb";
import { DownloadUrlRequest, DownloadUrlResponse, UploadUrlRequest, UploadUrlResponse } from '@gitpod/content-service/lib/blobs_pb';
import { AppInstallationDB, UserDB, UserMessageViewsDB, WorkspaceDB, DBWithTracing, TracedWorkspaceDB, DBGitpodToken, DBUser, UserStorageResourcesDB, ProjectDB, TeamDB } from '@gitpod/gitpod-db/lib';
-import { AuthProviderEntry, AuthProviderInfo, Branding, CommitContext, Configuration, CreateWorkspaceMode, DisposableCollection, GetWorkspaceTimeoutResult, GitpodClient, GitpodServer, GitpodToken, GitpodTokenType, InstallPluginsParams, PermissionName, PortVisibility, PrebuiltWorkspace, PrebuiltWorkspaceContext, PreparePluginUploadParams, ResolvedPlugins, ResolvePluginsParams, SetWorkspaceTimeoutResult, StartPrebuildContext, StartWorkspaceResult, Terms, Token, UninstallPluginParams, User, UserEnvVar, UserEnvVarValue, UserInfo, WhitelistedRepository, Workspace, WorkspaceContext, WorkspaceCreationResult, WorkspaceImageBuild, WorkspaceInfo, WorkspaceInstance, WorkspaceInstancePort, WorkspaceInstanceUser, WorkspaceTimeoutDuration, GuessGitTokenScopesParams, GuessedGitTokenScopes, Team, TeamMemberInfo, TeamMembershipInvite, CreateProjectParams, ProjectInfo, Project, ProviderRepository, PrebuildInfo, TeamMemberRole, WithDefaultConfig } from '@gitpod/gitpod-protocol';
+import { AuthProviderEntry, AuthProviderInfo, Branding, CommitContext, Configuration, CreateWorkspaceMode, DisposableCollection, GetWorkspaceTimeoutResult, GitpodClient, GitpodServer, GitpodToken, GitpodTokenType, InstallPluginsParams, PermissionName, PortVisibility, PrebuiltWorkspace, PrebuiltWorkspaceContext, PreparePluginUploadParams, ResolvedPlugins, ResolvePluginsParams, SetWorkspaceTimeoutResult, StartPrebuildContext, StartWorkspaceResult, Terms, Token, UninstallPluginParams, User, UserEnvVar, UserEnvVarValue, UserInfo, WhitelistedRepository, Workspace, WorkspaceContext, WorkspaceCreationResult, WorkspaceImageBuild, WorkspaceInfo, WorkspaceInstance, WorkspaceInstancePort, WorkspaceInstanceUser, WorkspaceTimeoutDuration, GuessGitTokenScopesParams, GuessedGitTokenScopes, Team, TeamMemberInfo, TeamMembershipInvite, CreateProjectParams, Project, ProviderRepository, PrebuildInfo, TeamMemberRole, WithDefaultConfig, FindPrebuildsParams } from '@gitpod/gitpod-protocol';
import { AccountStatement } from "@gitpod/gitpod-protocol/lib/accounting-protocol";
import { AdminBlockUserRequest, AdminGetListRequest, AdminGetListResult, AdminGetWorkspacesRequest, AdminModifyPermanentWorkspaceFeatureFlagRequest, AdminModifyRoleOrPermissionRequest, WorkspaceAndInstance } from '@gitpod/gitpod-protocol/lib/admin-protocol';
import { GetLicenseInfoResult, LicenseFeature, LicenseValidationResult } from '@gitpod/gitpod-protocol/lib/license-protocol';
@@ -51,6 +51,7 @@ import { WorkspaceStarter } from './workspace-starter';
import { HeadlessLogUrls } from "@gitpod/gitpod-protocol/lib/headless-workspace-log";
import { HeadlessLogService } from "./headless-log-service";
import { InvalidGitpodYMLError } from "./config-provider";
+import { ProjectsService } from "../projects/projects-service";
@injectable()
export class GitpodServerImpl implements GitpodServer, Disposable {
@@ -95,6 +96,8 @@ export class GitpodServerImpl {
- this.checkUser("getProviderRepositoriesForUser");
+ this.checkAndBlockUser("getProviderRepositoriesForUser");
// Note: this operation is per-user only, hence needs no resource guard
+
+ // implemented in EE
return [];
}
-
- public async createProject(params: CreateProjectParams): Promise {
- this.checkAndBlockUser("createProject");
- const { name, cloneUrl, teamId, appInstallationId } = params;
+ async createProject(params: CreateProjectParams): Promise {
+ this.checkUser("createProject");
// Anyone who can read a team's information (i.e. any team member) can create a new project.
- await this.guardTeamOperation(teamId, "get");
- return this.projectDB.storeProject(Project.create({name, cloneUrl, teamId, appInstallationId}));
+ await this.guardTeamOperation(params.teamId, "get");
+ return this.projectsService.createProject(params);
}
-
- public async getProjects(teamId: string): Promise {
+ async deleteProject(projectId: string): Promise {
+ this.checkUser("deleteProject");
+ const project = await this.projectsService.getProject(projectId);
+ if (!project) {
+ return;
+ }
+ await this.guardTeamOperation(project.teamId, "delete"); // this is actually not deletion of a team ;-)
+ return this.projectsService.deleteProject(projectId);
+ }
+ async getProjects(teamId: string): Promise {
this.checkUser("getProjects");
await this.guardTeamOperation(teamId, "get");
- return this.projectDB.findProjectsByTeam(teamId);
+
+ return this.projectsService.getProjects(teamId);
}
+ async findPrebuilds(params: FindPrebuildsParams): Promise {
+ const user = this.checkAndBlockUser("getPrebuilds");
+ await this.guardTeamOperation(params.teamId, "get");
- public async getPrebuilds(teamId: string, projectId: string): Promise {
- this.checkUser("getPrebuilds");
+ return this.projectsService.findPrebuilds(user, params);
+ }
+ async getProjectOverview(teamId: string, projectName: string): Promise {
+ const user = this.checkAndBlockUser("getProjectOverview");
await this.guardTeamOperation(teamId, "get");
- return [];
+
+ return this.projectsService.getProjectOverview(user, teamId, projectName);
}
+ async triggerPrebuild(projectId: string, branch: string): Promise {
+ this.checkAndBlockUser("triggerPrebuild");
+
+ // implemented in EE
+ }
+
public async setProjectConfiguration(projectId: string, configString: string): Promise {
this.checkAndBlockUser("setProjectConfiguration");
@@ -1974,4 +1998,5 @@ export class GitpodServerImpl