Auto assign of releases to commits is implemented

* Default data processor is introduced
* Repo module is fixed in Vcs
* Fixed marks and review author and week fields

Implements blueprint auto-assign-release

Change-Id: Ic0bc297dd75b12e73cdd0256cf0c7684cc6cd158
This commit is contained in:
Ilya Shakhat 2013-07-31 13:25:08 +04:00
parent 6c33901c05
commit 92636f91c6
8 changed files with 127 additions and 89 deletions

View File

@ -41,13 +41,7 @@
"branches": ["master"], "branches": ["master"],
"module": "stackalytics", "module": "stackalytics",
"project_type": "stackforge", "project_type": "stackforge",
"uri": "git://github.com/stackforge/stackalytics.git", "uri": "git://github.com/stackforge/stackalytics.git"
"releases": [
{
"release_name": "Havana",
"tag_to": "HEAD"
}
]
} }
], ],

View File

@ -0,0 +1,77 @@
# Copyright (c) 2013 Mirantis Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from stackalytics.openstack.common import log as logging
from stackalytics.processor import utils
LOG = logging.getLogger(__name__)
def normalize_user(user):
user['emails'] = [email.lower() for email in user['emails']]
if user['launchpad_id']:
user['launchpad_id'] = user['launchpad_id'].lower()
for c in user['companies']:
end_date_numeric = 0
if c['end_date']:
end_date_numeric = utils.date_to_timestamp(c['end_date'])
c['end_date'] = end_date_numeric
# sort companies by end_date
def end_date_comparator(x, y):
if x["end_date"] == 0:
return 1
elif y["end_date"] == 0:
return -1
else:
return cmp(x["end_date"], y["end_date"])
user['companies'].sort(cmp=end_date_comparator)
def _process_users(users):
for user in users:
if ('launchpad_id' not in user) or ('emails' not in user):
LOG.warn('Skipping invalid user: %s', user)
continue
normalize_user(user)
user['user_id'] = user['launchpad_id'] or user['emails'][0]
def _process_releases(releases):
for release in releases:
release['release_name'] = release['release_name'].lower()
release['end_date'] = utils.date_to_timestamp(release['end_date'])
releases.sort(key=lambda x: x['end_date'])
def _process_repos(repos):
for repo in repos:
if 'releases' not in repo:
repo['releases'] = [] # release will be assigned automatically
PROCESSORS = {
'users': _process_users,
'releases': _process_releases,
'repos': _process_repos,
}
def process(persistent_storage, default_data):
for key, processor in PROCESSORS.items():
processor(default_data[key])
persistent_storage.sync(default_data, force=True)

View File

@ -23,11 +23,11 @@ from psutil import _error
from stackalytics.openstack.common import log as logging from stackalytics.openstack.common import log as logging
from stackalytics.processor import config from stackalytics.processor import config
from stackalytics.processor import default_data_processor
from stackalytics.processor import persistent_storage from stackalytics.processor import persistent_storage
from stackalytics.processor import rcs from stackalytics.processor import rcs
from stackalytics.processor import record_processor from stackalytics.processor import record_processor
from stackalytics.processor import runtime_storage from stackalytics.processor import runtime_storage
from stackalytics.processor import utils
from stackalytics.processor import vcs from stackalytics.processor import vcs
@ -140,35 +140,9 @@ def _read_default_persistent_storage(file_name):
LOG.error('Error while reading config: %s' % e) LOG.error('Error while reading config: %s' % e)
def process_users(users):
res = []
for user in users:
if ('launchpad_id' not in user) or ('emails' not in user):
LOG.warn('Skipping invalid user: %s', user)
continue
u = utils.normalize_user(user.copy())
u['user_id'] = user['launchpad_id'] or user['emails'][0]
res.append(u)
return res
def process_releases(releases):
res = []
for release in releases:
r = utils.normalize_release(release)
res.append(r)
res.sort(key=lambda x: x['end_date'])
return res
def load_default_data(persistent_storage_inst, file_name, force): def load_default_data(persistent_storage_inst, file_name, force):
default_data = _read_default_persistent_storage(file_name) default_data = _read_default_persistent_storage(file_name)
default_data_processor.process(persistent_storage_inst, default_data)
default_data['users'] = process_users(default_data['users'])
default_data['releases'] = process_releases(default_data['releases'])
persistent_storage_inst.sync(default_data, force=force)
def main(): def main():

View File

