From 8d70ad09b2e37d93c47f13e40ff530eb2a985578 Mon Sep 17 00:00:00 2001
From: Christopher Bohn <bohn@unl.edu>
Date: Sun, 24 Nov 2019 22:24:22 -0600
Subject: [PATCH] Prepared code to track updates to project issues.
---
Timeline.py | 77 +++++++++++++++++++++++++++++++++++++++++++
api/gitlab_classes.py | 38 +++++++++++++--------
prep_assignment.py | 24 +++++++++++++-
3 files changed, 125 insertions(+), 14 deletions(-)
create mode 100644 Timeline.py
diff --git a/Timeline.py b/Timeline.py
new file mode 100644
index 0000000..411cfa9
--- /dev/null
+++ b/Timeline.py
@@ -0,0 +1,77 @@
+import json
+from pathlib import Path
+
+from api.gitlab_classes import *
+from course import Course
+
+
+class Timeline:
+ # noinspection PyShadowingNames
+ def __init__(self, project, filename):
+ self.project = project
+ self.save_file = filename
+ if Path(self.save_file).exists():
+ with open(self.save_file, mode='r') as json_file:
+ this_dict = json.load(json_file)
+ self.start_time = this_dict['start_time']
+ self.issues = this_dict['issues']
+ self.observational_periods = this_dict['observational_periods']
+ else:
+ self.project = project
+ # self.commits = project.get_commits() # we'll worry about commits later
+ self.start_time = datetime.now().isoformat()
+ self.observational_periods = []
+ self.issues = []
+ self.observational_periods.append({'from': datetime.now().isoformat(), 'to': datetime.now().isoformat()})
+ self.issues = list(map(lambda issue: self.update_issue_timeline(issue), project.get_issues()))
+
+ def save(self):
+ this_dict = {'project': self.project.get_path_with_namespace(),
+ 'issues': self.issues,
+ 'start_time': self.start_time,
+ 'observational_periods': self.observational_periods
+ }
+ path = Path(self.save_file)
+ backup_path = Path(f'{self.save_file}.bk')
+ if path.exists():
+ path.rename(backup_path)
+ with open(self.save_file, mode='w') as json_file:
+ json.dump(this_dict, json_file, indent=4)
+
+ def update_issue_timeline(self, issue):
+ possible_issue_timeline = list(filter(lambda i: i['number'] == issue.get_project_issue_id(), self.issues))
+ if len(possible_issue_timeline) == 0:
+ issue_timeline = {'number': issue.get_project_issue_id(),
+ 'opened': issue.get_created_at().isoformat(),
+ 'events': []}
+ old_labels = set()
+ else:
+ issue_timeline = possible_issue_timeline[0]
+ old_labels = set(issue_timeline['current_labels'])
+ new_labels = issue.get_labels()
+ all_labels = old_labels.union(new_labels)
+ for label in all_labels:
+ if label in old_labels.difference(new_labels):
+ issue_timeline['events'].append(('Remove Label', label, datetime.now().isoformat()))
+ if label in new_labels.difference(old_labels):
+ issue_timeline['events'].append(('Add Label', label, datetime.now().isoformat()))
+ issue_timeline['current_labels'] = list(issue.get_labels())
+ if issue.get_closed_at() is not None:
+ issue_timeline['closed'] = issue.get_closed_at().isoformat()
+ else:
+ issue_timeline['closed'] = None
+ return issue_timeline
+
+
+if __name__ == '__main__':
+ # A handy project to work from for now
+ projects = GitlabProject.get_projects_by_group(Course.gitlab_namespace)
+ project = list(filter(lambda p: p.get_name() == 'Chess 4', projects))[0]
+ print(project)
+ timeline = Timeline(project, 'chess4.json')
+ # print(len(project.get_commits(branch_name = 'staging')))
+ # print(len(project.git_project.commits.list(all=True)))
+ timeline.save()
+ new_timeline = Timeline(project, 'chess4.json')
+ print(new_timeline.project)
+ new_timeline.save()
diff --git a/api/gitlab_classes.py b/api/gitlab_classes.py
index 4c2fd4e..9b12b76 100644
--- a/api/gitlab_classes.py
+++ b/api/gitlab_classes.py
@@ -1,6 +1,7 @@
from datetime import datetime
-from datetime import timezone
+
import gitlab
+
from config import Config
@@ -22,9 +23,9 @@ class GitlabUser:
the username
"""
super().__init__()
- if isinstance(user, int): # by user id
+ if isinstance(user, int): # by user id
self.git_user = GitlabSession.get_session().users.get(user)
- elif isinstance(user, str): # by username
+ elif isinstance(user, str): # by username
self.git_user = GitlabSession.get_session().users.list(username=user)[0]
else:
self.git_user = user
@@ -99,7 +100,7 @@ class GitlabIssue:
:return: an "aware" datetime object representing the creation date/time
"""
created_at = self.git_issue.created_at
- if created_at[-1] in ('z', 'Z'): # Didn't encounter this problem with created_at
+ if created_at[-1] in ('z', 'Z'): # Didn't encounter this problem with created_at
created_at = created_at[:-1] + '+00:00'
return datetime.fromisoformat(created_at)
@@ -108,7 +109,7 @@ class GitlabIssue:
:return: an "aware" datetime object representing the last date/time the issue was updated
"""
updated_at = self.git_issue.updated_at
- if updated_at[-1] in ('z', 'Z'): # Didn't encounter this problem with updated_at
+ if updated_at[-1] in ('z', 'Z'): # Didn't encounter this problem with updated_at
updated_at = updated_at[:-1] + '+00:00'
return datetime.fromisoformat(updated_at)
@@ -121,15 +122,16 @@ class GitlabIssue:
if closed_at is None:
return None
else:
- if closed_at[-1] in ('z', 'Z'): # Did encounter this problem with closed_at
+ if closed_at[-1] in ('z', 'Z'): # Did encounter this problem with closed_at
closed_at = closed_at[:-1] + '+00:00'
return datetime.fromisoformat(closed_at)
def get_labels(self):
"""
- :return: list of labels
+ :return: set of label names
"""
- return self.git_issue.labels.list(all=True)
+ return set(self.git_issue.labels)
+ # return self.git_issue.labels.list(all=True)
def get_page(self):
"""
@@ -169,6 +171,7 @@ class GitlabIssue:
class GitlabCommit:
+ # noinspection PyShadowingNames
def __init__(self, commit):
super().__init__()
self.gitlab_commit = commit
@@ -185,6 +188,7 @@ class GitlabCommit:
def is_merge(self):
return len(self.gitlab_commit.parent_ids) > 1
+ # noinspection PyShadowingNames
def get_diffs(self):
diffs = []
gitlab_diffs = self.gitlab_commit.diff()
@@ -193,6 +197,7 @@ class GitlabCommit:
'+': diff['diff'].count('\n+'), '-': diff['diff'].count('\n-')})
return diffs
+ # noinspection PyShadowingNames
def get_diff_size(self):
insertions = 0
deletions = 0
@@ -231,9 +236,9 @@ class GitlabProject:
containing the project's path (namespace and name, such as 'csce_361/sandbox/HelloWorld')
"""
super().__init__()
- if isinstance(project, int): # by project id
+ if isinstance(project, int): # by project id
self.git_project = GitlabSession.get_session().projects.get(project)
- elif isinstance(project, str): # by path
+ elif isinstance(project, str): # by path
self.git_project = GitlabSession.get_session().projects.get(project)
else:
# self.git_project = project # for some reason, many attributes (including members) might be lost
@@ -245,7 +250,7 @@ class GitlabProject:
: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
+ if isinstance(group, int): # by group id
gitlab_projects = GitlabSession.get_session().groups.get(group).projects.list(all=True)
else: # isinstance(group, str): # by path
gitlab_projects = GitlabSession.get_session().groups.get(group).projects.list(all=True)
@@ -375,10 +380,11 @@ class GitlabProject:
gitlab_issue = self.git_project.issues.create({'title': title, 'description': description})
return GitlabIssue(gitlab_issue)
- def get_commits(self, branch_name = '', after_date ='1970-01-01', before_date ='9999-12-31'):
+ # noinspection PyShadowingNames
+ def get_commits(self, branch_name='', after_date='1970-01-01', before_date='9999-12-31'):
"""
:param branch_name: the branch to retrieve commits from; if an empty string (default) then retrieves commits
- from all branches
+ from all branches <-- NO, RETRIEVES FROM DEFAULT BRANCH; WILL NEED TO FIX THAT #TODO
:param after_date: the earliest date of any retrieved commit; if '1970-01-01' (Unix epoch) then treated as
having no earliest-bound
:param before_date: the latest date of any retrieved commit; if '9999-12-31' (Y10K problem) then treated a
@@ -401,6 +407,12 @@ class GitlabProject:
commits.append(GitlabCommit(commit))
return commits
+ def get_labels(self):
+ """
+ :return: set of label names
+ """
+ return set(map(lambda label: label.name, self.git_project.labels.list()))
+
def __repr__(self):
return self.get_name_with_namespace()
diff --git a/prep_assignment.py b/prep_assignment.py
index f9d5ab4..bfa6094 100644
--- a/prep_assignment.py
+++ b/prep_assignment.py
@@ -81,7 +81,7 @@ def create_groups(assignment_number, student_pairs):
if __name__ == '__main__':
- assignment = '21b'
+ assignment = '28'
pairs = create_pairs('2019-08.csv')
save_pairs(assignment, pairs)
print('Pairs created')
@@ -94,3 +94,25 @@ if __name__ == '__main__':
print('TODO:\tAdd issues')
print('\tCommit starter code')
print('\tUpdate graylists (also, please update the code to update the graylists)')
+
+"""
+had a graylist violation on 28pair 4
+
+encountered this on 28pair 18:
+
+ Traceback (most recent call last):
+ File "/Users/cabohn/courses/csce361/scripts/prep_assignment.py", line 91, in <module>
+ create_groups(assignment, pairs)
+ File "/Users/cabohn/courses/csce361/scripts/prep_assignment.py", line 79, in create_groups
+ group.add_student(pair[1].get_canvas_user())
+ File "/Users/cabohn/courses/csce361/scripts/api/composite_user.py", line 33, in get_canvas_user
+ self.canvas_user = CanvasUser(self.NUID) # n.b., can retrieve own user but not arbitrary user
+ File "/Users/cabohn/courses/csce361/scripts/api/canvas_classes.py", line 22, in __init__
+ self.canvas_user = CanvasSession.get_session().get_user(user, 'sis_user_id')
+ File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/canvasapi/canvas.py", line 1110, in get_user
+ response = self.__requester.request("GET", uri)
+ File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/canvasapi/requester.py", line 227, in request
+ raise Unauthorized(response.json())
+ canvasapi.exceptions.Unauthorized: [{'message': 'user not authorized to perform that action'}]
+
+"""
\ No newline at end of file
--
GitLab