| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -19,6 +19,7 @@ API examples | |||
| 19 | 19 | gl_objects/deploy_tokens | |
| 20 | 20 | gl_objects/deployments | |
| 21 | 21 | gl_objects/discussions | |
| 22 | + gl_objects/draft_notes | ||
| 22 | 23 | gl_objects/environments | |
| 23 | 24 | gl_objects/events | |
| 24 | 25 | gl_objects/epics | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,58 @@ | |||
| 1 | + .. _draft-notes: | ||
| 2 | + | ||
| 3 | + ########### | ||
| 4 | + Draft Notes | ||
| 5 | + ########### | ||
| 6 | + | ||
| 7 | + Draft notes are pending, unpublished comments on merge requests. | ||
| 8 | + They can be either start a discussion, or be associated with an existing discussion as a reply. | ||
| 9 | + They are viewable only by the author until they are published. | ||
| 10 | + | ||
| 11 | + Reference | ||
| 12 | + --------- | ||
| 13 | + | ||
| 14 | + * v4 API: | ||
| 15 | + | ||
| 16 | + + :class:`gitlab.v4.objects.ProjectMergeRequestDraftNote` | ||
| 17 | + + :class:`gitlab.v4.objects.ProjectMergeRequestDraftNoteManager` | ||
| 18 | + + :attr:`gitlab.v4.objects.ProjectMergeRequest.draft_notes` | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + * GitLab API: https://docs.gitlab.com/ee/api/draft_notes.html | ||
| 22 | + | ||
| 23 | + Examples | ||
| 24 | + -------- | ||
| 25 | + | ||
| 26 | + List all draft notes for a merge request:: | ||
| 27 | + | ||
| 28 | + draft_notes = merge_request.draft_notes.list() | ||
| 29 | + | ||
| 30 | + Get a draft note for a merge request by ID:: | ||
| 31 | + | ||
| 32 | + draft_note = merge_request.draft_notes.get(note_id) | ||
| 33 | + | ||
| 34 | + .. warning:: | ||
| 35 | + | ||
| 36 | + When creating or updating draft notes, you can provide a complex nested ``position`` argument as a dictionary. | ||
| 37 | + Please consult the upstream API documentation linked above for the exact up-to-date attributes. | ||
| 38 | + | ||
| 39 | + Create a draft note for a merge request:: | ||
| 40 | + | ||
| 41 | + draft_note = merge_request.draft_notes.create({'note': 'note content'}) | ||
| 42 | + | ||
| 43 | + Update an existing draft note:: | ||
| 44 | + | ||
| 45 | + draft_note.note = 'updated note content' | ||
| 46 | + draft_note.save() | ||
| 47 | + | ||
| 48 | + Delete an existing draft note:: | ||
| 49 | + | ||
| 50 | + draft_note.delete() | ||
| 51 | + | ||
| 52 | + Publish an existing draft note:: | ||
| 53 | + | ||
| 54 | + draft_note.publish() | ||
| 55 | + | ||
| 56 | + Publish all existing draft notes for a merge request in bulk:: | ||
| 57 | + | ||
| 58 | + merge_request.draft_notes.bulk_publish() | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1061,6 +1061,8 @@ def http_put( | |||
| 1061 | 1061 | raw=raw, | |
| 1062 | 1062 | **kwargs, | |
| 1063 | 1063 | ) | |
| 1064 | + if result.status_code in gitlab.const.NO_JSON_RESPONSE_CODES: | ||
| 1065 | + return result | ||
| 1064 | 1066 | try: | |
| 1065 | 1067 | json_result = result.json() | |
| 1066 | 1068 | if TYPE_CHECKING: | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -18,6 +18,7 @@ | |||
| 18 | 18 | from .deploy_tokens import * | |
| 19 | 19 | from .deployments import * | |
| 20 | 20 | from .discussions import * | |
| 21 | + from .draft_notes import * | ||
| 21 | 22 | from .environments import * | |
| 22 | 23 | from .epics import * | |
| 23 | 24 | from .events import * | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,43 @@ | |||
| 1 | + from typing import Any, cast, Union | ||
| 2 | + | ||
| 3 | + from gitlab.base import RESTManager, RESTObject | ||
| 4 | + from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin | ||
| 5 | + from gitlab.types import RequiredOptional | ||
| 6 | + | ||
| 7 | + __all__ = [ | ||
| 8 | + "ProjectMergeRequestDraftNote", | ||
| 9 | + "ProjectMergeRequestDraftNoteManager", | ||
| 10 | + ] | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + class ProjectMergeRequestDraftNote(ObjectDeleteMixin, SaveMixin, RESTObject): | ||
| 14 | + def publish(self, **kwargs: Any) -> None: | ||
| 15 | + path = f"{self.manager.path}/{self.encoded_id}/publish" | ||
| 16 | + self.manager.gitlab.http_put(path, **kwargs) | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + class ProjectMergeRequestDraftNoteManager(CRUDMixin, RESTManager): | ||
| 20 | + _path = "/projects/{project_id}/merge_requests/{mr_iid}/draft_notes" | ||
| 21 | + _obj_cls = ProjectMergeRequestDraftNote | ||
| 22 | + _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} | ||
| 23 | + _create_attrs = RequiredOptional( | ||
| 24 | + required=("note",), | ||
| 25 | + optional=( | ||
| 26 | + "commit_id", | ||
| 27 | + "in_reply_to_discussion_id", | ||
| 28 | + "position", | ||
| 29 | + "resolve_discussion", | ||
| 30 | + ), | ||
| 31 | + ) | ||
| 32 | + _update_attrs = RequiredOptional(optional=("position",)) | ||
| 33 | + | ||
| 34 | + def get( | ||
| 35 | + self, id: Union[str, int], lazy: bool = False, **kwargs: Any | ||
| 36 | + ) -> ProjectMergeRequestDraftNote: | ||
| 37 | + return cast( | ||
| 38 | + ProjectMergeRequestDraftNote, super().get(id=id, lazy=lazy, **kwargs) | ||
| 39 | + ) | ||
| 40 | + | ||
| 41 | + def bulk_publish(self, **kwargs: Any) -> None: | ||
| 42 | + path = f"{self.path}/bulk_publish" | ||
| 43 | + self.gitlab.http_post(path, **kwargs) | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -28,6 +28,7 @@ | |||
| 28 | 28 | from .award_emojis import ProjectMergeRequestAwardEmojiManager # noqa: F401 | |
| 29 | 29 | from .commits import ProjectCommit, ProjectCommitManager | |
| 30 | 30 | from .discussions import ProjectMergeRequestDiscussionManager # noqa: F401 | |
| 31 | + from .draft_notes import ProjectMergeRequestDraftNoteManager | ||
| 31 | 32 | from .events import ( # noqa: F401 | |
| 32 | 33 | ProjectMergeRequestResourceLabelEventManager, | |
| 33 | 34 | ProjectMergeRequestResourceMilestoneEventManager, | |
@@ -157,6 +158,7 @@ class ProjectMergeRequest( | |||
| 157 | 158 | awardemojis: ProjectMergeRequestAwardEmojiManager | |
| 158 | 159 | diffs: "ProjectMergeRequestDiffManager" | |
| 159 | 160 | discussions: ProjectMergeRequestDiscussionManager | |
| 161 | + draft_notes: ProjectMergeRequestDraftNoteManager | ||
| 160 | 162 | notes: ProjectMergeRequestNoteManager | |
| 161 | 163 | pipelines: ProjectMergeRequestPipelineManager | |
| 162 | 164 | resourcelabelevents: ProjectMergeRequestResourceLabelEventManager | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,175 @@ | |||
| 1 | + """ | ||
| 2 | + GitLab API: https://docs.gitlab.com/ee/api/draft_notes.html | ||
| 3 | + """ | ||
| 4 | + from copy import deepcopy | ||
| 5 | + | ||
| 6 | + import pytest | ||
| 7 | + import responses | ||
| 8 | + | ||
| 9 | + from gitlab.v4.objects import ProjectMergeRequestDraftNote | ||
| 10 | + | ||
| 11 | + draft_note_content = { | ||
| 12 | + "id": 1, | ||
| 13 | + "author_id": 23, | ||
| 14 | + "merge_request_id": 1, | ||
| 15 | + "resolve_discussion": False, | ||
| 16 | + "discussion_id": None, | ||
| 17 | + "note": "Example title", | ||
| 18 | + "commit_id": None, | ||
| 19 | + "line_code": None, | ||
| 20 | + "position": { | ||
| 21 | + "base_sha": None, | ||
| 22 | + "start_sha": None, | ||
| 23 | + "head_sha": None, | ||
| 24 | + "old_path": None, | ||
| 25 | + "new_path": None, | ||
| 26 | + "position_type": "text", | ||
| 27 | + "old_line": None, | ||
| 28 | + "new_line": None, | ||
| 29 | + "line_range": None, | ||
| 30 | + }, | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + | ||
| 34 | + @pytest.fixture() | ||
| 35 | + def resp_list_merge_request_draft_notes(): | ||
| 36 | + with responses.RequestsMock() as rsps: | ||
| 37 | + rsps.add( | ||
| 38 | + method=responses.GET, | ||
| 39 | + url="http://localhost/api/v4/projects/1/merge_requests/1/draft_notes", | ||
| 40 | + json=[draft_note_content], | ||
| 41 | + content_type="application/json", | ||
| 42 | + status=200, | ||
| 43 | + ) | ||
| 44 | + yield rsps | ||
| 45 | + | ||
| 46 | + | ||
| 47 | + @pytest.fixture() | ||
| 48 | + def resp_get_merge_request_draft_note(): | ||
| 49 | + with responses.RequestsMock() as rsps: | ||
| 50 | + rsps.add( | ||
| 51 | + method=responses.GET, | ||
| 52 | + url="http://localhost/api/v4/projects/1/merge_requests/1/draft_notes/1", | ||
| 53 | + json=draft_note_content, | ||
| 54 | + content_type="application/json", | ||
| 55 | + status=200, | ||
| 56 | + ) | ||
| 57 | + yield rsps | ||
| 58 | + | ||
| 59 | + | ||
| 60 | + @pytest.fixture() | ||
| 61 | + def resp_create_merge_request_draft_note(): | ||
| 62 | + with responses.RequestsMock() as rsps: | ||
| 63 | + rsps.add( | ||
| 64 | + method=responses.POST, | ||
| 65 | + url="http://localhost/api/v4/projects/1/merge_requests/1/draft_notes", | ||
| 66 | + json=draft_note_content, | ||
| 67 | + content_type="application/json", | ||
| 68 | + status=201, | ||
| 69 | + ) | ||
| 70 | + yield rsps | ||
| 71 | + | ||
| 72 | + | ||
| 73 | + @pytest.fixture() | ||
| 74 | + def resp_update_merge_request_draft_note(): | ||
| 75 | + updated_content = deepcopy(draft_note_content) | ||
| 76 | + updated_content["note"] = "New title" | ||
| 77 | + | ||
| 78 | + with responses.RequestsMock() as rsps: | ||
| 79 | + rsps.add( | ||
| 80 | + method=responses.PUT, | ||
| 81 | + url="http://localhost/api/v4/projects/1/merge_requests/1/draft_notes/1", | ||
| 82 | + json=updated_content, | ||
| 83 | + content_type="application/json", | ||
| 84 | + status=201, | ||
| 85 | + ) | ||
| 86 | + yield rsps | ||
| 87 | + | ||
| 88 | + | ||
| 89 | + @pytest.fixture() | ||
| 90 | + def resp_delete_merge_request_draft_note(): | ||
| 91 | + with responses.RequestsMock() as rsps: | ||
| 92 | + rsps.add( | ||
| 93 | + method=responses.DELETE, | ||
| 94 | + url="http://localhost/api/v4/projects/1/merge_requests/1/draft_notes/1", | ||
| 95 | + json=draft_note_content, | ||
| 96 | + content_type="application/json", | ||
| 97 | + status=201, | ||
| 98 | + ) | ||
| 99 | + yield rsps | ||
| 100 | + | ||
| 101 | + | ||
| 102 | + @pytest.fixture() | ||
| 103 | + def resp_publish_merge_request_draft_note(): | ||
| 104 | + with responses.RequestsMock() as rsps: | ||
| 105 | + rsps.add( | ||
| 106 | + method=responses.PUT, | ||
| 107 | + url="http://localhost/api/v4/projects/1/merge_requests/1/draft_notes/1/publish", | ||
| 108 | + status=204, | ||
| 109 | + ) | ||
| 110 | + yield rsps | ||
| 111 | + | ||
| 112 | + | ||
| 113 | + @pytest.fixture() | ||
| 114 | + def resp_bulk_publish_merge_request_draft_notes(): | ||
| 115 | + with responses.RequestsMock() as rsps: | ||
| 116 | + rsps.add( | ||
| 117 | + method=responses.POST, | ||
| 118 | + url="http://localhost/api/v4/projects/1/merge_requests/1/draft_notes/bulk_publish", | ||
| 119 | + status=204, | ||
| 120 | + ) | ||
| 121 | + yield rsps | ||
| 122 | + | ||
| 123 | + | ||
| 124 | + def test_list_merge_requests_draft_notes( | ||
| 125 | + project_merge_request, resp_list_merge_request_draft_notes | ||
| 126 | + ): | ||
| 127 | + draft_notes = project_merge_request.draft_notes.list() | ||
| 128 | + assert len(draft_notes) == 1 | ||
| 129 | + assert isinstance(draft_notes[0], ProjectMergeRequestDraftNote) | ||
| 130 | + assert draft_notes[0].note == draft_note_content["note"] | ||
| 131 | + | ||
| 132 | + | ||
| 133 | + def test_get_merge_requests_draft_note( | ||
| 134 | + project_merge_request, resp_get_merge_request_draft_note | ||
| 135 | + ): | ||
| 136 | + draft_note = project_merge_request.draft_notes.get(1) | ||
| 137 | + assert isinstance(draft_note, ProjectMergeRequestDraftNote) | ||
| 138 | + assert draft_note.note == draft_note_content["note"] | ||
| 139 | + | ||
| 140 | + | ||
| 141 | + def test_create_merge_requests_draft_note( | ||
| 142 | + project_merge_request, resp_create_merge_request_draft_note | ||
| 143 | + ): | ||
| 144 | + draft_note = project_merge_request.draft_notes.create({"note": "Example title"}) | ||
| 145 | + assert isinstance(draft_note, ProjectMergeRequestDraftNote) | ||
| 146 | + assert draft_note.note == draft_note_content["note"] | ||
| 147 | + | ||
| 148 | + | ||
| 149 | + def test_update_merge_requests_draft_note( | ||
| 150 | + project_merge_request, resp_update_merge_request_draft_note | ||
| 151 | + ): | ||
| 152 | + draft_note = project_merge_request.draft_notes.get(1, lazy=True) | ||
| 153 | + draft_note.note = "New title" | ||
| 154 | + draft_note.save() | ||
| 155 | + assert draft_note.note == "New title" | ||
| 156 | + | ||
| 157 | + | ||
| 158 | + def test_delete_merge_requests_draft_note( | ||
| 159 | + project_merge_request, resp_delete_merge_request_draft_note | ||
| 160 | + ): | ||
| 161 | + draft_note = project_merge_request.draft_notes.get(1, lazy=True) | ||
| 162 | + draft_note.delete() | ||
| 163 | + | ||
| 164 | + | ||
| 165 | + def test_publish_merge_requests_draft_note( | ||
| 166 | + project_merge_request, resp_publish_merge_request_draft_note | ||
| 167 | + ): | ||
| 168 | + draft_note = project_merge_request.draft_notes.get(1, lazy=True) | ||
| 169 | + draft_note.publish() | ||
| 170 | + | ||
| 171 | + | ||
| 172 | + def test_bulk_publish_merge_requests_draft_notes( | ||
| 173 | + project_merge_request, resp_bulk_publish_merge_request_draft_notes | ||
| 174 | + ): | ||
| 175 | + project_merge_request.draft_notes.bulk_publish() | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -766,6 +766,21 @@ def test_put_request_404(gl): | |||
| 766 | 766 | assert responses.assert_call_count(url, 1) is True | |
| 767 | 767 | ||
| 768 | 768 | ||
| 769 | + @responses.activate | ||
| 770 | + def test_put_request_204(gl): | ||
| 771 | + url = "http://localhost/api/v4/projects" | ||
| 772 | + responses.add( | ||
| 773 | + method=responses.PUT, | ||
| 774 | + url=url, | ||
| 775 | + status=204, | ||
| 776 | + match=helpers.MATCH_EMPTY_QUERY_PARAMS, | ||
| 777 | + ) | ||
| 778 | + | ||
| 779 | + result = gl.http_put("/projects") | ||
| 780 | + assert isinstance(result, requests.Response) | ||
| 781 | + assert responses.assert_call_count(url, 1) is True | ||
| 782 | + | ||
| 783 | + | ||
| 769 | 784 | @responses.activate | |
| 770 | 785 | def test_put_request_invalid_data(gl): | |
| 771 | 786 | url = "http://localhost/api/v4/projects" | |
| Back | FazBrowse Home | New Git URL |
0 commit comments