@ -12,13 +12,14 @@
# implied. # implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import bisect
import logging import bisect
import re import re
from launchpadlib import launchpad from launchpadlib import launchpad
from oslo.config import cfg from oslo.config import cfg
from stackalytics.openstack.common import log as logging
from stackalytics.processor import default_data_processor
from stackalytics.processor import utils from stackalytics.processor import utils
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -51,6 +52,13 @@ class CachedProcessor(RecordProcessor):
for email in user['emails']: for email in user['emails']:
self.users_index[email] = user self.users_index[email] = user
self.releases = list(persistent_storage.get_releases())
self.releases_dates = [r['end_date'] for r in self.releases]
def _get_release(self, timestamp):
release_index = bisect.bisect(self.releases_dates, timestamp)
return self.releases[release_index]['release_name']
def _find_company(self, companies, date): def _find_company(self, companies, date):
for r in companies: for r in companies:
if date < r['end_date']: if date < r['end_date']:
@ -101,6 +109,7 @@ class CachedProcessor(RecordProcessor):
'end_date': 0, 'end_date': 0,
}], }],
} }
default_data_processor.normalize_user(user)
self.persistent_storage.insert_user(user) self.persistent_storage.insert_user(user)
return user return user
@ -133,6 +142,7 @@ class CachedProcessor(RecordProcessor):
'end_date': 0 'end_date': 0
}] }]
} }
default_data_processor.normalize_user(user)
# add new user # add new user
self.persistent_storage.insert_user(user) self.persistent_storage.insert_user(user)
else: else:
@ -186,6 +196,9 @@ class CommitProcessor(CachedProcessor):
record['week'] = utils.timestamp_to_week(record['date']) record['week'] = utils.timestamp_to_week(record['date'])
record['loc'] = record['lines_added'] + record['lines_deleted'] record['loc'] = record['lines_added'] + record['lines_deleted']
if not record['release']:
record['release'] = self._get_release(record['date'])
yield record yield record
@ -198,15 +211,8 @@ class ReviewProcessor(CachedProcessor):
for user in users: for user in users:
self.launchpad_to_company_index[user['launchpad_id']] = user self.launchpad_to_company_index[user['launchpad_id']] = user
self.releases = list(persistent_storage.get_releases())
self.releases_dates = [r['end_date'] for r in self.releases]
LOG.debug('Review processor is instantiated') LOG.debug('Review processor is instantiated')
def _get_release(self, timestamp):
release_index = bisect.bisect(self.releases_dates, timestamp)
return self.releases[release_index]['release_name']
def _process_user(self, email, launchpad_id, user_name, date): def _process_user(self, email, launchpad_id, user_name, date):
if email in self.users_index: if email in self.users_index:
user = self.users_index[email] user = self.users_index[email]
@ -228,21 +234,25 @@ class ReviewProcessor(CachedProcessor):
return # ignore return # ignore
review['record_type'] = 'review' review['record_type'] = 'review'
review['primary_key'] = record['id'] review['primary_key'] = review['id']
review['launchpad_id'] = owner['username'] review['launchpad_id'] = owner['username']
review['author'] = owner['name']
review['author_email'] = owner['email'].lower() review['author_email'] = owner['email'].lower()
review['release'] = self._get_release(review['createdOn']) review['release'] = self._get_release(review['createdOn'])
review['week'] = utils.timestamp_to_week(review['createdOn'])
company, user_id = self._process_user(review['author_email'], company, user_id = self._process_user(review['author_email'],
review['launchpad_id'], review['launchpad_id'],
owner['name'], review['author'],
record['createdOn']) review['createdOn'])
review['company_name'] = company review['company_name'] = company
review['user_id'] = user_id review['user_id'] = user_id
yield review yield review
def _spawn_marks(self, record): def _spawn_marks(self, record):
review_id = record['id'] review_id = record['id']
module = record['module']
for patch in record['patchSets']: for patch in record['patchSets']:
if 'approvals' not in patch: if 'approvals' not in patch:
continue # not reviewed by anyone continue # not reviewed by anyone
@ -260,17 +270,19 @@ class ReviewProcessor(CachedProcessor):
str(mark['grantedOn']) + str(mark['grantedOn']) +
mark['type']) mark['type'])
mark['launchpad_id'] = reviewer['username'] mark['launchpad_id'] = reviewer['username']
mark['author'] = reviewer['name']
mark['author_email'] = reviewer['email'].lower() mark['author_email'] = reviewer['email'].lower()
mark['module'] = record['module'] mark['module'] = module
mark['review_id'] = review_id
mark['release'] = self._get_release(mark['grantedOn'])
mark['week'] = utils.timestamp_to_week(mark['grantedOn'])
company, user_id = self._process_user(mark['author_email'], company, user_id = self._process_user(mark['author_email'],
mark['launchpad_id'], mark['launchpad_id'],
reviewer['name'], mark['author'],
mark['grantedOn']) mark['grantedOn'])
mark['company_name'] = company mark['company_name'] = company
mark['user_id'] = user_id mark['user_id'] = user_id
mark['review_id'] = review_id
mark['release'] = self._get_release(mark['grantedOn'])
yield mark yield mark

