Skip to content

Commit 9e6f705

Browse files
committed
feat(api): add project label promotion
Adds a mixin that allows the /promote endpoint to be called. Signed-off-by: Raimund Hook <raimund.hook@exfo.com>
1 parent a5d8b7f commit 9e6f705

File tree

5 files changed

+98
-2
lines changed

5 files changed

+98
-2
lines changed

gitlab/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ class GitlabProjectDeployKeyError(GitlabOperationError):
111111
pass
112112

113113

114+
class GitlabPromoteError(GitlabOperationError):
115+
pass
116+
117+
114118
class GitlabCancelError(GitlabOperationError):
115119
pass
116120

gitlab/mixins.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -926,3 +926,51 @@ def render(self, link_url: str, image_url: str, **kwargs: Any) -> Dict[str, Any]
926926
if TYPE_CHECKING:
927927
assert not isinstance(result, requests.Response)
928928
return result
929+
930+
931+
class PromoteMixin(_RestObjectBase):
932+
_id_attr: Optional[str]
933+
_attrs: Dict[str, Any]
934+
_module: ModuleType
935+
_parent_attrs: Dict[str, Any]
936+
_updated_attrs: Dict[str, Any]
937+
_update_uses_post: bool = False
938+
manager: base.RESTManager
939+
940+
def _get_update_method(
941+
self,
942+
) -> Callable[..., Union[Dict[str, Any], requests.Response]]:
943+
"""Return the HTTP method to use.
944+
945+
Returns:
946+
object: http_put (default) or http_post
947+
"""
948+
if self._update_uses_post:
949+
http_method = self.manager.gitlab.http_post
950+
else:
951+
http_method = self.manager.gitlab.http_put
952+
return http_method
953+
954+
# @cli.register_custom_action(("ProjectLabel"))
955+
@exc.on_http_error(exc.GitlabPromoteError)
956+
def promote(self, **kwargs: Any) -> Dict[str, Any]:
957+
"""Promote the item.
958+
959+
Args:
960+
**kwargs: Extra options to send to the server (e.g. sudo)
961+
962+
Raises:
963+
GitlabAuthenticationError: If authentication is not correct
964+
GitlabHttpError: When the return code is not 2xx
965+
GitlabParsingError: If the json data could not be parsed
966+
967+
Returns:
968+
dict: The updated object data (*not* a RESTObject)
969+
"""
970+
971+
path = "%s/%s/promote" % (self.manager.path, self.get_id())
972+
http_method = self._get_update_method()
973+
result = http_method(path, **kwargs)
974+
if TYPE_CHECKING:
975+
assert not isinstance(result, requests.Response)
976+
return result

gitlab/v4/objects/labels.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
DeleteMixin,
66
ListMixin,
77
ObjectDeleteMixin,
8+
PromoteMixin,
89
RetrieveMixin,
910
SaveMixin,
1011
SubscribableMixin,
@@ -83,8 +84,10 @@ def delete(self, name, **kwargs):
8384
self.gitlab.http_delete(self.path, query_data={"name": name}, **kwargs)
8485

8586

86-
class ProjectLabel(SubscribableMixin, SaveMixin, ObjectDeleteMixin, RESTObject):
87-
_id_attr = "name"
87+
class ProjectLabel(
88+
PromoteMixin, SubscribableMixin, SaveMixin, ObjectDeleteMixin, RESTObject
89+
):
90+
_id_attr = "id"
8891

8992
# Update without ID, but we need an ID to get from list.
9093
@exc.on_http_error(exc.GitlabUpdateError)

tests/functional/api/test_projects.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,25 @@ def test_project_labels(project):
159159
assert len(project.labels.list()) == 0
160160

161161

162+
def test_project_label_promotion(gl, groupproject):
163+
"""
164+
Label promotion requires the project to be a child of a group (not in a user namespace)
165+
166+
"""
167+
promotedlabel = groupproject.labels.create(
168+
{"name": "promoteme", "color": "#112233"}
169+
)
170+
promotedlabel.promote()
171+
172+
group = gl.groups.get(groupproject.namespace["id"])
173+
174+
grouplabels = group.labels.list()
175+
assert len(grouplabels) == 1
176+
177+
group.labels.delete("promoteme")
178+
assert len(group.labels.list()) == 0
179+
180+
162181
def test_project_milestones(project):
163182
milestone = project.milestones.create({"title": "milestone1"})
164183
assert len(project.milestones.list()) == 1

tests/functional/conftest.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,28 @@ def project(gl):
228228
print(f"Project already deleted: {e}")
229229

230230

231+
@pytest.fixture(scope="module")
232+
def groupproject(gl):
233+
"""Project fixture for project API resource tests that need a group/project hierarchy."""
234+
_id = uuid.uuid4().hex
235+
data = {
236+
"name": f"test-group-{_id}",
237+
"path": f"group-{_id}",
238+
}
239+
group = gl.groups.create(data)
240+
241+
projectname = f"test-project-{_id}"
242+
243+
project = gl.projects.create(name=projectname, namespace_id=group.id)
244+
245+
yield project
246+
247+
try:
248+
group.delete()
249+
except gitlab.exceptions.GitlabDeleteError as e:
250+
print(f"Group already deleted: {e}")
251+
252+
231253
@pytest.fixture(scope="function")
232254
def merge_request(project, wait_for_sidekiq):
233255
"""Fixture used to create a merge_request.

0 commit comments

Comments
 (0)