Skip to content
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
21 changes: 21 additions & 0 deletions api/integrations/gitlab/client/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from integrations.gitlab.client.api import (
fetch_gitlab_projects,
search_gitlab_issues,
search_gitlab_merge_requests,
)
from integrations.gitlab.client.types import (
GitLabIssue,
GitLabMergeRequest,
GitLabPage,
GitLabProject,
)

__all__ = [
"GitLabIssue",
"GitLabMergeRequest",
"GitLabPage",
"GitLabProject",
"fetch_gitlab_projects",
"search_gitlab_issues",
"search_gitlab_merge_requests",
]
149 changes: 149 additions & 0 deletions api/integrations/gitlab/client/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
from collections.abc import Mapping
from typing import Any

import requests

from integrations.gitlab.client.types import (
GitLabIssue,
GitLabMergeRequest,
GitLabPage,
GitLabProject,
T,
)


def _get_from_gitlab_api(
instance_url: str,
access_token: str,
*,
path: str,
params: dict[str, Any] | None = None,
) -> requests.Response:
response = requests.get(
f"{instance_url}/api/v4/{path}",
headers={"PRIVATE-TOKEN": access_token},
params=params,
)
response.raise_for_status()
return response


def _gitlab_page(
results: list[T],
headers: Mapping[str, str],
) -> GitLabPage[T]:
return {
"results": results,
"current_page": int(headers.get("x-page", "1")),
"total_pages": int(headers.get("x-total-pages", "1")),
"total_count": int(headers.get("x-total", str(len(results)))),
}


def fetch_gitlab_projects(
instance_url: str,
access_token: str,
*,
page: int,
page_size: int,
) -> GitLabPage[GitLabProject]:
response = _get_from_gitlab_api(
instance_url,
access_token,
path="projects",
params={
"membership": "true",
"per_page": str(page_size),
"page": str(page),
},
)

results: list[GitLabProject] = [
GitLabProject(
id=p["id"],
name=p["name"],
path_with_namespace=p["path_with_namespace"],
)
for p in response.json()
]
return _gitlab_page(results, response.headers)


def search_gitlab_issues(
instance_url: str,
access_token: str,
*,
gitlab_project_id: int,
page: int,
page_size: int,
search_text: str | None = None,
state: str | None = "opened",
) -> GitLabPage[GitLabIssue]:
query: dict[str, str | int] = {
"per_page": page_size,
"page": page,
}
if search_text:
query["search"] = search_text
if state:
query["state"] = state

response = _get_from_gitlab_api(
instance_url,
access_token,
path=f"projects/{gitlab_project_id}/issues",
params=query,
)

results: list[GitLabIssue] = [
{
"web_url": item["web_url"],
"id": item["id"],
"title": item["title"],
"iid": item["iid"],
"state": item["state"],
}
for item in response.json()
]
return _gitlab_page(results, response.headers)


def search_gitlab_merge_requests(
instance_url: str,
access_token: str,
*,
gitlab_project_id: int,
page: int,
page_size: int,
search_text: str | None = None,
state: str | None = "opened",
) -> GitLabPage[GitLabMergeRequest]:
query: dict[str, str | int] = {
"per_page": page_size,
"page": page,
}
if search_text:
query["search"] = search_text
if state:
query["state"] = state

response = _get_from_gitlab_api(
instance_url,
access_token,
path=f"projects/{gitlab_project_id}/merge_requests",
params=query,
)

results: list[GitLabMergeRequest] = [
{
"web_url": item["web_url"],
"id": item["id"],
"title": item["title"],
"iid": item["iid"],
"state": item["state"],
"merged": item.get("merged_at") is not None,
"draft": item.get("draft", False),
}
for item in response.json()
]
return _gitlab_page(results, response.headers)
34 changes: 34 additions & 0 deletions api/integrations/gitlab/client/types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from typing import Generic, TypedDict, TypeVar

T = TypeVar("T")


class GitLabProject(TypedDict):
id: int
name: str
path_with_namespace: str


class GitLabIssue(TypedDict):
web_url: str
id: int
title: str
iid: int
state: str


class GitLabMergeRequest(TypedDict):
web_url: str
id: int
title: str
iid: int
state: str
merged: bool
draft: bool


class GitLabPage(TypedDict, Generic[T]):
results: list[T]
current_page: int
total_pages: int
total_count: int
13 changes: 13 additions & 0 deletions api/integrations/gitlab/serializers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from typing import Any

from rest_framework import serializers

from integrations.common.serializers import BaseProjectIntegrationModelSerializer
from integrations.gitlab.models import GitLabConfiguration

Expand All @@ -15,3 +17,14 @@ def to_representation(self, instance: GitLabConfiguration) -> dict[str, Any]:
data = super().to_representation(instance)
data["access_token"] = WRITE_ONLY_PLACEHOLDER
return data


class PaginatedQueryParamsSerializer(serializers.Serializer[None]):
page = serializers.IntegerField(default=1, min_value=1)
page_size = serializers.IntegerField(default=100, min_value=1, max_value=100)


class SearchQueryParamsSerializer(PaginatedQueryParamsSerializer):
gitlab_project_id = serializers.IntegerField()
search_text = serializers.CharField(required=False, allow_blank=True)
state = serializers.CharField(default="opened", required=False)
13 changes: 13 additions & 0 deletions api/integrations/gitlab/views/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from integrations.gitlab.views.browse_gitlab import (
BrowseGitLabIssues,
BrowseGitLabMergeRequests,
BrowseGitLabProjects,
)
from integrations.gitlab.views.configuration import GitLabConfigurationViewSet

__all__ = [
"BrowseGitLabIssues",
"BrowseGitLabMergeRequests",
"BrowseGitLabProjects",
"GitLabConfigurationViewSet",
]
Loading
Loading