Skip to content

Commit 76715a5

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 76715a5

File tree

6 files changed

+100
-1
lines changed

6 files changed

+100
-1
lines changed

docs/gl_objects/labels.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ Update a label for a project::
3636
label.color = '#112233'
3737
label.save()
3838

39+
Promote a project label to a group label::
40+
41+
label.promote()
42+
3943
Delete a label for a project::
4044

4145
project.labels.delete(label_id)

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: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -926,3 +926,50 @@ 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+
@exc.on_http_error(exc.GitlabPromoteError)
955+
def promote(self, **kwargs: Any) -> Dict[str, Any]:
956+
"""Promote the item.
957+
958+
Args:
959+
**kwargs: Extra options to send to the server (e.g. sudo)
960+
961+
Raises:
962+
GitlabAuthenticationError: If authentication is not correct
963+
GitlabHttpError: When the return code is not 2xx
964+
GitlabParsingError: If the json data could not be parsed
965+
966+
Returns:
967+
dict: The updated object data (*not* a RESTObject)
968+
"""
969+
970+
path = "%s/%s/promote" % (self.manager.path, self.id)
971+
http_method = self._get_update_method()
972+
result = http_method(path, **kwargs)
973+
if TYPE_CHECKING:
974+
assert not isinstance(result, requests.Response)
975+
return result

gitlab/v4/objects/labels.py

Lines changed: 4 additions & 1 deletion
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,7 +84,9 @@ 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+
class ProjectLabel(
88+
PromoteMixin, SubscribableMixin, SaveMixin, ObjectDeleteMixin, RESTObject
89+
):
8790
_id_attr = "name"
8891

8992
# Update without ID, but we need an ID to get from list.

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)