diff --git a/reviewstats/utils.py b/reviewstats/utils.py index 7ad569a..c148cf0 100644 --- a/reviewstats/utils.py +++ b/reviewstats/utils.py @@ -1,4 +1,4 @@ -# +## -*- coding: utf-8 -*- # Copyright (C) 2011 - Soren Hansen # Copyright (C) 2013 - Red Hat, Inc. # @@ -14,6 +14,8 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. +"""Utility functions module +""" import glob import json @@ -30,6 +32,28 @@ LOG = logging.getLogger(__name__) def get_projects_info(project=None, all_projects=False, base_dir='./projects'): + """Return the list of project dict objects. + + :param project: pathname of the JSON project info file. + :type project: str or None + :param bool all_projects: + If True deserialize all the “.json“ files of officials projects in + base_dir. + :param str base_dir: + dirname of the path containing the json projects files. + :return: unserialized json of project info. + :rtype: dict + + project or all_projects=True must be given. + + .. note:: + Unofficial projects are filtered out if all_projects is True. + Official qualification is a key of the json file name “unofficial”, if + present and true, its an unofficial project. + + .. note:: + all_projects parameters overrides project parameter value if True + """ if all_projects: files = glob.glob('%s/*.json' % base_dir) else: @@ -52,6 +76,18 @@ def get_projects_info(project=None, all_projects=False, base_dir='./projects'): def projects_q(project): + """Return the gerrit query selecting all the project in the given list + + :param project: List of gerrit project name. + :type project: list of str + :return: + gerrit query according to `Searching Changes`_ section of gerrit + documentation. + :rtype: str + + .. _Searching Changes: + https://review.openstack.org/Documentation/user-search.html + """ return ('(' + ' OR '.join(['project:' + p for p in project['subprojects']]) + ')') @@ -59,6 +95,30 @@ def projects_q(project): def get_changes(projects, ssh_user, ssh_key, only_open=False, stable='', server='review.openstack.org'): + """Get the changesets data list. + + :param projects: List of gerrit project names. + :type projects: list of str + :param str ssh_user: Gerrit username. + :param str ssh_key: Filename of one SSH key registered at gerrit. + :param bool only_open: If True, get only the not closed reviews. + :param str stable: + Name of the stable branch. If empty string, the changesets are not + filtered by any branch. + + :return: List of de-serialized JSON changeset data as returned by gerrit. + :rtype: list + + .. note:: + If all the issue are requested whatever the branch is, a cache system + override the gerrit request. + Requesting only the open changes isn't nearly as big of a deal, + so just get the current data. + Also do not use cache for stable stats as they cover different + results. + Cached results are pickled per project in the following filenames: + “.{projectname}-changes.pickle”. + """ all_changes = [] client = paramiko.SSHClient() @@ -71,10 +131,6 @@ def get_changes(projects, ssh_user, ssh_key, only_open=False, stable='', if not only_open and not stable: # Only use the cache for *all* changes (the entire history). - # Requesting only the open changes isn't nearly as big of a deal, - # so just get the current data. - # Also do not use cache for stable stats as they cover different - # results. pickle_fn = '.%s-changes.pickle' % project['name'] if os.path.isfile(pickle_fn): @@ -132,6 +188,13 @@ def get_changes(projects, ssh_user, ssh_key, only_open=False, stable='', def patch_set_approved(patch_set): + """Return True if the patchset has been approved. + + :param dict patch_set: De-serialized dict of a gerrit change + + :return: True if one of the patchset reviews approved it. + :rtype: bool + """ approvals = patch_set.get('approvals', []) for review in approvals: if review['type'] == 'APRV': @@ -140,16 +203,24 @@ def patch_set_approved(patch_set): def get_age_of_patch(patch, now_ts): + """Compute the number of seconds since the patch submission and now. + + :param dict patch: Deserialized JSON of a Gerrit patchset + :param int now_ts: Unix-like timestamp in seconds since EPOCH. + :return int: number of seconds between patch submission and now_ts + + .. note:: + The createdOn timestamp on the patch isn't what we want. + It's when the patch was written, not submitted for review. + The next best thing in the data we have is the time of the + first review. When all is working well, jenkins or smokestack + will comment within the first hour or two, so that's better + than the other timestamp, which may reflect that the code + was written many weeks ago, even though it was just recently + submitted for review. + """ approvals = patch.get('approvals', []) approvals.sort(key=lambda a: a['grantedOn']) - # The createdOn timestamp on the patch isn't what we want. - # It's when the patch was written, not submitted for review. - # The next best thing in the data we have is the time of the - # first review. When all is working well, jenkins or smokestack - # will comment within the first hour or two, so that's better - # than the other timestamp, which may reflect that the code - # was written many weeks ago, even though it was just recently - # submitted for review. if approvals: return now_ts - approvals[0]['grantedOn'] else: