Loading docs/gl_objects/merge_requests.rst +4 −0 Original line number Diff line number Diff line Loading @@ -144,6 +144,10 @@ List the changes of a MR:: changes = mr.changes() List issues related to this merge request:: related_issues = mr.related_issues() List issues that will close on merge:: mr.closes_issues() Loading gitlab/v4/objects/merge_requests.py +29 −0 Original line number Diff line number Diff line Loading @@ -197,6 +197,35 @@ class ProjectMergeRequest( assert isinstance(server_data, dict) return server_data @cli.register_custom_action(cls_names="ProjectMergeRequest") @exc.on_http_error(exc.GitlabListError) def related_issues(self, **kwargs: Any) -> RESTObjectList: """List issues related to this merge request." Args: all: If True, return all the items, without pagination per_page: Number of items to retrieve per request page: ID of the page to return (starts with page 1) **kwargs: Extra options to send to the server (e.g. sudo) Raises: GitlabAuthenticationError: If authentication is not correct GitlabListError: If the list could not be retrieved Returns: List of issues """ path = f"{self.manager.path}/{self.encoded_id}/related_issues" data_list = self.manager.gitlab.http_list(path, iterator=True, **kwargs) if TYPE_CHECKING: assert isinstance(data_list, gitlab.GitlabList) manager = ProjectIssueManager(self.manager.gitlab, parent=self.manager._parent) return RESTObjectList(manager, ProjectIssue, data_list) @cli.register_custom_action(cls_names="ProjectMergeRequest") @exc.on_http_error(exc.GitlabListError) def closes_issues(self, **kwargs: Any) -> RESTObjectList: Loading tests/unit/objects/test_merge_requests.py +104 −0 Original line number Diff line number Diff line Loading @@ -9,8 +9,10 @@ import re import pytest import responses from gitlab.base import RESTObjectList from gitlab.v4.objects import ( ProjectDeploymentMergeRequest, ProjectIssue, ProjectMergeRequest, ProjectMergeRequestReviewerDetail, ) Loading Loading @@ -57,6 +59,78 @@ reviewers_content = [ } ] related_issues = [ { "id": 1, "iid": 1, "project_id": 1, "title": "Fake Title for Merge Requests via API", "description": "Something here", "state": "closed", "created_at": "2024-05-14T04:01:40.042Z", "updated_at": "2024-06-13T05:29:13.661Z", "closed_at": "2024-06-13T05:29:13.602Z", "closed_by": { "id": 2, "name": "Sam Bauch", "username": "kenyatta_oconnell", "state": "active", "avatar_url": "https://www.gravatar.com/avatar/956c92487c6f6f7616b536927e22c9a0?s=80&d=identicon", "web_url": "http://gitlab.example.com/kenyatta_oconnell", }, "labels": [ "FakeCategory", "fake:ml", ], "assignees": [ { "id": 2, "name": "Sam Bauch", "username": "kenyatta_oconnell", "state": "active", "avatar_url": "https://www.gravatar.com/avatar/956c92487c6f6f7616b536927e22c9a0?s=80&d=identicon", "web_url": "http://gitlab.example.com/kenyatta_oconnell", } ], "author": { "id": 2, "name": "Sam Bauch", "username": "kenyatta_oconnell", "state": "active", "avatar_url": "https://www.gravatar.com/avatar/956c92487c6f6f7616b536927e22c9a0?s=80&d=identicon", "web_url": "http://gitlab.example.com//kenyatta_oconnell", }, "type": "ISSUE", "assignee": { "id": 4459593, "username": "fakeuser", "name": "Fake User", "state": "active", "locked": False, "avatar_url": "https://example.com/uploads/-/system/user/avatar/4459593/avatar.png", "web_url": "https://example.com/fakeuser", }, "user_notes_count": 9, "merge_requests_count": 0, "upvotes": 1, "downvotes": 0, "due_date": None, "confidential": False, "discussion_locked": None, "issue_type": "issue", "web_url": "https://example.com/fakeorg/fakeproject/-/issues/461536", "time_stats": { "time_estimate": 0, "total_time_spent": 0, "human_time_estimate": None, "human_total_time_spent": None, }, "task_completion_status": {"count": 0, "completed_count": 0}, "weight": None, "blocking_issues_count": 0, } ] @pytest.fixture def resp_list_merge_requests(): Loading Loading @@ -93,6 +167,26 @@ def resp_get_merge_request_reviewers(): yield rsps @pytest.fixture def resp_list_merge_requests_related_issues(): with responses.RequestsMock() as rsps: rsps.add( method=responses.GET, url="http://localhost/api/v4/projects/1/merge_requests/1", json=mr_content, content_type="application/json", status=200, ) rsps.add( method=responses.GET, url="http://localhost/api/v4/projects/1/merge_requests/1/related_issues", json=related_issues, content_type="application/json", status=200, ) yield rsps def test_list_project_merge_requests(project, resp_list_merge_requests): mrs = project.mergerequests.list() assert isinstance(mrs[0], ProjectMergeRequest) Loading @@ -115,3 +209,13 @@ def test_get_merge_request_reviewers(project, resp_get_merge_request_reviewers): assert mr.reviewers[0]["name"] == reviewers_details[0].user["name"] assert reviewers_details[0].state == "unreviewed" assert reviewers_details[0].created_at == "2022-07-27T17:03:27.684Z" def test_list_related_issues(project, resp_list_merge_requests_related_issues): mr = project.mergerequests.get(1) this_mr_related_issues = mr.related_issues() the_issue = next(iter(this_mr_related_issues)) assert isinstance(mr, ProjectMergeRequest) assert isinstance(this_mr_related_issues, RESTObjectList) assert isinstance(the_issue, ProjectIssue) assert the_issue.title == related_issues[0]["title"] Loading
docs/gl_objects/merge_requests.rst +4 −0 Original line number Diff line number Diff line Loading @@ -144,6 +144,10 @@ List the changes of a MR:: changes = mr.changes() List issues related to this merge request:: related_issues = mr.related_issues() List issues that will close on merge:: mr.closes_issues() Loading
gitlab/v4/objects/merge_requests.py +29 −0 Original line number Diff line number Diff line Loading @@ -197,6 +197,35 @@ class ProjectMergeRequest( assert isinstance(server_data, dict) return server_data @cli.register_custom_action(cls_names="ProjectMergeRequest") @exc.on_http_error(exc.GitlabListError) def related_issues(self, **kwargs: Any) -> RESTObjectList: """List issues related to this merge request." Args: all: If True, return all the items, without pagination per_page: Number of items to retrieve per request page: ID of the page to return (starts with page 1) **kwargs: Extra options to send to the server (e.g. sudo) Raises: GitlabAuthenticationError: If authentication is not correct GitlabListError: If the list could not be retrieved Returns: List of issues """ path = f"{self.manager.path}/{self.encoded_id}/related_issues" data_list = self.manager.gitlab.http_list(path, iterator=True, **kwargs) if TYPE_CHECKING: assert isinstance(data_list, gitlab.GitlabList) manager = ProjectIssueManager(self.manager.gitlab, parent=self.manager._parent) return RESTObjectList(manager, ProjectIssue, data_list) @cli.register_custom_action(cls_names="ProjectMergeRequest") @exc.on_http_error(exc.GitlabListError) def closes_issues(self, **kwargs: Any) -> RESTObjectList: Loading
tests/unit/objects/test_merge_requests.py +104 −0 Original line number Diff line number Diff line Loading @@ -9,8 +9,10 @@ import re import pytest import responses from gitlab.base import RESTObjectList from gitlab.v4.objects import ( ProjectDeploymentMergeRequest, ProjectIssue, ProjectMergeRequest, ProjectMergeRequestReviewerDetail, ) Loading Loading @@ -57,6 +59,78 @@ reviewers_content = [ } ] related_issues = [ { "id": 1, "iid": 1, "project_id": 1, "title": "Fake Title for Merge Requests via API", "description": "Something here", "state": "closed", "created_at": "2024-05-14T04:01:40.042Z", "updated_at": "2024-06-13T05:29:13.661Z", "closed_at": "2024-06-13T05:29:13.602Z", "closed_by": { "id": 2, "name": "Sam Bauch", "username": "kenyatta_oconnell", "state": "active", "avatar_url": "https://www.gravatar.com/avatar/956c92487c6f6f7616b536927e22c9a0?s=80&d=identicon", "web_url": "http://gitlab.example.com/kenyatta_oconnell", }, "labels": [ "FakeCategory", "fake:ml", ], "assignees": [ { "id": 2, "name": "Sam Bauch", "username": "kenyatta_oconnell", "state": "active", "avatar_url": "https://www.gravatar.com/avatar/956c92487c6f6f7616b536927e22c9a0?s=80&d=identicon", "web_url": "http://gitlab.example.com/kenyatta_oconnell", } ], "author": { "id": 2, "name": "Sam Bauch", "username": "kenyatta_oconnell", "state": "active", "avatar_url": "https://www.gravatar.com/avatar/956c92487c6f6f7616b536927e22c9a0?s=80&d=identicon", "web_url": "http://gitlab.example.com//kenyatta_oconnell", }, "type": "ISSUE", "assignee": { "id": 4459593, "username": "fakeuser", "name": "Fake User", "state": "active", "locked": False, "avatar_url": "https://example.com/uploads/-/system/user/avatar/4459593/avatar.png", "web_url": "https://example.com/fakeuser", }, "user_notes_count": 9, "merge_requests_count": 0, "upvotes": 1, "downvotes": 0, "due_date": None, "confidential": False, "discussion_locked": None, "issue_type": "issue", "web_url": "https://example.com/fakeorg/fakeproject/-/issues/461536", "time_stats": { "time_estimate": 0, "total_time_spent": 0, "human_time_estimate": None, "human_total_time_spent": None, }, "task_completion_status": {"count": 0, "completed_count": 0}, "weight": None, "blocking_issues_count": 0, } ] @pytest.fixture def resp_list_merge_requests(): Loading Loading @@ -93,6 +167,26 @@ def resp_get_merge_request_reviewers(): yield rsps @pytest.fixture def resp_list_merge_requests_related_issues(): with responses.RequestsMock() as rsps: rsps.add( method=responses.GET, url="http://localhost/api/v4/projects/1/merge_requests/1", json=mr_content, content_type="application/json", status=200, ) rsps.add( method=responses.GET, url="http://localhost/api/v4/projects/1/merge_requests/1/related_issues", json=related_issues, content_type="application/json", status=200, ) yield rsps def test_list_project_merge_requests(project, resp_list_merge_requests): mrs = project.mergerequests.list() assert isinstance(mrs[0], ProjectMergeRequest) Loading @@ -115,3 +209,13 @@ def test_get_merge_request_reviewers(project, resp_get_merge_request_reviewers): assert mr.reviewers[0]["name"] == reviewers_details[0].user["name"] assert reviewers_details[0].state == "unreviewed" assert reviewers_details[0].created_at == "2022-07-27T17:03:27.684Z" def test_list_related_issues(project, resp_list_merge_requests_related_issues): mr = project.mergerequests.get(1) this_mr_related_issues = mr.related_issues() the_issue = next(iter(this_mr_related_issues)) assert isinstance(mr, ProjectMergeRequest) assert isinstance(this_mr_related_issues, RESTObjectList) assert isinstance(the_issue, ProjectIssue) assert the_issue.title == related_issues[0]["title"]