Skip to content

Include GitLab subgroups #6546

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion components/dashboard/src/projects/ConfigureProject.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export default function () {
const location = useLocation();
const team = getCurrentTeam(location, teams);
const routeMatch = useRouteMatch<{ teamSlug: string, projectSlug: string }>("/(t/)?:teamSlug/:projectSlug/configure");
const projectSlug = routeMatch?.params.projectSlug;
const [project, setProject] = useState<Project | undefined>();
const [gitpodYml, setGitpodYml] = useState<string>('');
const [dockerfile, setDockerfile] = useState<string>('');
Expand Down Expand Up @@ -93,7 +94,11 @@ export default function () {
const projects = (!!team
? await getGitpodService().server.getTeamProjects(team.id)
: await getGitpodService().server.getUserProjects());
const project = projects.find(p => p.name === routeMatch?.params.projectSlug);

const project = projectSlug && projects.find(
p => p.slug ? p.slug === projectSlug :
p.name === projectSlug);

if (!project) {
setIsDetecting(false);
setEditorMessage(<EditorMessage type="warning" heading="Couldn't load project information." message="Please try to reload this page." />);
Expand Down
6 changes: 3 additions & 3 deletions components/dashboard/src/projects/NewProject.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export default function NewProject() {
if (!provider) {
return;
}
const repo = reposInAccounts.find(r => r.account === selectedAccount && r.name === selectedRepo);
const repo = reposInAccounts.find(r => r.account === selectedAccount && r.path === selectedRepo);
if (!repo) {
console.error("No repo selected!")
return;
Expand All @@ -179,7 +179,7 @@ export default function NewProject() {
appInstallationId: String(repo.installationId),
});

history.push(`/${User.is(teamOrUser) ? 'projects' : 't/'+teamOrUser.slug}/${repo.name}/configure`);
history.push(`/${User.is(teamOrUser) ? 'projects' : 't/'+teamOrUser.slug}/${repo.path}/configure`);
} catch (error) {
const message = (error && error?.message) || "Failed to create new project."
window.alert(message);
Expand Down Expand Up @@ -269,7 +269,7 @@ export default function NewProject() {
<div className="flex justify-end">
<div className="h-full my-auto flex self-center opacity-0 group-hover:opacity-100">
{!r.inUse ? (
<button className="primary" onClick={() => setSelectedRepo(r.name)}>Select</button>
<button className="primary" onClick={() => setSelectedRepo(r.path)}>Select</button>
) : (
<p className="my-auto">already taken</p>
)}
Expand Down
14 changes: 9 additions & 5 deletions components/dashboard/src/projects/Prebuild.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default function () {
const team = getCurrentTeam(location, teams);

const match = useRouteMatch<{ team: string, project: string, prebuildId: string }>("/(t/)?:team/:project/:prebuildId");
const projectName = match?.params?.project;
const projectSlug = match?.params?.project;
const prebuildId = match?.params?.prebuildId;

const [ prebuild, setPrebuild ] = useState<PrebuildWithStatus | undefined>();
Expand All @@ -33,16 +33,20 @@ export default function () {
const [ isCancellingPrebuild, setIsCancellingPrebuild ] = useState<boolean>(false);

useEffect(() => {
if (!teams || !projectName || !prebuildId) {
if (!teams || !projectSlug || !prebuildId) {
return;
}
(async () => {
const projects = (!!team
? await getGitpodService().server.getTeamProjects(team.id)
: await getGitpodService().server.getUserProjects());
const project = projects.find(p => p.name === projectName);

const project = projectSlug && projects.find(
p => p.slug ? p.slug === projectSlug :
p.name === projectSlug);

if (!project) {
console.error(new Error(`Project not found! (teamId: ${team?.id}, projectName: ${projectName})`));
console.error(new Error(`Project not found! (teamId: ${team?.id}, projectName: ${projectSlug})`));
return;
}
const prebuilds = await getGitpodService().server.findPrebuilds({
Expand Down Expand Up @@ -88,7 +92,7 @@ export default function () {
setIsRerunningPrebuild(true);
await getGitpodService().server.triggerPrebuild(prebuild.info.projectId, prebuild.info.branch);
// TODO: Open a Prebuilds page that's specific to `prebuild.info.branch`?
history.push(`/${!!team ? 't/'+team.slug : 'projects'}/${projectName}/prebuilds`);
history.push(`/${!!team ? 't/'+team.slug : 'projects'}/${projectSlug}/prebuilds`);
} catch (error) {
console.error('Could not rerun prebuild', error);
} finally {
Expand Down
9 changes: 6 additions & 3 deletions components/dashboard/src/projects/Prebuilds.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default function () {
const team = getCurrentTeam(location, teams);

const match = useRouteMatch<{ team: string, resource: string }>("/(t/)?:team/:resource");
const projectName = match?.params?.resource;
const projectSlug = match?.params?.resource;

const [project, setProject] = useState<Project | undefined>();

Expand Down Expand Up @@ -70,7 +70,10 @@ export default function () {
? await getGitpodService().server.getTeamProjects(team.id)
: await getGitpodService().server.getUserProjects());

const newProject = projectName && projects.find(p => p.name === projectName);
const newProject = projectSlug && projects.find(
p => p.slug ? p.slug === projectSlug :
p.name === projectSlug);

if (newProject) {
setProject(newProject);
}
Expand Down Expand Up @@ -180,7 +183,7 @@ export default function () {
</Item>
{prebuilds.filter(filter).sort(prebuildSorter).map((p, index) => <Item key={`prebuild-${p.info.id}`} className="grid grid-cols-3">
<ItemField className="flex items-center">
<Link to={`/${!!team ? 't/'+team.slug : 'projects'}/${projectName}/${p.info.id}`} className="cursor-pointer">
<Link to={`/${!!team ? 't/'+team.slug : 'projects'}/${projectSlug}/${p.info.id}`} className="cursor-pointer">
<div className="text-base text-gray-900 dark:text-gray-50 font-medium uppercase mb-1">
<div className="inline-block align-text-bottom mr-2 w-4 h-4">{prebuildStatusIcon(p)}</div>
{prebuildStatusLabel(p)}
Expand Down
10 changes: 5 additions & 5 deletions components/dashboard/src/workspaces/Workspaces.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default function () {
const { teams } = useContext(TeamsContext);
const team = getCurrentTeam(location, teams);
const match = useRouteMatch<{ team: string, resource: string }>("/(t/)?:team/:resource");
const projectName = match?.params?.resource !== 'workspaces' ? match?.params?.resource : undefined;
const projectSlug = match?.params?.resource !== 'workspaces' ? match?.params?.resource : undefined;
const [projects, setProjects] = useState<Project[]>([]);
const [activeWorkspaces, setActiveWorkspaces] = useState<WorkspaceInfo[]>([]);
const [inactiveWorkspaces, setInactiveWorkspaces] = useState<WorkspaceInfo[]>([]);
Expand All @@ -59,7 +59,7 @@ export default function () {

useEffect(() => {
// only show example repos on the global user context
if (!team && !projectName) {
if (!team && !projectSlug) {
getGitpodService().server.getFeaturedRepositories().then(setRepos);
}
(async () => {
Expand All @@ -68,8 +68,8 @@ export default function () {
: await getGitpodService().server.getUserProjects());

let project: Project | undefined = undefined;
if (projectName) {
project = projects.find(p => p.name === projectName);
if (projectSlug) {
project = projects.find(p => p.slug ? p.slug === projectSlug : p.name === projectSlug);
if (project) {
setProjects([project]);
}
Expand All @@ -95,7 +95,7 @@ export default function () {
const hideStartWSModal = () => setIsTemplateModelOpen(false);

const getRecentSuggestions: () => WsStartEntry[] = () => {
if (projectName || team) {
if (projectSlug || team) {
return projects.map(p => {
const remoteUrl = toRemoteURL(p.cloneUrl);
return {
Expand Down
1 change: 0 additions & 1 deletion components/gitpod-db/src/project-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ export interface ProjectDB {
findProjectById(projectId: string): Promise<Project | undefined>;
findProjectByCloneUrl(cloneUrl: string): Promise<Project | undefined>;
findProjectsByCloneUrls(cloneUrls: string[]): Promise<Project[]>;
findProjectByTeamAndName(teamId: string, projectName: string): Promise<Project | undefined>;
findTeamProjects(teamId: string): Promise<Project[]>;
findUserProjects(userId: string): Promise<Project[]>;
storeProject(project: Project): Promise<Project>;
Expand Down
5 changes: 0 additions & 5 deletions components/gitpod-db/src/typeorm/project-db-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,6 @@ export class ProjectDBImpl implements ProjectDB {
return result;
}

public async findProjectByTeamAndName(teamId: string, projectName: string): Promise<Project | undefined> {
const projects = await this.findTeamProjects(teamId);
return projects.find(p => p.name === projectName);
}

public async findTeamProjects(teamId: string): Promise<Project[]> {
const repo = await this.getRepo();
return repo.find({ teamId, markedDeleted: false });
Expand Down
4 changes: 2 additions & 2 deletions components/server/ee/src/workspace/workspace-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { ResponseError } from 'vscode-jsonrpc';
import { ErrorCodes } from '@gitpod/gitpod-protocol/lib/messaging/error';
import { generateWorkspaceID } from '@gitpod/gitpod-protocol/lib/util/generate-workspace-id';
import { HostContextProvider } from '../../../src/auth/host-context-provider';
import { parseRepoUrl } from '../../../src/repohost';
import { RepoURL } from '../../../src/repohost';

@injectable()
export class WorkspaceFactoryEE extends WorkspaceFactory {
Expand Down Expand Up @@ -153,7 +153,7 @@ export class WorkspaceFactoryEE extends WorkspaceFactory {
protected async storePrebuildInfo(ctx: TraceContext, project: Project, pws: PrebuiltWorkspace, ws: Workspace, user: User) {
const span = TraceContext.startSpan("storePrebuildInfo", ctx);
const { userId, teamId, name: projectName, id: projectId } = project;
const parsedUrl = parseRepoUrl(project.cloneUrl);
const parsedUrl = RepoURL.parseRepoUrl(project.cloneUrl);
if (!parsedUrl) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import { Branch, CommitInfo, Repository, User } from "@gitpod/gitpod-protocol";
import { inject, injectable } from 'inversify';
import { parseRepoUrl } from '../repohost/repo-url';
import { RepoURL } from '../repohost/repo-url';
import { RepositoryProvider } from '../repohost/repository-provider';
import { BitbucketApiFactory } from './bitbucket-api-factory';

Expand All @@ -19,7 +19,7 @@ export class BitbucketRepositoryProvider implements RepositoryProvider {
const api = await this.apiFactory.create(user);
const repo = (await api.repositories.get({ workspace: owner, repo_slug: name })).data;
const cloneUrl = repo.links!.clone!.find((x: any) => x.name === "https")!.href!;
const host = parseRepoUrl(cloneUrl)!.host;
const host = RepoURL.parseRepoUrl(cloneUrl)!.host;
const description = repo.description;
const avatarUrl = repo.owner!.links!.avatar!.href;
const webUrl = repo.links!.html!.href;
Expand Down
4 changes: 2 additions & 2 deletions components/server/src/github/github-repository-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { injectable, inject } from 'inversify';
import { User, Repository } from "@gitpod/gitpod-protocol"
import { GitHubGraphQlEndpoint, GitHubRestApi } from "./api";
import { RepositoryProvider } from '../repohost/repository-provider';
import { parseRepoUrl } from '../repohost/repo-url';
import { RepoURL } from '../repohost/repo-url';
import { Branch, CommitInfo } from '@gitpod/gitpod-protocol/src/protocol';

@injectable()
Expand All @@ -20,7 +20,7 @@ export class GithubRepositoryProvider implements RepositoryProvider {
async getRepo(user: User, owner: string, repo: string): Promise<Repository> {
const repository = await this.github.getRepository(user, { owner, repo });
const cloneUrl = repository.clone_url;
const host = parseRepoUrl(cloneUrl)!.host;
const host = RepoURL.parseRepoUrl(cloneUrl)!.host;
const description = repository.description;
const avatarUrl = repository.owner.avatar_url;
const webUrl = repository.html_url;
Expand Down
4 changes: 2 additions & 2 deletions components/server/src/gitlab/gitlab-repository-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { injectable, inject } from 'inversify';
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';
import { RepoURL } from '../repohost/repo-url';

import { log } from '@gitpod/gitpod-protocol/lib/util/logging';

Expand All @@ -26,7 +26,7 @@ export class GitlabRepositoryProvider implements RepositoryProvider {
}
const cloneUrl = response.http_url_to_repo;
const description = response.default_branch;
const host = parseRepoUrl(cloneUrl)!.host;
const host = RepoURL.parseRepoUrl(cloneUrl)!.host;
const avatarUrl = response.owner?.avatar_url || undefined;
const webUrl = response.web_url;
const defaultBranch = response.default_branch
Expand Down
10 changes: 5 additions & 5 deletions components/server/src/projects/projects-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { DBWithTracing, ProjectDB, TeamDB, TracedWorkspaceDB, UserDB, WorkspaceD
import { Branch, CommitContext, PrebuildWithStatus, CreateProjectParams, FindPrebuildsParams, Project, ProjectConfig, User, WorkspaceConfig } from "@gitpod/gitpod-protocol";
import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing";
import { HostContextProvider } from "../auth/host-context-provider";
import { FileProvider, parseRepoUrl } from "../repohost";
import { FileProvider, RepoURL } from "../repohost";
import { log } from '@gitpod/gitpod-protocol/lib/util/logging';
import { ContextParser } from "../workspace/context-parser-service";
import { ConfigInferrer } from "./config-inferrer";
Expand Down Expand Up @@ -46,13 +46,13 @@ export class ProjectsService {
}

protected getRepositoryProvider(project: Project) {
const parsedUrl = parseRepoUrl(project.cloneUrl);
const parsedUrl = RepoURL.parseRepoUrl(project.cloneUrl);
const repositoryProvider = parsedUrl && this.hostContextProvider.get(parsedUrl.host)?.services?.repositoryProvider;
return repositoryProvider;
}

async getBranchDetails(user: User, project: Project, branchName?: string): Promise<Project.BranchDetails[]> {
const parsedUrl = parseRepoUrl(project.cloneUrl);
const parsedUrl = RepoURL.parseRepoUrl(project.cloneUrl);
if (!parsedUrl) {
return [];
}
Expand Down Expand Up @@ -109,7 +109,7 @@ export class ProjectsService {

protected async onDidCreateProject(project: Project) {
let { userId, teamId, cloneUrl } = project;
const parsedUrl = parseRepoUrl(project.cloneUrl);
const parsedUrl = RepoURL.parseRepoUrl(project.cloneUrl);
if ("gitlab.com" === parsedUrl?.host) {
const repositoryService = this.hostContextProvider.get(parsedUrl?.host)?.services?.repositoryService;
if (repositoryService) {
Expand Down Expand Up @@ -140,7 +140,7 @@ export class ProjectsService {
if (!project) {
return [];
}
const parsedUrl = parseRepoUrl(project.cloneUrl);
const parsedUrl = RepoURL.parseRepoUrl(project.cloneUrl);
if (!parsedUrl) {
return [];
}
Expand Down
65 changes: 65 additions & 0 deletions components/server/src/repohost/repo-url.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* 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 * as chai from 'chai';
import { suite, test } from 'mocha-typescript';
import { RepoURL } from './repo-url';

const expect = chai.expect;

@suite
export class RepoUrlTest {

@test public parseRepoUrl() {
const testUrl = RepoURL.parseRepoUrl("https://gitlab.com/hello-group/my-cool-project.git")
expect(testUrl).to.deep.equal({
host: 'gitlab.com',
owner: 'hello-group',
repo: 'my-cool-project'
});
}

@test public parseSubgroupOneLevel() {
const testUrl = RepoURL.parseRepoUrl("https://gitlab.com/hello-group/my-subgroup/my-cool-project.git")
expect(testUrl).to.deep.equal({
host: 'gitlab.com',
owner: 'hello-group/my-subgroup',
repo: 'my-cool-project'
});
}

@test public parseSubgroupTwoLevels() {
const testUrl = RepoURL.parseRepoUrl("https://gitlab.com/hello-group/my-subgroup/my-sub-subgroup/my-cool-project.git")
expect(testUrl).to.deep.equal({
host: 'gitlab.com',
owner: 'hello-group/my-subgroup/my-sub-subgroup',
repo: 'my-cool-project'
});
}

@test public parseSubgroupThreeLevels() {
const testUrl = RepoURL.parseRepoUrl(
"https://gitlab.com/hello-group/my-subgroup/my-sub-subgroup/my-sub-sub-subgroup/my-cool-project.git")
expect(testUrl).to.deep.equal({
host: 'gitlab.com',
owner: 'hello-group/my-subgroup/my-sub-subgroup/my-sub-sub-subgroup',
repo: 'my-cool-project'
});
}

@test public parseSubgroupFourLevels() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: Thanks for adding these tests here, @laushinka! 🌟

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks to @svenefftinge for the reminder to write more tests :)

const testUrl = RepoURL.parseRepoUrl(
"https://gitlab.com/hello-group/my-subgroup/my-sub-subgroup/my-sub-sub-subgroup/my-sub-sub-sub-subgroup/my-cool-project.git")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue(non-blocking): Cross-posting from #5362 (comment) to bring this up again, although sounds ok to leave this out of the scope of this PR, in case we'd like to make plan any action:

Shall we move this group under gitpod-io group?

Cc @corneliusludmann @geropl

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd love to answer but I'm not sure I know what this question or this context means, even after reading the issue. Maybe something I can ask about in the next team call? Also cc-ing @JanKoehnlein in case he has context.

expect(testUrl).to.deep.equal({
host: 'gitlab.com',
owner: 'hello-group/my-subgroup/my-sub-subgroup/my-sub-sub-subgroup/my-sub-sub-sub-subgroup',
repo: 'my-cool-project'
});
}

}

module.exports = new RepoUrlTest()
32 changes: 20 additions & 12 deletions components/server/src/repohost/repo-url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,24 @@


import * as url from 'url';

export function parseRepoUrl(repoUrl: string): { host: string, owner: string, repo: string} | undefined {
const u = url.parse(repoUrl);
const host = u.hostname || '';
const path = u.pathname || '';
const segments = path.split('/').filter(s => !!s); // e.g. [ 'gitpod-io', 'gitpod.git' ]
if (segments.length === 2) {
const owner = segments[0];
const repo = segments[1].endsWith('.git') ? segments[1].slice(0, -4) : segments[1];
return { host, owner, repo };
export namespace RepoURL {
export function parseRepoUrl(repoUrl: string): { host: string, owner: string, repo: string} | undefined {
const u = url.parse(repoUrl);
const host = u.hostname || '';
const path = u.pathname || '';
const segments = path.split('/').filter(s => !!s); // e.g. [ 'gitpod-io', 'gitpod.git' ]
if (segments.length === 2) {
const owner = segments[0];
const repo = segments[1].endsWith('.git') ? segments[1].slice(0, -4) : segments[1];
return { host, owner, repo };
}
if (segments.length > 2) {
const endSegment = segments[segments.length - 1];
const ownerSegments = segments.slice(0, segments.length-1);
const owner = ownerSegments.join("/");
const repo = endSegment.endsWith('.git') ? endSegment.slice(0, -4) : endSegment;
return { host, owner, repo };
}
return undefined;
}
return undefined;
}
}
Loading