View File

@ -17,36 +17,6 @@ import datetime
import time import time
def normalize_user(user):
user['emails'] = [email.lower() for email in user['emails']]
if user['launchpad_id']:
user['launchpad_id'] = user['launchpad_id'].lower()
for c in user['companies']:
end_date_numeric = 0
if c['end_date']:
end_date_numeric = date_to_timestamp(c['end_date'])
c['end_date'] = end_date_numeric
# sort companies by end_date
def end_date_comparator(x, y):
if x["end_date"] == 0:
return 1
elif y["end_date"] == 0:
return -1
else:
return cmp(x["end_date"], y["end_date"])
user['companies'].sort(cmp=end_date_comparator)
return user
def normalize_release(release):
release['release_name'] = release['release_name'].lower()
release['end_date'] = date_to_timestamp(release['end_date'])
return release
def date_to_timestamp(d): def date_to_timestamp(d):
if d == 'now': if d == 'now':
return int(time.time()) return int(time.time())

View File

@ -78,24 +78,22 @@ class Git(Vcs):
uri = self.repo['uri'] uri = self.repo['uri']
match = re.search(r'([^\/]+)\.git$', uri) match = re.search(r'([^\/]+)\.git$', uri)
if match: if match:
self.module = match.group(1) self.folder = os.path.normpath(self.sources_root + '/' +
match.group(1))
else: else:
raise Exception('Unexpected uri %s for git' % uri) raise Exception('Unexpected uri %s for git' % uri)
self.release_index = {} self.release_index = {}
def _chdir(self): def _chdir(self):
folder = os.path.normpath(self.sources_root + '/' + self.module) os.chdir(self.folder)
os.chdir(folder)
def fetch(self): def fetch(self):
LOG.debug('Fetching repo uri %s' % self.repo['uri']) LOG.debug('Fetching repo uri %s' % self.repo['uri'])
folder = os.path.normpath(self.sources_root + '/' + self.module) if not os.path.exists(self.folder):
if not os.path.exists(folder):
os.chdir(self.sources_root) os.chdir(self.sources_root)
sh.git('clone', '%s' % self.repo['uri']) sh.git('clone', '%s' % self.repo['uri'])
os.chdir(folder) os.chdir(self.folder)
else: else:
self._chdir() self._chdir()
sh.git('pull', 'origin') sh.git('pull', 'origin')
@ -157,7 +155,7 @@ class Git(Vcs):
commit[key] = None commit[key] = None
commit['date'] = int(commit['date']) commit['date'] = int(commit['date'])
commit['module'] = self.module commit['module'] = self.repo['module']
commit['branches'] = set([branch]) commit['branches'] = set([branch])
if commit['commit_id'] in self.release_index: if commit['commit_id'] in self.release_index:
commit['release'] = self.release_index[commit['commit_id']] commit['release'] = self.release_index[commit['commit_id']]

View File

@ -20,6 +20,7 @@ import testtools
from stackalytics.processor import persistent_storage from stackalytics.processor import persistent_storage
from stackalytics.processor import record_processor from stackalytics.processor import record_processor
from stackalytics.processor import utils
class TestCommitProcessor(testtools.TestCase): class TestCommitProcessor(testtools.TestCase):
@ -57,6 +58,17 @@ class TestCommitProcessor(testtools.TestCase):
self.user, self.user,
]) ])
p_storage.get_releases = mock.Mock(return_value=[
{
'release_name': 'prehistory',
'end_date': utils.date_to_timestamp('2011-Apr-21')
},
{
'release_name': 'Diablo',
'end_date': utils.date_to_timestamp('2011-Sep-08')
},
])
self.persistent_storage = p_storage self.persistent_storage = p_storage
self.commit_processor = record_processor.CommitProcessor(p_storage) self.commit_processor = record_processor.CommitProcessor(p_storage)
self.launchpad_patch = mock.patch('launchpadlib.launchpad.Launchpad') self.launchpad_patch = mock.patch('launchpadlib.launchpad.Launchpad')

View File

@ -27,6 +27,7 @@ class TestVcsProcessor(testtools.TestCase):
super(TestVcsProcessor, self).setUp() super(TestVcsProcessor, self).setUp()
self.repo = { self.repo = {
'module': 'dummy',
'uri': 'git://github.com/dummy.git', 'uri': 'git://github.com/dummy.git',
'releases': [] 'releases': []
} }