Skip to content

Commit 1b8145a

Browse files
authored
feat(GitLab): Browse GitLab issues and merge requests (backend) (#7270)
1 parent ea70d3b commit 1b8145a

11 files changed

Lines changed: 852 additions & 7 deletions

File tree

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from integrations.gitlab.client.api import (
2+
fetch_gitlab_projects,
3+
search_gitlab_issues,
4+
search_gitlab_merge_requests,
5+
)
6+
from integrations.gitlab.client.types import (
7+
GitLabIssue,
8+
GitLabMergeRequest,
9+
GitLabPage,
10+
GitLabProject,
11+
)
12+
13+
__all__ = [
14+
"GitLabIssue",
15+
"GitLabMergeRequest",
16+
"GitLabPage",
17+
"GitLabProject",
18+
"fetch_gitlab_projects",
19+
"search_gitlab_issues",
20+
"search_gitlab_merge_requests",
21+
]
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
from collections.abc import Mapping
2+
from typing import Any
3+
4+
import requests
5+
6+
from integrations.gitlab.client.types import (
7+
GitLabIssue,
8+
GitLabMergeRequest,
9+
GitLabPage,
10+
GitLabProject,
11+
T,
12+
)
13+
14+
15+
def _get_from_gitlab_api(
16+
instance_url: str,
17+
access_token: str,
18+
*,
19+
path: str,
20+
params: dict[str, Any] | None = None,
21+
) -> requests.Response:
22+
response = requests.get(
23+
f"{instance_url}/api/v4/{path}",
24+
headers={"PRIVATE-TOKEN": access_token},
25+
params=params,
26+
)
27+
response.raise_for_status()
28+
return response
29+
30+
31+
def _gitlab_page(
32+
results: list[T],
33+
headers: Mapping[str, str],
34+
) -> GitLabPage[T]:
35+
return {
36+
"results": results,
37+
"current_page": int(headers.get("x-page", "1")),
38+
"total_pages": int(headers.get("x-total-pages", "1")),
39+
"total_count": int(headers.get("x-total", str(len(results)))),
40+
}
41+
42+
43+
def fetch_gitlab_projects(
44+
instance_url: str,
45+
access_token: str,
46+
*,
47+
page: int,
48+
page_size: int,
49+
) -> GitLabPage[GitLabProject]:
50+
response = _get_from_gitlab_api(
51+
instance_url,
52+
access_token,
53+
path="projects",
54+
params={
55+
"membership": "true",
56+
"per_page": str(page_size),
57+
"page": str(page),
58+
},
59+
)
60+
61+
results: list[GitLabProject] = [
62+
GitLabProject(
63+
id=p["id"],
64+
name=p["name"],
65+
path_with_namespace=p["path_with_namespace"],
66+
)
67+
for p in response.json()
68+
]
69+
return _gitlab_page(results, response.headers)
70+
71+
72+
def search_gitlab_issues(
73+
instance_url: str,
74+
access_token: str,
75+
*,
76+
gitlab_project_id: int,
77+
page: int,
78+
page_size: int,
79+
search_text: str | None = None,
80+
state: str | None = "opened",
81+
) -> GitLabPage[GitLabIssue]:
82+
query: dict[str, str | int] = {
83+
"per_page": page_size,
84+
"page": page,
85+
}
86+
if search_text:
87+
query["search"] = search_text
88+
if state:
89+
query["state"] = state
90+
91+
response = _get_from_gitlab_api(
92+
instance_url,
93+
access_token,
94+
path=f"projects/{gitlab_project_id}/issues",
95+
params=query,
96+
)
97+
98+
results: list[GitLabIssue] = [
99+
{
100+
"web_url": item["web_url"],
101+
"id": item["id"],
102+
"title": item["title"],
103+
"iid": item["iid"],
104+
"state": item["state"],
105+
}
106+
for item in response.json()
107+
]
108+
return _gitlab_page(results, response.headers)
109+
110+
111+
def search_gitlab_merge_requests(
112+
instance_url: str,
113+
access_token: str,
114+
*,
115+
gitlab_project_id: int,
116+
page: int,
117+
page_size: int,
118+
search_text: str | None = None,
119+
state: str | None = "opened",
120+
) -> GitLabPage[GitLabMergeRequest]:
121+
query: dict[str, str | int] = {
122+
"per_page": page_size,
123+
"page": page,
124+
}
125+
if search_text:
126+
query["search"] = search_text
127+
if state:
128+
query["state"] = state
129+
130+
response = _get_from_gitlab_api(
131+
instance_url,
132+
access_token,
133+
path=f"projects/{gitlab_project_id}/merge_requests",
134+
params=query,
135+
)
136+
137+
results: list[GitLabMergeRequest] = [
138+
{
139+
"web_url": item["web_url"],
140+
"id": item["id"],
141+
"title": item["title"],
142+
"iid": item["iid"],
143+
"state": item["state"],
144+
"merged": item.get("merged_at") is not None,
145+
"draft": item.get("draft", False),
146+
}
147+
for item in response.json()
148+
]
149+
return _gitlab_page(results, response.headers)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from typing import Generic, TypedDict, TypeVar
2+
3+
T = TypeVar("T")
4+
5+
6+
class GitLabProject(TypedDict):
7+
id: int
8+
name: str
9+
path_with_namespace: str
10+
11+
12+
class GitLabIssue(TypedDict):
13+
web_url: str
14+
id: int
15+
title: str
16+
iid: int
17+
state: str
18+
19+
20+
class GitLabMergeRequest(TypedDict):
21+
web_url: str
22+
id: int
23+
title: str
24+
iid: int
25+
state: str
26+
merged: bool
27+
draft: bool
28+
29+
30+
class GitLabPage(TypedDict, Generic[T]):
31+
results: list[T]
32+
current_page: int
33+
total_pages: int
34+
total_count: int

api/integrations/gitlab/serializers.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from typing import Any
22

3+
from rest_framework import serializers
4+
35
from integrations.common.serializers import BaseProjectIntegrationModelSerializer
46
from integrations.gitlab.models import GitLabConfiguration
57

@@ -15,3 +17,14 @@ def to_representation(self, instance: GitLabConfiguration) -> dict[str, Any]:
1517
data = super().to_representation(instance)
1618
data["access_token"] = WRITE_ONLY_PLACEHOLDER
1719
return data
20+
21+
22+
class PaginatedQueryParamsSerializer(serializers.Serializer[None]):
23+
page = serializers.IntegerField(default=1, min_value=1)
24+
page_size = serializers.IntegerField(default=100, min_value=1, max_value=100)
25+
26+
27+
class SearchQueryParamsSerializer(PaginatedQueryParamsSerializer):
28+
gitlab_project_id = serializers.IntegerField()
29+
search_text = serializers.CharField(required=False, allow_blank=True)
30+
state = serializers.CharField(default="opened", required=False)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from integrations.gitlab.views.browse_gitlab import (
2+
BrowseGitLabIssues,
3+
BrowseGitLabMergeRequests,
4+
BrowseGitLabProjects,
5+
)
6+
from integrations.gitlab.views.configuration import GitLabConfigurationViewSet
7+
8+
__all__ = [
9+
"BrowseGitLabIssues",
10+
"BrowseGitLabMergeRequests",
11+
"BrowseGitLabProjects",
12+
"GitLabConfigurationViewSet",
13+
]

0 commit comments

Comments
 (0)