Skip to content

Commit

Permalink
[projects] lookup projects by id not cloneURL
Browse files Browse the repository at this point in the history
  • Loading branch information
svenefftinge committed Sep 22, 2023
1 parent 2529922 commit 48a3c89
Show file tree
Hide file tree
Showing 17 changed files with 225 additions and 325 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ export const NewProjectRepoList: FC<Props> = ({ filteredRepos, noReposAvailable,
<div className="flex-grow">
<div
className={
"text-base text-gray-900 dark:text-gray-50 font-medium rounded-xl whitespace-nowrap" +
(r.inUse ? " text-gray-400 dark:text-gray-500" : "text-gray-700")
"text-base text-gray-900 dark:text-gray-50 font-medium rounded-xl whitespace-nowrap text-gray-700"
}
>
{toSimpleName(r)}
Expand All @@ -39,17 +38,9 @@ export const NewProjectRepoList: FC<Props> = ({ filteredRepos, noReposAvailable,
</div>
<div className="flex justify-end">
<div className="h-full my-auto flex self-center opacity-0 group-hover:opacity-100 items-center mr-2 text-right">
{!r.inUse ? (
<Button onClick={() => onRepoSelected(r)} loading={isCreating}>
Select
</Button>
) : (
<p className="text-gray-500">
Project already
<br />
exists.
</p>
)}
<Button onClick={() => onRepoSelected(r)} loading={isCreating}>
Select
</Button>
</div>
</div>
</div>
Expand Down
3 changes: 1 addition & 2 deletions components/gitpod-db/src/project-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import { TransactionalDB } from "./typeorm/transactional-db-impl";
export const ProjectDB = Symbol("ProjectDB");
export interface ProjectDB extends TransactionalDB<ProjectDB> {
findProjectById(projectId: string): Promise<Project | undefined>;
findProjectByCloneUrl(cloneUrl: string): Promise<Project | undefined>;
findProjectsByCloneUrls(cloneUrls: string[]): Promise<(Project & { teamOwners?: string[] })[]>;
findProjectsByCloneUrl(cloneUrl: string): Promise<Project[]>;
findProjects(orgID: string): Promise<Project[]>;
findProjectsBySearchTerm(
offset: number,
Expand Down
44 changes: 4 additions & 40 deletions components/gitpod-db/src/typeorm/project-db-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import { PartialProject, Project, ProjectEnvVar, ProjectEnvVarWithValue, ProjectUsage } from "@gitpod/gitpod-protocol";
import { EncryptionService } from "@gitpod/gitpod-protocol/lib/encryption/encryption-service";
import { inject, injectable, optional } from "inversify";
import { EntityManager, Repository } from "typeorm";
import { EntityManager, FindConditions, Repository } from "typeorm";
import { v4 as uuidv4 } from "uuid";
import { ProjectDB } from "../project-db";
import { DBProject } from "./entity/db-project";
Expand Down Expand Up @@ -58,46 +58,10 @@ export class ProjectDBImpl extends TransactionalDBImpl<ProjectDB> implements Pro
return repo.findOne({ id: projectId, markedDeleted: false });
}

public async findProjectByCloneUrl(cloneUrl: string): Promise<Project | undefined> {
public async findProjectsByCloneUrl(cloneUrl: string): Promise<Project[]> {
const repo = await this.getRepo();
return repo.findOne({ cloneUrl, markedDeleted: false });
}

public async findProjectsByCloneUrls(cloneUrls: string[]): Promise<(Project & { teamOwners?: string[] })[]> {
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 projects = await q.getMany();

const teamIds = Array.from(new Set(projects.map((p) => p.teamId).filter((id) => !!id)));

const teamIdsAndOwners =
teamIds.length === 0
? []
: ((await (
await this.getEntityManager()
).query(`
SELECT member.teamId AS teamId, user.name AS owner FROM d_b_user AS user
LEFT JOIN d_b_team_membership AS member ON (user.id = member.userId)
WHERE member.teamId IN (${teamIds.map((id) => `'${id}'`).join(", ")})
AND member.deleted = 0
AND member.role = 'owner'
`)) as { teamId: string; owner: string }[]);

const result: (Project & { teamOwners?: string[] })[] = [];
for (const project of projects) {
result.push({
...project,
teamOwners: teamIdsAndOwners.filter((i) => i.teamId === project.teamId).map((i) => i.owner),
});
}

return result;
const conditions: FindConditions<DBProject> = { cloneUrl, markedDeleted: false };
return repo.find(conditions);
}

public async findProjects(orgId: string): Promise<Project[]> {
Expand Down
47 changes: 12 additions & 35 deletions components/gitpod-db/src/typeorm/workspace-db-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import {
} from "./metrics";
import { TransactionalDBImpl } from "./transactional-db-impl";
import { TypeORM } from "./typeorm";
import { ApplicationError, ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";

type RawTo<T> = (instance: WorkspaceInstance, ws: Workspace) => T;
interface OrderBy {
Expand Down Expand Up @@ -710,16 +711,19 @@ export class TypeORMWorkspaceDBImpl extends TransactionalDBImpl<WorkspaceDB> imp

// Find the (last triggered) prebuild for a given commit
public async findPrebuiltWorkspaceByCommit(
cloneURL: string,
projectId: string,
commit: string,
): Promise<PrebuiltWorkspace | undefined> {
if (!commit || !cloneURL) {
return undefined;
if (!commit || !projectId) {
throw new ApplicationError(ErrorCodes.INTERNAL_SERVER_ERROR, "Illegal arguments", { projectId, commit });
}
const repo = await this.getPrebuiltWorkspaceRepo();
return await repo
.createQueryBuilder("pws")
.where("pws.cloneURL = :cloneURL AND pws.commit LIKE :commit", { cloneURL, commit: commit + "%" })
.where("pws.projectId = :projectId AND pws.commit LIKE :commit", {
projectId,
commit: commit + "%",
})
.orderBy("pws.creationTime", "DESC")
.innerJoinAndMapOne(
"pws.workspace",
Expand Down Expand Up @@ -770,19 +774,12 @@ export class TypeORMWorkspaceDBImpl extends TransactionalDBImpl<WorkspaceDB> imp
const repo = await this.getPrebuiltWorkspaceRepo();
return await repo.findOne(pwsid);
}
public async countRunningPrebuilds(cloneURL: string): Promise<number> {
const repo = await this.getPrebuiltWorkspaceRepo();
return await repo
.createQueryBuilder("pws")
.where('pws.cloneURL = :cloneURL AND state = "building"', { cloneURL })
.getCount();
}

public async findPrebuildsWithWorkpace(cloneURL: string): Promise<PrebuildWithWorkspace[]> {
public async findPrebuildsWithWorkspace(projectId: string): Promise<PrebuildWithWorkspace[]> {
const repo = await this.getPrebuiltWorkspaceRepo();

let query = repo.createQueryBuilder("pws");
query = query.where("pws.cloneURL = :cloneURL", { cloneURL });
query = query.where("pws.projectId = :projectId", { projectId });
query = query.orderBy("pws.creationTime", "DESC");
query = query.innerJoinAndMapOne("pws.workspace", DBWorkspace, "ws", "pws.buildWorkspaceId = ws.id");
query = query.andWhere("ws.deleted = false");
Expand All @@ -798,37 +795,17 @@ export class TypeORMWorkspaceDBImpl extends TransactionalDBImpl<WorkspaceDB> imp
});
}

public async countUnabortedPrebuildsSince(cloneURL: string, date: Date): Promise<number> {
public async countUnabortedPrebuildsSince(projectId: string, date: Date): Promise<number> {
const abortedState: PrebuiltWorkspaceState = "aborted";
const repo = await this.getPrebuiltWorkspaceRepo();

let query = repo.createQueryBuilder("pws");
query = query.where("pws.cloneURL = :cloneURL", { cloneURL });
query = query.where("pws.projectId != :projectId", { projectId });
query = query.andWhere("pws.creationTime >= :time", { time: date.toISOString() });
query = query.andWhere("pws.state != :state", { state: abortedState });
return query.getCount();
}

public async findQueuedPrebuilds(cloneURL?: string): Promise<PrebuildWithWorkspace[]> {
const repo = await this.getPrebuiltWorkspaceRepo();

let query = await repo.createQueryBuilder("pws");
query = query.where('state = "queued"');
if (cloneURL) {
query = query.andWhere("pws.cloneURL = :cloneURL", { cloneURL });
}
query = query.orderBy("pws.creationTime", "ASC");
query = query.innerJoinAndMapOne("pws.workspace", DBWorkspace, "ws", "pws.buildWorkspaceId = ws.id");

const res = await query.getMany();
return res.map((r) => {
const withWorkspace: PrebuiltWorkspace & { workspace: Workspace } = r as any;
return {
prebuild: r,
workspace: withWorkspace.workspace,
};
});
}
public async attachUpdatableToPrebuild(pwsid: string, update: PrebuiltWorkspaceUpdatable): Promise<void> {
const repo = await this.getPrebuiltWorkspaceUpdatableRepo();
await repo.save(update);
Expand Down
18 changes: 17 additions & 1 deletion components/gitpod-db/src/workspace-db.spec.db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { TypeORM } from "./typeorm/typeorm";
import { DBPrebuiltWorkspace } from "./typeorm/entity/db-prebuilt-workspace";
import { secondsBefore } from "@gitpod/gitpod-protocol/lib/util/timeutil";
import { resetDB } from "./test/reset-db";
import { v4 } from "uuid";

@suite
class WorkspaceDBSpec {
Expand Down Expand Up @@ -508,6 +509,7 @@ class WorkspaceDBSpec {
public async testCountUnabortedPrebuildsSince() {
const now = new Date();
const cloneURL = "https://github.com/gitpod-io/gitpod";
const projectId = v4();

await Promise.all([
// Created now, and queued
Expand All @@ -516,6 +518,7 @@ class WorkspaceDBSpec {
buildWorkspaceId: "apples",
creationTime: now.toISOString(),
cloneURL: cloneURL,
projectId,
commit: "",
state: "queued",
statusVersion: 0,
Expand All @@ -526,6 +529,7 @@ class WorkspaceDBSpec {
buildWorkspaceId: "bananas",
creationTime: now.toISOString(),
cloneURL: cloneURL,
projectId,
commit: "",
state: "aborted",
statusVersion: 0,
Expand All @@ -536,14 +540,26 @@ class WorkspaceDBSpec {
buildWorkspaceId: "oranges",
creationTime: secondsBefore(now.toISOString(), 62),
cloneURL: cloneURL,
projectId,
commit: "",
state: "available",
statusVersion: 0,
}),
// different project now and queued
this.storePrebuiltWorkspace({
id: "prebuild123-other",
buildWorkspaceId: "apples",
creationTime: now.toISOString(),
cloneURL: cloneURL,
projectId: "other-projectId",
commit: "",
state: "queued",
statusVersion: 0,
}),
]);

const minuteAgo = secondsBefore(now.toISOString(), 60);
const unabortedCount = await this.db.countUnabortedPrebuildsSince(cloneURL, new Date(minuteAgo));
const unabortedCount = await this.db.countUnabortedPrebuildsSince(projectId, new Date(minuteAgo));
expect(unabortedCount).to.eq(1);
}

Expand Down
9 changes: 3 additions & 6 deletions components/gitpod-db/src/workspace-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ export interface WorkspaceDB {
findInstancesByPhase(phases: string[]): Promise<WorkspaceInstance[]>;

getWorkspaceCount(type?: String): Promise<Number>;
getWorkspaceCountByCloneURL(cloneURL: string, sinceLastDays?: number, type?: string): Promise<number>;
getInstanceCount(type?: string): Promise<number>;

findRegularRunningInstances(userId?: string): Promise<WorkspaceInstance[]>;
Expand All @@ -158,17 +157,15 @@ export interface WorkspaceDB {
updateSnapshot(snapshot: DeepPartial<Snapshot> & Pick<Snapshot, "id">): Promise<void>;

storePrebuiltWorkspace(pws: PrebuiltWorkspace): Promise<PrebuiltWorkspace>;
findPrebuiltWorkspaceByCommit(cloneURL: string, commit: string): Promise<PrebuiltWorkspace | undefined>;
findPrebuiltWorkspaceByCommit(projectId: string, commit: string): Promise<PrebuiltWorkspace | undefined>;
findActivePrebuiltWorkspacesByBranch(
projectId: string,
branch: string,
): Promise<PrebuildWithWorkspaceAndInstances[]>;
findPrebuildsWithWorkpace(cloneURL: string): Promise<PrebuildWithWorkspace[]>;
findPrebuildsWithWorkspace(projectId: string): Promise<PrebuildWithWorkspace[]>;
findPrebuildByWorkspaceID(wsid: string): Promise<PrebuiltWorkspace | undefined>;
findPrebuildByID(pwsid: string): Promise<PrebuiltWorkspace | undefined>;
countRunningPrebuilds(cloneURL: string): Promise<number>;
countUnabortedPrebuildsSince(cloneURL: string, date: Date): Promise<number>;
findQueuedPrebuilds(cloneURL?: string): Promise<PrebuildWithWorkspace[]>;
countUnabortedPrebuildsSince(projectId: string, date: Date): Promise<number>;
attachUpdatableToPrebuild(pwsid: string, update: PrebuiltWorkspaceUpdatable): Promise<void>;
findUpdatablesForPrebuild(pwsid: string): Promise<PrebuiltWorkspaceUpdatable[]>;
markUpdatableResolved(updatableId: string): Promise<void>;
Expand Down
3 changes: 1 addition & 2 deletions components/gitpod-protocol/src/gitpod-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,8 +325,6 @@ export interface ProviderRepository {
updatedAt?: string;
installationId?: number;
installationUpdatedAt?: string;

inUse?: { userName: string };
}

export interface ClientHeaderFields {
Expand Down Expand Up @@ -418,6 +416,7 @@ export namespace GitpodServer {
export interface CreateWorkspaceOptions extends StartWorkspaceOptions {
contextUrl: string;
organizationId: string;
projectId?: string;

// whether running workspaces on the same context should be ignored. If false (default) users will be asked.
//TODO(se) remove this option and let clients do that check if they like. The new create workspace page does it already
Expand Down
33 changes: 18 additions & 15 deletions components/server/src/prebuilds/bitbucket-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import express from "express";
import { postConstruct, injectable, inject } from "inversify";
import { ProjectDB, TeamDB, WebhookEventDB } from "@gitpod/gitpod-db/lib";
import { TeamDB, WebhookEventDB } from "@gitpod/gitpod-db/lib";
import { User, StartPrebuildResult, CommitContext, CommitInfo, Project, WebhookEvent } from "@gitpod/gitpod-protocol";
import { PrebuildManager } from "./prebuild-manager";
import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing";
Expand All @@ -18,19 +18,21 @@ import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
import { UserService } from "../user/user-service";
import { ApplicationError, ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
import { URL } from "url";
import { ProjectsService } from "../projects/projects-service";

@injectable()
export class BitbucketApp {
@inject(UserService) protected readonly userService: UserService;
@inject(PrebuildManager) protected readonly prebuildManager: PrebuildManager;
@inject(TokenService) protected readonly tokenService: TokenService;
@inject(ProjectDB) protected readonly projectDB: ProjectDB;
@inject(TeamDB) protected readonly teamDB: TeamDB;
@inject(ContextParser) protected readonly contextParser: ContextParser;
@inject(HostContextProvider) protected readonly hostCtxProvider: HostContextProvider;
@inject(WebhookEventDB) protected readonly webhookEvents: WebhookEventDB;
constructor(
@inject(UserService) private readonly userService: UserService,
@inject(PrebuildManager) private readonly prebuildManager: PrebuildManager,
@inject(TeamDB) private readonly teamDB: TeamDB,
@inject(ContextParser) private readonly contextParser: ContextParser,
@inject(HostContextProvider) private readonly hostCtxProvider: HostContextProvider,
@inject(WebhookEventDB) private readonly webhookEvents: WebhookEventDB,
@inject(ProjectsService) private readonly projectService: ProjectsService,
) {}

protected _router = express.Router();
private _router = express.Router();
public static path = "/apps/bitbucket/";

@postConstruct()
Expand Down Expand Up @@ -82,7 +84,7 @@ export class BitbucketApp {
});
}

protected async findUser(ctx: TraceContext, secretToken: string): Promise<User> {
private async findUser(ctx: TraceContext, secretToken: string): Promise<User> {
const span = TraceContext.startSpan("BitbucketApp.findUser", ctx);
try {
span.setTag("secret-token", secretToken);
Expand All @@ -108,7 +110,7 @@ export class BitbucketApp {
}
}

protected async handlePushHook(
private async handlePushHook(
ctx: TraceContext,
data: ParsedRequestData,
user: User,
Expand Down Expand Up @@ -199,12 +201,13 @@ export class BitbucketApp {
* @param webhookInstaller the user account known from the webhook installation
* @returns a promise which resolves to a user account and an optional project.
*/
protected async findProjectAndOwner(
private async findProjectAndOwner(
cloneURL: string,
webhookInstaller: User,
): Promise<{ user: User; project?: Project }> {
try {
const project = await this.projectDB.findProjectByCloneUrl(cloneURL);
const projects = await this.projectService.findProjectsByCloneUrl(webhookInstaller.id, cloneURL);
const project = projects[0];
if (project) {
if (!project.teamId) {
throw new ApplicationError(ErrorCodes.INTERNAL_SERVER_ERROR, "Project has no teamId");
Expand All @@ -228,7 +231,7 @@ export class BitbucketApp {
return { user: webhookInstaller };
}

protected createContextUrl(data: ParsedRequestData) {
private createContextUrl(data: ParsedRequestData) {
const contextUrl = `${data.repoUrl}/src/${data.commitHash}/?at=${encodeURIComponent(data.branchName)}`;
return contextUrl;
}
Expand Down
Loading

0 comments on commit 48a3c89

Please sign in to comment.