Skip to content

Commit ba90e30

Browse files
author
Gauvain Pocentek
committed
Add support for epics API (EE)
Fixes python-gitlab#525
1 parent b2cb700 commit ba90e30

File tree

4 files changed

+180
-1
lines changed

4 files changed

+180
-1
lines changed

docs/api-objects.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ API examples
1818
gl_objects/discussions
1919
gl_objects/environments
2020
gl_objects/events
21+
gl_objects/epics
2122
gl_objects/features
2223
gl_objects/geo_nodes
2324
gl_objects/groups

docs/gl_objects/epics.rst

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#####
2+
Epics
3+
#####
4+
5+
Epics
6+
=====
7+
8+
Reference
9+
---------
10+
11+
* v4 API:
12+
13+
+ :class:`gitlab.v4.objects.GroupEpic`
14+
+ :class:`gitlab.v4.objects.GroupEpicManager`
15+
+ :attr:`gitlab.Gitlab.Group.epics`
16+
17+
* GitLab API: https://docs.gitlab.com/ee/api/epics.html (EE feature)
18+
19+
Examples
20+
--------
21+
22+
List the epics for a group::
23+
24+
epics = groups.epics.list()
25+
26+
Get a single epic for a group::
27+
28+
epic = group.epics.get(epic_iid)
29+
30+
Create an epic for a group::
31+
32+
epic = group.epics.create({'title': 'My Epic'})
33+
34+
Edit an epic::
35+
36+
epic.title = 'New title'
37+
epic.labels = ['label1', 'label2']
38+
epic.save()
39+
40+
Delete an epic::
41+
42+
epic.delete()
43+
44+
Epics issues
45+
============
46+
47+
Reference
48+
---------
49+
50+
* v4 API:
51+
52+
+ :class:`gitlab.v4.objects.GroupEpicIssue`
53+
+ :class:`gitlab.v4.objects.GroupEpicIssueManager`
54+
+ :attr:`gitlab.Gitlab.GroupEpic.issues`
55+
56+
* GitLab API: https://docs.gitlab.com/ee/api/epic_issues.html (EE feature)
57+
58+
Examples
59+
--------
60+
61+
List the issues associated with an issue::
62+
63+
ei = epic.issues.list()
64+
65+
Associate an issue with an epic::
66+
67+
# use the issue id, not its iid
68+
ei = epic.issues.create({'issue_id': 4})
69+
70+
Move an issue in the list::
71+
72+
ei.move_before_id = epic_issue_id_1
73+
# or
74+
ei.move_after_id = epic_issue_id_2
75+
ei.save()
76+
77+
Delete an issue association::
78+
79+
ei.delete()

