From 288f94ae28e340024c12e2efbe9ce95bc67bf55a Mon Sep 17 00:00:00 2001
From: Christopher Bohn <bohn@unl.edu>
Date: Fri, 13 Sep 2019 18:34:40 -0500
Subject: [PATCH] Expand interface and changed storage of backing object for
 Project.

Testing revealed that if a gitlab.project object was passed to Project's
constructor, it wouldn't store all of the attributes. This might be
unique to how the test obtained the gitlab.project object; even if so,
we need to protect against it.
---
 gitlab_classes.py | 84 ++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 76 insertions(+), 8 deletions(-)

diff --git a/gitlab_classes.py b/gitlab_classes.py
index 87328fd..2be43b1 100644
--- a/gitlab_classes.py
+++ b/gitlab_classes.py
@@ -1,12 +1,9 @@
 from datetime import datetime
+from datetime import timezone
 import gitlab
 from config import Config
 
 
-def gitlab_timestamp_to_datetime(timestamp):
-    return datetime.fromisoformat(timestamp)
-
-
 class Session:
     __instance = None
 
@@ -44,6 +41,10 @@ class User:
     def get_site(self):
         return self.git_user.web_url
 
+    def __repr__(self):
+        username = self.get_username()
+        return f'@{username}'
+
 
 class Issue:
     def __init__(self, issue):
@@ -66,6 +67,18 @@ class Issue:
         """
         return self.git_issue.iid
 
+    def get_title(self):
+        """
+        :return: issue's title
+        """
+        return self.git_issue.title
+
+    def get_description(self):
+        """
+        :return: issue's description
+        """
+        return self.git_issue.description
+
     def get_state(self):
         """
         :return: opened or closed
@@ -115,6 +128,11 @@ class Issue:
         """
         return self.git_issue.web_url
 
+    def __repr__(self):
+        issue_number = self.get_project_issue_id()
+        title = self.get_title()
+        return f'{issue_number}. {title}'
+
     # other git_issue fields:
     # project_id
     # title
@@ -154,11 +172,19 @@ class Project:
         elif isinstance(project, str):          # by path
             self.git_project = Session.get_session().projects.get(project)
         else:
-            self.git_project = project
+            # self.git_project = project        # for some reason, many attributes (including members) might be lost
+            self.git_project = Session.get_session().projects.get(project.id)
 
     @staticmethod
-    def get_projects_by_group(group_id):
-        gitlab_projects = Session.get_session().groups.get(group_id).projects.list(all=True)
+    def get_projects_by_group(group):
+        """
+        :param group: must be either an integer representing the group ID, or a string containing the group's namespace
+        :return: list of projects in the specified group
+        """
+        if isinstance(group, int):              # by group id
+            gitlab_projects = Session.get_session().groups.get(group).projects.list(all=True)
+        else:  # isinstance(group, str):        # by path
+            gitlab_projects = Session.get_session().groups.get(group).projects.list(all=True)
         projects = []
         for project in gitlab_projects:
             projects.append(Project(project))
@@ -238,6 +264,15 @@ class Project:
         """
         return User(self.git_project.creator_id)
 
+    def get_created_at(self):
+        """
+        :return: an "aware" datetime object representing the creation date/time
+        """
+        created_at = self.git_project.created_at
+        if created_at[-1] in ('z', 'Z'):
+            created_at = created_at[:-1] + '+00:00'
+        return datetime.fromisoformat(created_at)
+
     def get_users(self):
         """
         :return: List of User objects representing the project's members (not including inherited members)
@@ -275,9 +310,11 @@ class Project:
         gitlab_issue = self.git_project.issues.create({'title': title, 'description': description})
         return Issue(gitlab_issue)
 
+    def __repr__(self):
+        return self.get_name_with_namespace()
+
     # other git_project fields:
     # description
-    # created_at
     # default_branch
     # tag_list
     # http_url_to_repo      https URL to clone repository
@@ -323,3 +360,34 @@ class Project:
     # auto_devops_enabled
     # auto_devops_deploy_strategy
     # permissions
+
+
+if __name__ == '__main__':
+    namespace = 'csce_361/sandbox'
+    test_projects = Project.get_projects_by_group(namespace)
+    print('All projects in sandbox:')
+    for test_project in test_projects:
+        print(test_project)
+    print('Selecting second project. Here are the members:')
+    test_project = test_projects[1]
+    members = test_project.get_users()
+    for member in members:
+        print(member)
+    print('Here are ALL the members:')
+    members = test_project.get_all_users()
+    for member in members:
+        print(member)
+    print('Here are the issues:')
+    test_issues = test_project.get_issues()
+    for test_issue in test_issues:
+        creation = test_issue.get_created_at()
+        print(f'{test_issue}\tcreated at {creation}.')
+    test_projects = Project.get_projects_by_keyword('csce361-homework')
+    number_of_projects = len(test_projects)
+    print(f'retrieved {number_of_projects} projects matching \'csce361-homework\'')
+    start_date = datetime(2019, 8, 1, tzinfo=timezone.utc)
+    test_projects = list(filter(lambda p: p.get_created_at() > start_date, test_projects))
+    new_number_of_projects = len(test_projects)
+    print(f'after culling, there are {new_number_of_projects} projects that were created in/after August 2019')
+    print(f'including {test_projects[0]} created by {test_projects[0].get_creator()} at '
+          f'{test_projects[0].get_created_at()}.')
-- 
GitLab