From 6d7c88a1fe401d271a34df80943634652195b140 Mon Sep 17 00:00:00 2001 From: Raimund Hook Date: Fri, 24 Sep 2021 10:22:27 +0100 Subject: [PATCH] feat(api): add project label promotion Adds a mixin that allows the /promote endpoint to be called. Signed-off-by: Raimund Hook --- docs/gl_objects/labels.rst | 4 +++ gitlab/exceptions.py | 4 +++ gitlab/mixins.py | 47 +++++++++++++++++++++++++++ gitlab/v4/objects/labels.py | 5 ++- tests/functional/api/test_projects.py | 24 ++++++++++++++ 5 files changed, 83 insertions(+), 1 deletion(-) diff --git a/docs/gl_objects/labels.rst b/docs/gl_objects/labels.rst index a4667aac0..9a955dd89 100644 --- a/docs/gl_objects/labels.rst +++ b/docs/gl_objects/labels.rst @@ -36,6 +36,10 @@ Update a label for a project:: label.color = '#112233' label.save() +Promote a project label to a group label:: + + label.promote() + Delete a label for a project:: project.labels.delete(label_id) diff --git a/gitlab/exceptions.py b/gitlab/exceptions.py index 6f2d4c4ae..66b1ee091 100644 --- a/gitlab/exceptions.py +++ b/gitlab/exceptions.py @@ -111,6 +111,10 @@ class GitlabProjectDeployKeyError(GitlabOperationError): pass +class GitlabPromoteError(GitlabOperationError): + pass + + class GitlabCancelError(GitlabOperationError): pass diff --git a/gitlab/mixins.py b/gitlab/mixins.py index 0c2cd949b..62ff6dcfa 100644 --- a/gitlab/mixins.py +++ b/gitlab/mixins.py @@ -926,3 +926,50 @@ def render(self, link_url: str, image_url: str, **kwargs: Any) -> Dict[str, Any] if TYPE_CHECKING: assert not isinstance(result, requests.Response) return result + + +class PromoteMixin(_RestObjectBase): + _id_attr: Optional[str] + _attrs: Dict[str, Any] + _module: ModuleType + _parent_attrs: Dict[str, Any] + _updated_attrs: Dict[str, Any] + _update_uses_post: bool = False + manager: base.RESTManager + + def _get_update_method( + self, + ) -> Callable[..., Union[Dict[str, Any], requests.Response]]: + """Return the HTTP method to use. + + Returns: + object: http_put (default) or http_post + """ + if self._update_uses_post: + http_method = self.manager.gitlab.http_post + else: + http_method = self.manager.gitlab.http_put + return http_method + + @exc.on_http_error(exc.GitlabPromoteError) + def promote(self, **kwargs: Any) -> Dict[str, Any]: + """Promote the item. + + Args: + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabPromoteError: If the item could not be promoted + GitlabParsingError: If the json data could not be parsed + + Returns: + dict: The updated object data (*not* a RESTObject) + """ + + path = "%s/%s/promote" % (self.manager.path, self.id) + http_method = self._get_update_method() + result = http_method(path, **kwargs) + if TYPE_CHECKING: + assert not isinstance(result, requests.Response) + return result diff --git a/gitlab/v4/objects/labels.py b/gitlab/v4/objects/labels.py index 544c3cd90..99da06a79 100644 --- a/gitlab/v4/objects/labels.py +++ b/gitlab/v4/objects/labels.py @@ -5,6 +5,7 @@ DeleteMixin, ListMixin, ObjectDeleteMixin, + PromoteMixin, RetrieveMixin, SaveMixin, SubscribableMixin, @@ -83,7 +84,9 @@ def delete(self, name, **kwargs): self.gitlab.http_delete(self.path, query_data={"name": name}, **kwargs) -class ProjectLabel(SubscribableMixin, SaveMixin, ObjectDeleteMixin, RESTObject): +class ProjectLabel( + PromoteMixin, SubscribableMixin, SaveMixin, ObjectDeleteMixin, RESTObject +): _id_attr = "name" # Update without ID, but we need an ID to get from list. diff --git a/tests/functional/api/test_projects.py b/tests/functional/api/test_projects.py index ba8e25be9..3da9d2b0e 100644 --- a/tests/functional/api/test_projects.py +++ b/tests/functional/api/test_projects.py @@ -1,3 +1,5 @@ +import uuid + import pytest import gitlab @@ -159,6 +161,28 @@ def test_project_labels(project): assert len(project.labels.list()) == 0 +def test_project_label_promotion(gl, group): + """ + Label promotion requires the project to be a child of a group (not in a user namespace) + + """ + _id = uuid.uuid4().hex + data = { + "name": f"test-project-{_id}", + "namespace_id": group.id, + } + project = gl.projects.create(data) + + label_name = "promoteme" + promoted_label = project.labels.create({"name": label_name, "color": "#112233"}) + promoted_label.promote() + + assert any(label.name == label_name for label in group.labels.list()) + + group.labels.delete(label_name) + assert not any(label.name == label_name for label in group.labels.list()) + + def test_project_milestones(project): milestone = project.milestones.create({"title": "milestone1"}) assert len(project.milestones.list()) == 1