gitlab/v4/objects.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,83 @@ class GroupCustomAttributeManager(RetrieveMixin, SetMixin, DeleteMixin,
602602
_from_parent_attrs = {'group_id': 'id'}
603603

604604

605+
class GroupEpicIssue(ObjectDeleteMixin, SaveMixin, RESTObject):
606+
_id_attr = 'epic_issue_id'
607+
608+
def save(self, **kwargs):
609+
"""Save the changes made to the object to the server.
610+
611+
The object is updated to match what the server returns.
612+
613+
Args:
614+
**kwargs: Extra options to send to the server (e.g. sudo)
615+
616+
Raise:
617+
GitlabAuthenticationError: If authentication is not correct
618+
GitlabUpdateError: If the server cannot perform the request
619+
"""
620+
updated_data = self._get_updated_data()
621+
# Nothing to update. Server fails if sent an empty dict.
622+
if not updated_data:
623+
return
624+
625+
# call the manager
626+
obj_id = self.get_id()
627+
self.manager.update(obj_id, updated_data, **kwargs)
628+
629+
630+
class GroupEpicIssueManager(ListMixin, CreateMixin, UpdateMixin, DeleteMixin,
631+
RESTManager):
632+
_path = '/groups/%(group_id)s/epics/%(epic_iid)s/issues'
633+
_obj_cls = GroupEpicIssue
634+
_from_parent_attrs = {'group_id': 'group_id', 'epic_iid': 'iid'}
635+
_create_attrs = (('issue_id',), tuple())
636+
_update_attrs = (tuple(), ('move_before_id', 'move_after_id'))
637+
638+
@exc.on_http_error(exc.GitlabCreateError)
639+
def create(self, data, **kwargs):
640+
"""Create a new object.
641+
642+
Args:
643+
data (dict): Parameters to send to the server to create the
644+
resource
645+
**kwargs: Extra data to send to the Gitlab server (e.g. sudo)
646+
647+
Raises:
648+
GitlabAuthenticationError: If authentication is not correct
649+
GitlabCreateError: If the server cannot perform the request
650+
651+
Returns:
652+
RESTObject: A new instance of the manage object class build with
653+
the data sent by the server
654+
"""
655+
CreateMixin._check_missing_create_attrs(self, data)
656+
path = '%s/%s' % (self.path, data.pop('issue_id'))
657+
server_data = self.gitlab.http_post(path, **kwargs)
658+
# The epic_issue_id attribute doesn't exist when creating the resource,
659+
# but is used everywhere elese. Let's create it to be consistent client
660+
# side
661+
server_data['epic_issue_id'] = server_data['id']
662+
return self._obj_cls(self, server_data)
663+
664+
665+
class GroupEpic(ObjectDeleteMixin, SaveMixin, RESTObject):
666+
_id_attr = 'iid'
667+
_managers = (('issues', 'GroupEpicIssueManager'),)
668+
669+
670+
class GroupEpicManager(CRUDMixin, RESTManager):
671+
_path = '/groups/%(group_id)s/epics'
672+
_obj_cls = GroupEpic
673+
_from_parent_attrs = {'group_id': 'id'}
674+
_list_filters = ('author_id', 'labels', 'order_by', 'sort', 'search')
675+
_create_attrs = (('title',),
676+
('labels', 'description', 'start_date', 'end_date'))
677+
_update_attrs = (tuple(), ('title', 'labels', 'description', 'start_date',
678+
'end_date'))
679+
_types = {'labels': types.ListAttribute}
680+
681+
605682
class GroupIssue(RESTObject):
606683
pass
607684

@@ -762,6 +839,7 @@ class Group(SaveMixin, ObjectDeleteMixin, RESTObject):
762839
('badges', 'GroupBadgeManager'),
763840
('boards', 'GroupBoardManager'),
764841
('customattributes', 'GroupCustomAttributeManager'),
842+
('epics', 'GroupEpicManager'),
765843
('issues', 'GroupIssueManager'),
766844
('members', 'GroupMemberManager'),
767845
('milestones', 'GroupMilestoneManager'),

tools/ee-test.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
MR_P1 = 1
99
I_P1 = 1
1010
I_P2 = 1
11+
EPIC_ISSUES = [4, 5]
1112
G1 = 'group1'
1213
LDAP_CN = 'app1'
1314
LDAP_PROVIDER = 'ldapmain'
@@ -83,7 +84,7 @@ def end_log():
8384
group1.delete_ldap_group_link(LDAP_CN)
8485
end_log()
8586

86-
start_log('Boards')
87+
start_log('boards')
8788
# bit of cleanup just in case
8889
for board in project1.boards.list():
8990
if board.name == 'testboard':
@@ -121,3 +122,23 @@ def end_log():
121122
except Exception as e:
122123
assert('The license key is invalid.' in e.error_message)
123124
end_log()
125+
126+
start_log('epics')
127+
epic = group1.epics.create({'title': 'Test epic'})
128+
epic.title = 'Fixed title'
129+
epic.labels = ['label1', 'label2']
130+
epic.save()
131+
epic = group1.epics.get(epic.iid)
132+
assert(epic.title == 'Fixed title')
133+
assert(len(group1.epics.list()))
134+
135+
# issues
136+
assert(not epic.issues.list())
137+
for i in EPIC_ISSUES:
138+
epic.issues.create({'issue_id': i})
139+
assert(len(EPIC_ISSUES) == len(epic.issues.list()))
140+
for ei in epic.issues.list():
141+
ei.delete()
142+
143+
epic.delete()
144+
end_log()

0 commit comments

Comments
 (0)