summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/api-objects.rst1
-rw-r--r--docs/gl_objects/epics.rst79
-rw-r--r--gitlab/v4/objects.py78
-rwxr-xr-xtools/ee-test.py23
4 files changed, 180 insertions, 1 deletions
diff --git a/docs/api-objects.rst b/docs/api-objects.rst
index 4e7961d..0cc5014 100644
--- a/docs/api-objects.rst
+++ b/docs/api-objects.rst
@@ -18,6 +18,7 @@ API examples
gl_objects/discussions
gl_objects/environments
gl_objects/events
+ gl_objects/epics
gl_objects/features
gl_objects/geo_nodes
gl_objects/groups
diff --git a/docs/gl_objects/epics.rst b/docs/gl_objects/epics.rst
new file mode 100644
index 0000000..2b1e23e
--- /dev/null
+++ b/docs/gl_objects/epics.rst
@@ -0,0 +1,79 @@
+#####
+Epics
+#####
+
+Epics
+=====
+
+Reference
+---------
+
+* v4 API:
+
+ + :class:`gitlab.v4.objects.GroupEpic`
+ + :class:`gitlab.v4.objects.GroupEpicManager`
+ + :attr:`gitlab.Gitlab.Group.epics`
+
+* GitLab API: https://docs.gitlab.com/ee/api/epics.html (EE feature)
+
+Examples
+--------
+
+List the epics for a group::
+
+ epics = groups.epics.list()
+
+Get a single epic for a group::
+
+ epic = group.epics.get(epic_iid)
+
+Create an epic for a group::
+
+ epic = group.epics.create({'title': 'My Epic'})
+
+Edit an epic::
+
+ epic.title = 'New title'
+ epic.labels = ['label1', 'label2']
+ epic.save()
+
+Delete an epic::
+
+ epic.delete()
+
+Epics issues
+============
+
+Reference
+---------
+
+* v4 API:
+
+ + :class:`gitlab.v4.objects.GroupEpicIssue`
+ + :class:`gitlab.v4.objects.GroupEpicIssueManager`
+ + :attr:`gitlab.Gitlab.GroupEpic.issues`
+
+* GitLab API: https://docs.gitlab.com/ee/api/epic_issues.html (EE feature)
+
+Examples
+--------
+
+List the issues associated with an issue::
+
+ ei = epic.issues.list()
+
+Associate an issue with an epic::
+
+ # use the issue id, not its iid
+ ei = epic.issues.create({'issue_id': 4})
+
+Move an issue in the list::
+
+ ei.move_before_id = epic_issue_id_1
+ # or
+ ei.move_after_id = epic_issue_id_2
+ ei.save()
+
+Delete an issue association::
+
+ ei.delete()
diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py
index 7231919..3e16bac 100644
--- a/gitlab/v4/objects.py
+++ b/gitlab/v4/objects.py
@@ -602,6 +602,83 @@ class GroupCustomAttributeManager(RetrieveMixin, SetMixin, DeleteMixin,
_from_parent_attrs = {'group_id': 'id'}
+class GroupEpicIssue(ObjectDeleteMixin, SaveMixin, RESTObject):
+ _id_attr = 'epic_issue_id'
+
+ def save(self, **kwargs):
+ """Save the changes made to the object to the server.
+
+ The object is updated to match what the server returns.
+
+ Args:
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raise:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabUpdateError: If the server cannot perform the request
+ """
+ updated_data = self._get_updated_data()
+ # Nothing to update. Server fails if sent an empty dict.
+ if not updated_data:
+ return
+
+ # call the manager
+ obj_id = self.get_id()
+ self.manager.update(obj_id, updated_data, **kwargs)
+
+
+class GroupEpicIssueManager(ListMixin, CreateMixin, UpdateMixin, DeleteMixin,
+ RESTManager):
+ _path = '/groups/%(group_id)s/epics/%(epic_iid)s/issues'
+ _obj_cls = GroupEpicIssue
+ _from_parent_attrs = {'group_id': 'group_id', 'epic_iid': 'iid'}
+ _create_attrs = (('issue_id',), tuple())
+ _update_attrs = (tuple(), ('move_before_id', 'move_after_id'))
+
+ @exc.on_http_error(exc.GitlabCreateError)
+ def create(self, data, **kwargs):
+ """Create a new object.
+
+ Args:
+ data (dict): Parameters to send to the server to create the
+ resource
+ **kwargs: Extra data to send to the Gitlab server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabCreateError: If the server cannot perform the request
+
+ Returns:
+ RESTObject: A new instance of the manage object class build with
+ the data sent by the server
+ """
+ CreateMixin._check_missing_create_attrs(self, data)
+ path = '%s/%s' % (self.path, data.pop('issue_id'))
+ server_data = self.gitlab.http_post(path, **kwargs)
+ # The epic_issue_id attribute doesn't exist when creating the resource,
+ # but is used everywhere elese. Let's create it to be consistent client
+ # side
+ server_data['epic_issue_id'] = server_data['id']
+ return self._obj_cls(self, server_data)
+
+
+class GroupEpic(ObjectDeleteMixin, SaveMixin, RESTObject):
+ _id_attr = 'iid'
+ _managers = (('issues', 'GroupEpicIssueManager'),)
+
+
+class GroupEpicManager(CRUDMixin, RESTManager):
+ _path = '/groups/%(group_id)s/epics'
+ _obj_cls = GroupEpic
+ _from_parent_attrs = {'group_id': 'id'}
+ _list_filters = ('author_id', 'labels', 'order_by', 'sort', 'search')
+ _create_attrs = (('title',),
+ ('labels', 'description', 'start_date', 'end_date'))
+ _update_attrs = (tuple(), ('title', 'labels', 'description', 'start_date',
+ 'end_date'))
+ _types = {'labels': types.ListAttribute}
+
+
class GroupIssue(RESTObject):
pass
@@ -762,6 +839,7 @@ class Group(SaveMixin, ObjectDeleteMixin, RESTObject):
('badges', 'GroupBadgeManager'),
('boards', 'GroupBoardManager'),
('customattributes', 'GroupCustomAttributeManager'),
+ ('epics', 'GroupEpicManager'),
('issues', 'GroupIssueManager'),
('members', 'GroupMemberManager'),
('milestones', 'GroupMilestoneManager'),
diff --git a/tools/ee-test.py b/tools/ee-test.py
index b171e68..bc98cc6 100755
--- a/tools/ee-test.py
+++ b/tools/ee-test.py
@@ -8,6 +8,7 @@ P2 = 'root/project2'
MR_P1 = 1
I_P1 = 1
I_P2 = 1
+EPIC_ISSUES = [4, 5]
G1 = 'group1'
LDAP_CN = 'app1'
LDAP_PROVIDER = 'ldapmain'
@@ -83,7 +84,7 @@ group1.ldap_sync()
group1.delete_ldap_group_link(LDAP_CN)
end_log()
-start_log('Boards')
+start_log('boards')
# bit of cleanup just in case
for board in project1.boards.list():
if board.name == 'testboard':
@@ -121,3 +122,23 @@ try:
except Exception as e:
assert('The license key is invalid.' in e.error_message)
end_log()
+
+start_log('epics')
+epic = group1.epics.create({'title': 'Test epic'})
+epic.title = 'Fixed title'
+epic.labels = ['label1', 'label2']
+epic.save()
+epic = group1.epics.get(epic.iid)
+assert(epic.title == 'Fixed title')
+assert(len(group1.epics.list()))
+
+# issues
+assert(not epic.issues.list())
+for i in EPIC_ISSUES:
+ epic.issues.create({'issue_id': i})
+assert(len(EPIC_ISSUES) == len(epic.issues.list()))
+for ei in epic.issues.list():
+ ei.delete()
+
+epic.delete()
+end_log()