Completed processing of blueprints data

* Update blueprints with number of mentions (emails, commits, reviews)
 * Improved understandability of record processor tests (significantly refactored)
 * Extracted LP specific functions into launchpad_utils
 * Refactored activity template in UI

Implements bp metric-by-bugs-blueprints

Change-Id: I9008a84ef1960e54be6e61f0ef3d69cd2cc1d9e4
This commit is contained in:
Ilya Shakhat
2013-09-17 21:26:01 +04:00
parent 193d285019
commit 47f26a2f29
10 changed files with 943 additions and 403 deletions

View File

@@ -216,3 +216,94 @@ a[href^="https://launchpad"]:after {
.review_mark {
font-weight: bold;
}
.specstatusApproved, .specstatusApproved a {
color: #008000;
}
.specstatusPendingApproval, .specstatusPendingApproval a, .specstatusPendingView, .specstatusPendingView a {
color: #FF0099;
}
.specstatusDraft, .specstatusDraft a, .specstatusDiscussion, .specstatusDiscussion a {
color: #993300;
}
.specstatusNew, .specstatusNew a {
color: #FF0000;
}
.specstatusSuperseded, .specstatusSuperseded a, .specstatusObsolete, .specstatusObsolete a, .specpriorityUndefined, .specpriorityUndefined a {
color: #808080;
}
.specpriorityLow, .specpriorityLow a {
color: #000000;
}
.specpriorityMedium, .specpriorityMedium a {
color: #FF6600;
}
.specpriorityHigh, .specpriorityHigh a, .specpriorityEssential, .specpriorityEssential a {
color: #FF0000;
}
.specdeliveryUnknown, .specdeliveryUnknown a, .specdeliveryNotStarted, .specdeliveryNotStarted a {
color: #808080;
}
.specdeliveryDeferred, .specdeliveryDeferred a, .specdeliveryNeendsInfrastructure, .specdeliveryNeendsInfrastructure a, .specdeliveryBlocked, .specdeliveryBlocked a {
color: #FF0000;
}
.specdeliveryStarted, .specdeliveryStarted a, .specdeliveryGood, .specdeliveryGood a {
color: #0000FF;
}
.specdeliverySlow, .specdeliverySlow a {
color: #FF0000;
}
.specdeliveryBeta, .specdeliveryBeta a {
color: #FF6600;
}
.specdeliveryNEEDSREVIEW, .specdeliveryNEEDSREVIEW a {
color: #800080;
}
.specdeliveryAWAITINGDEPLOYMENT, .specdeliveryAWAITINGDEPLOYMENT a {
color: #FF0000;
}
.specdeliveryImplemented, .specdeliveryImplemented a, .specdeliveryINFORMATIONAL, .specdeliveryINFORMATIONAL a {
color: #008000;
}
.bug-activity {
color: #555555;
}
.statusNew, .statusNew a {
color: #993300;
}
.statusIncomplete, .statusIncomplete a, .statusConfirmed, .statusConfirmed a {
color: #FF0000;
}
.statusTriaged, .statusTriaged a {
color: #FF6600;
}
.statusInProgress, .statusInProgress a {
color: #000000;
}
.statusFixCommitted, .statusFixCommitted a {
color: #005500;
}
.statusFixReleased, .statusFixReleased a {
color: #008000;
}
.statusInvalid, .statusInvalid a, .statusWontFix, .statusWontFix a {
color: #555555;
}
.importanceCritical, .importanceCritical a {
color: #FF0000;
}
.importanceHigh, .importanceHigh a {
color: #FF6600;
}
.importanceMedium, .importanceMedium a {
color: #008000;
}
.importanceLow, .importanceLow a {
color: #000000;
}
.importanceWishlist, .importanceWishlist a {
color: #0000FF;
}
.importanceUndecided, .importanceUndecided a {
color: #999999;
}

View File

@@ -6,7 +6,7 @@
{% set show_user_activity = (user_id) %}
{% set show_module_activity = (module) and (not user_id) %}
{% set show_activity = (show_user_activity) or (show_module_activity) %}
{% set show_user_contribution = (user_id) %}
{% set show_user_contribution = (user_id) or (company) %}
{% set show_module_contribution = (module) and (not user_id) %}
{% set show_contribution = (show_user_contribution) or (show_module_contribution) %}
{% set show_user_profile = (user_id) %}
@@ -117,10 +117,24 @@
</a>
{%/if%}
</div>
{%elif ((record_type == "bp_draft") || (record_type == "bp_implementation")) %}
{%if blueprint_id_count %}
<div>Mentions blueprints:
{%each( index, value ) blueprint_id %}
${value}
{%/each%}
</div>
{%/if%}
{%elif ((record_type == "bpd") || (record_type == "bpc")) %}
<div style='font-weight: bold;'>${title} (<a href='${web_link}'>${name}</a>)</div>
<div style='white-space: pre-wrap;'>${summary}</div>
<div>Priority: <span class="specpriority${priority}">${priority}</span></div>
<div>Status: <span class="status${lifecycle_status}">${lifecycle_status}</span>
(<span class="specstatus${definition_status}">${definition_status}</span>,
<span class="specdelivery${implementation_status}">${implementation_status}</span>)</div>
{%if mention_count %}
<div><b>Mention count: ${mention_count}, last mention on ${mention_date_str}</b></div>
{%/if%}
{%/if%}
</div>
</div>
@@ -144,37 +158,14 @@
<script id="contribution_template" type="text/x-jquery-tmpl">
{% raw %}
<h3>Contribution overview</h3>
{%if blueprints.length > 0 %}
<div>Blueprints:
<ol>
{%each(i,rec) blueprints %}
<li>
<a href="https://blueprints.launchpad.net/${rec.module}/+spec/${rec.id}">${rec.id}</a>
<small>${rec.module}</small>
</li>
{%/each%}
</ol>
</div>
{%/if%}
{%if bugs.length > 0 %}
<div>Bugs:
<ol>
{%each(i,rec) bugs %}
<li>
<a href="https://bugs.launchpad.net/bugs/${rec.id}">${rec.id}</a>
<small>${rec.module}</small>
</li>
{%/each%}
</ol>
</div>
{%/if%}
<h2>Contribution Summary</h2>
<div>Total commits: <b>${commit_count}</b></div>
<div>Total LOC: <b>${loc}</b></div>
<div>Review stat (-2, -1, +1, +2): <b>${marks["-2"]}, ${marks["-1"]}, ${marks["1"]}, ${marks["2"]}</b></div>
{% endraw %}
<div>Draft Blueprints: <b>${new_blueprint_count}</b></div>
<div>Completed Blueprints: <b>${competed_blueprint_count}</b></div>
<div>Emails: <b>${email_count}</b></div>
</script>
{% endblock %}
@@ -230,7 +221,7 @@
<div id="user_profile_container" style="margin-bottom: 2em;"></div>
{% endif %}
{% if show_user_activity %}
<h3 id="activity_header">Activity log</h3>
<h2 id="activity_header">Activity log</h2>
<div id="activity_container"></div>
<div style="height: 44px;">
@@ -274,7 +265,7 @@
{% endif %}
{% if show_module_activity %}
<h3 id="activity_header">Activity log</h3>
<h2 id="activity_header">Activity log</h2>
<div id="activity_container"></div>
<div style="height: 44px;">

View File

@@ -49,8 +49,8 @@ METRIC_LABELS = {
'commits': 'Commits',
'marks': 'Reviews',
'emails': 'Emails',
'bp_draft': 'New Blueprints',
'bp_implementation': 'Completed Blueprints',
'bpd': 'New Blueprints',
'bpc': 'Completed Blueprints',
}
METRIC_TO_RECORD_TYPE = {
@@ -58,8 +58,8 @@ METRIC_TO_RECORD_TYPE = {
'commits': 'commit',
'marks': 'mark',
'emails': 'email',
'bp_draft': 'bp_draft',
'bp_implementation': 'bp_implementation',
'bpd': 'bpd',
'bpc': 'bpc',
}
DEFAULT_RECORDS_LIMIT = 10
@@ -406,8 +406,8 @@ def aggregate_filter():
'loc': (loc_filter, None),
'marks': (mark_filter, mark_finalize),
'emails': (incremental_filter, None),
'bp_draft': (incremental_filter, None),
'bp_implementation': (incremental_filter, None),
'bpd': (incremental_filter, None),
'bpc': (incremental_filter, None),
}
if metric not in metric_to_filters_map:
raise Exception('Invalid metric %s' % metric)
@@ -554,44 +554,6 @@ def page_not_found(e):
pass
def contribution_details(records):
blueprints_map = {}
bugs_map = {}
marks = dict((m, 0) for m in [-2, -1, 0, 1, 2])
commit_count = 0
loc = 0
for record in records:
if 'blueprint_id' in record:
for bp in record['blueprint_id']:
blueprints_map[bp] = record
if 'bug_id' in record:
for bug in record['bug_id']:
bugs_map[bug] = record
if record['record_type'] == 'mark':
marks[int(record['value'])] += 1
elif record['record_type'] == 'commit':
commit_count += 1
loc += record['loc']
blueprints = sorted([{'id': key, 'module': value['module']}
for key, value in blueprints_map.iteritems()],
key=lambda x: x['id'])
bugs = sorted([{'id': key, 'module': value['module']}
for key, value in bugs_map.iteritems()],
key=lambda x: int(x['id']))
result = {
'blueprints': blueprints,
'bugs': bugs,
'commit_count': commit_count,
'loc': loc,
'marks': marks,
}
return result
# AJAX Handlers ---------
def _get_aggregated_stats(records, metric_filter, keys, param_id,
@@ -653,7 +615,9 @@ def _extend_record(record):
record['company_link'] = make_link(
record['company_name'], '/',
{'company': record['company_name'], 'user_id': ''})
record['gravatar'] = gravatar(record['author_email'])
record['gravatar'] = gravatar(record.get('author_email', 'stackalytics'))
record['blueprint_id_count'] = len(record.get('blueprint_id', []))
record['bug_id_count'] = len(record.get('bug_id', []))
@app.route('/api/1.0/activity')
@@ -693,10 +657,13 @@ def get_activity_json(records):
_extend_record(email)
email['email_link'] = email.get('email_link') or ''
result.append(email)
elif ((record['record_type'] == 'bp_draft') or
(record['record_type'] == 'bp_implementation')):
elif ((record['record_type'] == 'bpd') or
(record['record_type'] == 'bpc')):
blueprint = record.copy()
_extend_record(blueprint)
if 'mention_date' in record:
record['mention_date_str'] = format_datetime(
record['mention_date'])
result.append(blueprint)
result.sort(key=lambda x: x['date'], reverse=True)
@@ -708,7 +675,36 @@ def get_activity_json(records):
@exception_handler()
@record_filter(ignore='metric')
def get_contribution_json(records):
return contribution_details(records)
marks = dict((m, 0) for m in [-2, -1, 0, 1, 2])
commit_count = 0
loc = 0
new_blueprint_count = 0
competed_blueprint_count = 0
email_count = 0
for record in records:
record_type = record['record_type']
if record_type == 'commit':
commit_count += 1
loc += record['loc']
elif record['record_type'] == 'mark':
marks[int(record['value'])] += 1
elif record['record_type'] == 'email':
email_count += 1
elif record['record_type'] == 'bpd':
new_blueprint_count += 1
elif record['record_type'] == 'bpc':
competed_blueprint_count += 1
result = {
'new_blueprint_count': new_blueprint_count,
'competed_blueprint_count': competed_blueprint_count,
'commit_count': commit_count,
'email_count': email_count,
'loc': loc,
'marks': marks,
}
return result
@app.route('/api/1.0/companies')
@@ -817,7 +813,10 @@ def get_user(user_id):
company_name, '/', {'company': company_name, 'user_id': ''})
else:
user['company_link'] = ''
if user['emails']:
user['gravatar'] = gravatar(user['emails'][0])
else:
user['gravatar'] = gravatar('stackalytics')
return user

View File

@@ -119,7 +119,7 @@
"releases": [
{
"release_name": "prehistory",
"end_date": "2011-Apr-21"
"end_date": "2011-Apr-22"
},
{
"release_name": "Diablo",

View File

@@ -0,0 +1,66 @@
# 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.
import httplib
import urlparse
from stackalytics.openstack.common import log as logging
from stackalytics.processor import utils
LOG = logging.getLogger(__name__)
LP_URI_V1 = 'https://api.launchpad.net/1.0/%s'
LP_URI_DEVEL = 'https://api.launchpad.net/devel/%s'
def lp_profile_by_launchpad_id(launchpad_id):
LOG.debug('Lookup user id %s at Launchpad', launchpad_id)
uri = LP_URI_V1 % ('~' + launchpad_id)
return utils.read_json_from_uri(uri)
def lp_profile_by_email(email):
LOG.debug('Lookup user email %s at Launchpad', email)
uri = LP_URI_V1 % ('people/?ws.op=getByEmail&email=' + email)
return utils.read_json_from_uri(uri)
def lp_module_exists(module):
uri = LP_URI_DEVEL % module
parsed_uri = urlparse.urlparse(uri)
conn = httplib.HTTPConnection(parsed_uri.netloc)
conn.request('GET', parsed_uri.path)
res = conn.getresponse()
LOG.debug('Checked uri: %(uri)s, status: %(status)s',
{'uri': uri, 'status': res.status})
conn.close()
return res.status != 404
def lp_blueprint_generator(module):
uri = LP_URI_DEVEL % (module + '/all_specifications')
while uri:
LOG.debug('Reading chunk from uri %s', uri)
chunk = utils.read_json_from_uri(uri)
if not chunk:
LOG.warn('No data was read from uri %s', uri)
break
for record in chunk['entries']:
yield record
uri = chunk.get('next_collection_link')

View File

@@ -13,10 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import httplib
import urlparse
from stackalytics.openstack.common import log as logging
from stackalytics.processor import launchpad_utils
from stackalytics.processor import utils
@@ -27,18 +25,6 @@ LINK_FIELDS = ['owner', 'drafter', 'starter', 'completer',
DATE_FIELDS = ['date_created', 'date_completed', 'date_started']
def _module_exists(module):
uri = 'https://api.launchpad.net/devel/%s' % module
parsed_uri = urlparse.urlparse(uri)
conn = httplib.HTTPConnection(parsed_uri.netloc)
conn.request('GET', parsed_uri.path)
res = conn.getresponse()
LOG.debug('Checked uri: %(uri)s, status: %(status)s',
{'uri': uri, 'status': res.status})
conn.close()
return res.status != 404
def _link_to_launchpad_id(link):
return link[link.find('~') + 1:]
@@ -47,19 +33,11 @@ def log(repo):
module = repo['module']
LOG.debug('Retrieving list of blueprints for module: %s', module)
if not _module_exists(module):
if not launchpad_utils.lp_module_exists(module):
LOG.debug('Module %s not exist at Launchpad', module)
return
uri = 'https://api.launchpad.net/devel/%s/all_specifications' % module
while True:
LOG.debug('Reading chunk from uri %s', uri)
chunk = utils.read_json_from_uri(uri)
if 'next_collection_link' not in chunk:
break
uri = chunk['next_collection_link']
for record in chunk['entries']:
for record in launchpad_utils.lp_blueprint_generator(module):
for field in LINK_FIELDS:
link = record[field + '_link']
if link:

View File

@@ -44,7 +44,10 @@ def normalize_user(user):
return cmp(x["end_date"], y["end_date"])
user['companies'].sort(cmp=end_date_comparator)
if user['emails']:
user['user_id'] = get_user_id(user['launchpad_id'], user['emails'][0])
else:
user['user_id'] = user['launchpad_id']
def _normalize_users(users):

View File

@@ -16,6 +16,7 @@
import bisect
from stackalytics.openstack.common import log as logging
from stackalytics.processor import launchpad_utils
from stackalytics.processor import normalizer
from stackalytics.processor import utils
@@ -66,6 +67,9 @@ class RecordProcessor(object):
return companies[-1]['company_name']
def _get_company_by_email(self, email):
if not email:
return None
name, at, domain = email.partition('@')
if domain:
parts = domain.split('.')
@@ -81,13 +85,16 @@ class RecordProcessor(object):
user = {
'user_id': normalizer.get_user_id(launchpad_id, email),
'launchpad_id': launchpad_id,
'user_name': user_name,
'emails': [email],
'user_name': user_name or '',
'companies': [{
'company_name': company,
'end_date': 0,
}],
}
if email:
user['emails'] = [email]
else:
user['emails'] = []
normalizer.normalize_user(user)
LOG.debug('Create new user: %s', user)
return user
@@ -95,12 +102,9 @@ class RecordProcessor(object):
def _get_lp_info(self, email):
lp_profile = None
if not utils.check_email_validity(email):
LOG.debug('User email is not valid %s' % email)
LOG.debug('User email is not valid %s', email)
else:
LOG.debug('Lookup user email %s at Launchpad' % email)
uri = ('https://api.launchpad.net/1.0/people/?'
'ws.op=getByEmail&email=%s' % email)
lp_profile = utils.read_json_from_uri(uri)
lp_profile = launchpad_utils.lp_profile_by_email(email)
if not lp_profile:
LOG.debug('User with email %s not found', email)
@@ -109,6 +113,18 @@ class RecordProcessor(object):
LOG.debug('Email is mapped to launchpad user: %s', lp_profile['name'])
return lp_profile['name'], lp_profile['display_name']
def _get_lp_user_name(self, launchpad_id):
if not launchpad_id:
return None
lp_profile = launchpad_utils.lp_profile_by_launchpad_id(launchpad_id)
if not lp_profile:
LOG.debug('User with id %s not found', launchpad_id)
return launchpad_id
return lp_profile['display_name']
def _get_independent(self):
return self.domains_index['']
@@ -124,14 +140,14 @@ class RecordProcessor(object):
self.updated_users.add(user['user_id'])
def update_user(self, record):
email = record['author_email']
email = record.get('author_email')
if email in self.users_index:
user = self.users_index[email]
else:
if ('launchpad_id' in record) and (record['launchpad_id']):
launchpad_id = record['launchpad_id']
user_name = record['author_name']
if record.get('launchpad_id'):
launchpad_id = record.get('launchpad_id')
user_name = record.get('author_name')
else:
launchpad_id, user_name = self._get_lp_info(email)
@@ -142,10 +158,13 @@ class RecordProcessor(object):
else:
# create new
if not user_name:
user_name = record['author_name']
user_name = record.get('author_name')
if not user_name:
user_name = self._get_lp_user_name(launchpad_id)
user = self._create_user(launchpad_id, email, user_name)
utils.store_user(self.runtime_storage_inst, user)
if email:
self.users_index[email] = user
if user['launchpad_id']:
self.users_index[user['launchpad_id']] = user
@@ -158,12 +177,12 @@ class RecordProcessor(object):
record['user_id'] = user['user_id']
record['launchpad_id'] = user['launchpad_id']
if ('user_name' in user) and (user['user_name']):
if user.get('user_name'):
record['author_name'] = user['user_name']
company = self._find_company(user['companies'], record['date'])
if company != '*robots':
company = (self._get_company_by_email(record['author_email'])
company = (self._get_company_by_email(record.get('author_email'))
or company)
record['company_name'] = company
@@ -199,7 +218,7 @@ class RecordProcessor(object):
review_id = record['id']
module = record['module']
for patch in record['patchSets']:
for patch in record.get('patchSets', []):
if 'approvals' not in patch:
continue # not reviewed by anyone
for approval in patch['approvals']:
@@ -265,40 +284,28 @@ class RecordProcessor(object):
yield record
def _process_blueprint(self, record):
if record.get('drafter'):
bp_draft = dict([(k, v) for k, v in record.iteritems()])
bp_draft['primary_key'] = 'bpd:' + record['self_link']
bpd_author = record.get('drafter') or record.get('owner')
drafter = utils.load_user(self.runtime_storage_inst,
record['drafter'])
if drafter and record['date_created']:
bp_draft['record_type'] = 'bp_draft'
bp_draft['author_name'] = drafter['user_name']
bp_draft['author_email'] = drafter['emails'][0]
bp_draft['launchpad_id'] = record['drafter']
bp_draft['date'] = record['date_created']
bpd = dict([(k, v) for k, v in record.iteritems()])
bpd['record_type'] = 'bpd'
bpd['primary_key'] = 'bpd:' + record['self_link']
bpd['launchpad_id'] = bpd_author
bpd['date'] = record['date_created']
self._update_record_and_user(bp_draft)
self._update_record_and_user(bpd)
yield bp_draft
yield bpd
if record.get('assignee'):
bp_implementation = dict([(k, v) for k, v in record.iteritems()])
bp_implementation['primary_key'] = 'bpi:' + record['self_link']
if record.get('assignee') and record['date_completed']:
bpc = dict([(k, v) for k, v in record.iteritems()])
bpc['record_type'] = 'bpc'
bpc['primary_key'] = 'bpc:' + record['self_link']
bpc['launchpad_id'] = record['assignee']
bpc['date'] = record['date_completed']
assignee = utils.load_user(self.runtime_storage_inst,
record['assignee'])
if assignee and record['date_completed']:
bp_implementation['record_type'] = 'bp_implementation'
bp_implementation['author_name'] = assignee['user_name']
bp_implementation['author_email'] = assignee['emails'][0]
bp_implementation['launchpad_id'] = record['assignee']
bp_implementation['date'] = record['date_completed']
self._update_record_and_user(bpc)
if bp_implementation['author_email']:
self._update_record_and_user(bp_implementation)
yield bp_implementation
yield bpc
def _apply_type_based_processing(self, record):
if record['record_type'] == 'commit':
@@ -359,15 +366,68 @@ class RecordProcessor(object):
self.runtime_storage_inst.set_by_key('users', self.users_index)
def _get_records_for_users_to_update(self):
valid_blueprints = {}
mentioned_blueprints = {}
for record in self.runtime_storage_inst.get_all_records():
for bp in record.get('blueprint_id', []):
if bp in mentioned_blueprints:
mentioned_blueprints[bp]['count'] += 1
if record['date'] > mentioned_blueprints[bp]['date']:
mentioned_blueprints[bp]['date'] = record['date']
else:
mentioned_blueprints[bp] = {
'count': 1,
'date': record['date']
}
if record['record_type'] in ['bpd', 'bpi']:
valid_blueprints[record['name']] = {
'primary_key': record['primary_key'],
'count': 0,
'date': record['date']
}
for bp in valid_blueprints.keys():
if bp in mentioned_blueprints:
valid_blueprints[bp]['count'] = (
mentioned_blueprints[bp]['count'])
valid_blueprints[bp]['date'] = (
mentioned_blueprints[bp]['date'])
for record in self.runtime_storage_inst.get_all_records():
need_update = False
user_id = record['user_id']
if user_id in self.updated_users:
user = self.users_index[user_id]
user_company_name = user['companies'][0]['company_name']
if record['company_name'] != user_company_name:
LOG.debug('Record company will be changed to: %s',
user_company_name)
LOG.debug('Update record %s: company changed to: %s',
record['primary_key'], user_company_name)
record['company_name'] = user_company_name
need_update = True
valid_bp = set([])
for bp in record.get('blueprint_id', []):
if bp in valid_blueprints:
valid_bp.add(bp)
else:
LOG.debug('Update record %s: removed invalid bp: %s',
record['primary_key'], bp)
need_update = True
record['blueprint_id'] = list(valid_bp)
if record['record_type'] in ['bpd', 'bpi']:
bp = valid_blueprints[record['name']]
if ((record.get('mention_count') != bp['count']) or
(record.get('mention_date') != bp['date'])):
record['mention_count'] = bp['count']
record['mention_date'] = bp['date']
LOG.debug('Update record %s: mention stats: (%s:%s)',
record['primary_key'], bp['count'], bp['date'])
need_update = True
if need_update:
yield record
def finalize(self):

View File

@@ -63,7 +63,10 @@ def read_uri(uri):
def read_json_from_uri(uri):
try:
return json.loads(read_uri(uri))
except Exception as e:
LOG.warn('Error parsing json: %s' % e)
def make_range(start, stop, step):

View File

@@ -21,38 +21,6 @@ from stackalytics.processor import runtime_storage
from stackalytics.processor import utils
LP_URI = 'https://api.launchpad.net/1.0/people/?ws.op=getByEmail&email=%s'
COMPANIES = [
{
'company_name': 'SuperCompany',
'domains': ['super.com', 'super.no']
},
{
"domains": ["nec.com", "nec.co.jp"],
"company_name": "NEC"
},
{
'company_name': '*independent',
'domains': ['']
},
]
USERS = [
{
'user_id': 'john_doe',
'launchpad_id': 'john_doe',
'user_name': 'John Doe',
'emails': ['johndoe@gmail.com', 'jdoe@super.no'],
'companies': [
{'company_name': '*independent',
'end_date': 1234567890},
{'company_name': 'SuperCompany',
'end_date': 0},
]
}
]
RELEASES = [
{
'release_name': 'prehistory',
@@ -84,164 +52,423 @@ class TestRecordProcessor(testtools.TestCase):
self.read_json_from_uri_patch = mock.patch(
'stackalytics.processor.utils.read_json_from_uri')
self.read_launchpad = self.read_json_from_uri_patch.start()
self.lp_profile_by_launchpad_id_patch = mock.patch(
'stackalytics.processor.launchpad_utils.'
'lp_profile_by_launchpad_id')
self.lp_profile_by_launchpad_id = (
self.lp_profile_by_launchpad_id_patch.start())
self.lp_profile_by_launchpad_id.return_value = None
self.lp_profile_by_email_patch = mock.patch(
'stackalytics.processor.launchpad_utils.lp_profile_by_email')
self.lp_profile_by_email = (
self.lp_profile_by_email_patch.start())
self.lp_profile_by_email.return_value = None
def tearDown(self):
super(TestRecordProcessor, self).tearDown()
self.read_json_from_uri_patch.stop()
self.lp_profile_by_launchpad_id_patch.stop()
self.lp_profile_by_email_patch.stop()
# get_company_by_email
def test_get_company_by_email_mapped(self):
record_processor_inst = make_record_processor()
email = 'jdoe@super.no'
record_processor_inst = self.make_record_processor(
companies=[{'company_name': 'IBM', 'domains': ['ibm.com']}]
)
email = 'jdoe@ibm.com'
res = record_processor_inst._get_company_by_email(email)
self.assertEquals('SuperCompany', res)
self.assertEquals('IBM', res)
def test_get_company_by_email_with_long_suffix_mapped(self):
record_processor_inst = make_record_processor()
record_processor_inst = self.make_record_processor(
companies=[{'company_name': 'NEC', 'domains': ['nec.co.jp']}]
)
email = 'man@mxw.nes.nec.co.jp'
res = record_processor_inst._get_company_by_email(email)
self.assertEquals('NEC', res)
def test_get_company_by_email_with_long_suffix_mapped_2(self):
record_processor_inst = make_record_processor()
record_processor_inst = self.make_record_processor(
companies=[{'company_name': 'NEC',
'domains': ['nec.co.jp', 'nec.com']}]
)
email = 'man@mxw.nes.nec.com'
res = record_processor_inst._get_company_by_email(email)
self.assertEquals('NEC', res)
def test_get_company_by_email_not_mapped(self):
record_processor_inst = make_record_processor()
record_processor_inst = self.make_record_processor()
email = 'foo@boo.com'
res = record_processor_inst._get_company_by_email(email)
self.assertEquals(None, res)
def test_update_commit_existing_user(self):
record_processor_inst = make_record_processor()
commit_generator = generate_commits()
commit = list(record_processor_inst.process(commit_generator))[0]
# get_lp_info
self.assertEquals('SuperCompany', commit['company_name'])
self.assertEquals('john_doe', commit['launchpad_id'])
def test_get_lp_info_invalid_email(self):
self.read_launchpad.return_value = None
record_processor_inst = self.make_record_processor(users=[])
self.assertEquals((None, None),
record_processor_inst._get_lp_info('error.root'))
def test_update_commit_existing_user_old_job(self):
record_processor_inst = make_record_processor()
commit_generator = generate_commits(date=1000000000)
commit = list(record_processor_inst.process(commit_generator))[0]
# commit processing
self.assertEquals('*independent', commit['company_name'])
self.assertEquals('john_doe', commit['launchpad_id'])
def test_update_commit_existing_user_new_email_known_company(self):
# User is known to LP, his email is new to us, and maps to other
# company. Should return other company instead of those mentioned
# in user db
email = 'johndoe@nec.co.jp'
commit_generator = generate_commits(email=email)
launchpad_id = 'john_doe'
self.read_launchpad.return_value = {'name': launchpad_id,
'display_name': launchpad_id}
user = make_user()
record_processor_inst = make_record_processor(
make_runtime_storage(users=[user]))
commit = list(record_processor_inst.process(commit_generator))[0]
self.read_launchpad.assert_called_once_with(LP_URI % email)
self.assertIn(email, user['emails'])
self.assertEquals('NEC', commit['company_name'])
self.assertEquals(launchpad_id, commit['launchpad_id'])
def test_update_commit_existing_user_new_email_unknown_company(self):
# User is known to LP, but his email is new to us. Should match
# the user and return current company
email = 'johndoe@yahoo.com'
commit_generator = generate_commits(email=email)
launchpad_id = 'john_doe'
self.read_launchpad.return_value = {'name': launchpad_id,
'display_name': launchpad_id}
user = make_user()
record_processor_inst = make_record_processor(
make_runtime_storage(users=[user]))
commit = list(record_processor_inst.process(commit_generator))[0]
self.read_launchpad.assert_called_once_with(LP_URI % email)
self.assertIn(email, user['emails'])
self.assertEquals('SuperCompany', commit['company_name'])
self.assertEquals(launchpad_id, commit['launchpad_id'])
def test_update_commit_existing_user_new_email_known_company_update(self):
# User is known to LP, his email is new to us and belongs to company B.
# Should match the user and return company B and update user
email = 'johndoe@nec.com'
commit_generator = generate_commits(email=email)
launchpad_id = 'john_doe'
self.read_launchpad.return_value = {'name': launchpad_id,
'display_name': launchpad_id}
user = {
def test_process_commit_existing_user(self):
record_processor_inst = self.make_record_processor(
users=[
{
'user_id': 'john_doe',
'launchpad_id': launchpad_id,
'launchpad_id': 'john_doe',
'user_name': 'John Doe',
'emails': ['johndoe@gmail.com'],
'emails': ['johndoe@gmail.com', 'johndoe@nec.co.jp'],
'companies': [
{'company_name': '*independent', 'end_date': 0}
{'company_name': '*independent',
'end_date': 1234567890},
{'company_name': 'NEC',
'end_date': 0},
]
}
record_processor_inst = make_record_processor(
make_runtime_storage(users=[user]))
])
commit = list(record_processor_inst.process(commit_generator))[0]
processed_commit = list(record_processor_inst.process(
generate_commits(author_email='johndoe@gmail.com',
author_name='John Doe')))[0]
self.read_launchpad.assert_called_once_with(LP_URI % email)
self.assertIn(email, user['emails'])
self.assertEquals('NEC', user['companies'][0]['company_name'],
expected_commit = {
'launchpad_id': 'john_doe',
'author_email': 'johndoe@gmail.com',
'author_name': 'John Doe',
'company_name': 'NEC',
}
self.assertRecordsMatch(expected_commit, processed_commit)
def test_process_commit_existing_user_old_job(self):
record_processor_inst = self.make_record_processor(
users=[
{
'user_id': 'john_doe',
'launchpad_id': 'john_doe',
'user_name': 'John Doe',
'emails': ['johndoe@gmail.com', 'johndoe@nec.co.jp'],
'companies': [
{'company_name': '*independent',
'end_date': 1234567890},
{'company_name': 'NEC',
'end_date': 0},
]
}
])
processed_commit = list(record_processor_inst.process(
generate_commits(author_email='johndoe@gmail.com',
author_name='John Doe',
date=1000000000)))[0]
expected_commit = {
'launchpad_id': 'john_doe',
'author_email': 'johndoe@gmail.com',
'author_name': 'John Doe',
'company_name': '*independent',
}
self.assertRecordsMatch(expected_commit, processed_commit)
def test_process_commit_existing_user_new_email_known_company(self):
# User is known to LP, his email is new to us, and maps to other
# company. Should return other company instead of those mentioned
# in user profile
record_processor_inst = self.make_record_processor(
users=[
{'user_id': 'john_doe',
'launchpad_id': 'john_doe',
'user_name': 'John Doe',
'emails': ['johndoe@nec.co.jp'],
'companies': [{'company_name': 'NEC', 'end_date': 0}]}
],
companies=[{'company_name': 'IBM', 'domains': ['ibm.com']}],
lp_info={'johndoe@ibm.com':
{'name': 'john_doe', 'display_name': 'John Doe'}})
processed_commit = list(record_processor_inst.process(
generate_commits(author_email='johndoe@ibm.com',
author_name='John Doe')))[0]
expected_commit = {
'launchpad_id': 'john_doe',
'author_email': 'johndoe@ibm.com',
'author_name': 'John Doe',
'company_name': 'IBM',
}
self.assertRecordsMatch(expected_commit, processed_commit)
self.assertIn('johndoe@ibm.com',
record_processor_inst.users_index['john_doe']['emails'])
def test_process_commit_existing_user_new_email_unknown_company(self):
# User is known to LP, but his email is new to us. Should match
# the user and return company from user profile
record_processor_inst = self.make_record_processor(
users=[
{'user_id': 'john_doe',
'launchpad_id': 'john_doe',
'user_name': 'John Doe',
'emails': ['johndoe@nec.co.jp'],
'companies': [{'company_name': 'NEC', 'end_date': 0}]}
],
companies=[{'company_name': 'IBM', 'domains': ['ibm.com']}],
lp_info={'johndoe@gmail.com':
{'name': 'john_doe', 'display_name': 'John Doe'}})
processed_commit = list(record_processor_inst.process(
generate_commits(author_email='johndoe@gmail.com',
author_name='John Doe')))[0]
expected_commit = {
'launchpad_id': 'john_doe',
'author_email': 'johndoe@gmail.com',
'author_name': 'John Doe',
'company_name': 'NEC',
}
self.assertRecordsMatch(expected_commit, processed_commit)
self.assertIn('johndoe@gmail.com',
record_processor_inst.users_index['john_doe']['emails'])
def test_process_commit_existing_user_new_email_known_company_update(self):
record_processor_inst = self.make_record_processor(
users=[
{'user_id': 'john_doe',
'launchpad_id': 'john_doe',
'user_name': 'John Doe',
'emails': ['johndoe@gmail.com'],
'companies': [{'company_name': '*independent',
'end_date': 0}]}
],
companies=[{'company_name': 'IBM', 'domains': ['ibm.com']}],
lp_info={'johndoe@ibm.com':
{'name': 'john_doe', 'display_name': 'John Doe'}})
processed_commit = list(record_processor_inst.process(
generate_commits(author_email='johndoe@ibm.com',
author_name='John Doe')))[0]
expected_commit = {
'launchpad_id': 'john_doe',
'author_email': 'johndoe@ibm.com',
'author_name': 'John Doe',
'company_name': 'IBM',
}
self.assertRecordsMatch(expected_commit, processed_commit)
user = record_processor_inst.users_index['john_doe']
self.assertIn('johndoe@gmail.com', user['emails'])
self.assertEquals('IBM', user['companies'][0]['company_name'],
message='User affiliation should be updated')
self.assertEquals('NEC', commit['company_name'])
self.assertEquals(launchpad_id, commit['launchpad_id'])
def test_update_commit_new_user(self):
def test_process_commit_new_user(self):
# User is known to LP, but new to us
# Should add new user and set company depending on email
email = 'smith@nec.com'
commit_generator = generate_commits(email=email)
launchpad_id = 'smith'
self.read_launchpad.return_value = {'name': launchpad_id,
'display_name': 'Smith'}
record_processor_inst = make_record_processor(
make_runtime_storage(users=[]))
record_processor_inst = self.make_record_processor(
companies=[{'company_name': 'IBM', 'domains': ['ibm.com']}],
lp_info={'johndoe@ibm.com':
{'name': 'john_doe', 'display_name': 'John Doe'}})
commit = list(record_processor_inst.process(commit_generator))[0]
processed_commit = list(record_processor_inst.process(
generate_commits(author_email='johndoe@ibm.com',
author_name='John Doe')))[0]
self.read_launchpad.assert_called_once_with(LP_URI % email)
self.assertEquals('NEC', commit['company_name'])
self.assertEquals(launchpad_id, commit['launchpad_id'])
expected_commit = {
'launchpad_id': 'john_doe',
'author_email': 'johndoe@ibm.com',
'author_name': 'John Doe',
'company_name': 'IBM',
}
def test_update_commit_new_user_unknown_to_lb(self):
self.assertRecordsMatch(expected_commit, processed_commit)
user = record_processor_inst.users_index['john_doe']
self.assertIn('johndoe@ibm.com', user['emails'])
self.assertEquals('IBM', user['companies'][0]['company_name'])
def test_process_commit_new_user_unknown_to_lb(self):
# User is new to us and not known to LP
# Should set user name and empty LPid
email = 'inkognito@avs.com'
commit_generator = generate_commits(email=email)
self.read_launchpad.return_value = None
record_processor_inst = make_record_processor(
make_runtime_storage(users=[]))
record_processor_inst = self.make_record_processor(
companies=[{'company_name': 'IBM', 'domains': ['ibm.com']}])
commit = list(record_processor_inst.process(commit_generator))[0]
processed_commit = list(record_processor_inst.process(
generate_commits(author_email='johndoe@ibm.com',
author_name='John Doe')))[0]
self.read_launchpad.assert_called_once_with(LP_URI % email)
self.assertEquals('*independent', commit['company_name'])
self.assertEquals(None, commit['launchpad_id'])
expected_commit = {
'launchpad_id': None,
'author_email': 'johndoe@ibm.com',
'author_name': 'John Doe',
'company_name': 'IBM',
}
def test_update_commit_invalid_email(self):
# User's email is malformed
email = 'error.root'
commit_generator = generate_commits(email=email)
self.read_launchpad.return_value = None
record_processor_inst = make_record_processor(
make_runtime_storage(users=[]))
self.assertRecordsMatch(expected_commit, processed_commit)
self.assertEquals(1, len(record_processor_inst.users_index))
user = record_processor_inst.users_index['johndoe@ibm.com']
self.assertIn('johndoe@ibm.com', user['emails'])
self.assertEquals('IBM', user['companies'][0]['company_name'])
self.assertEquals(None, user['launchpad_id'])
commit = list(record_processor_inst.process(commit_generator))[0]
# process records complex scenarios
self.assertEquals(0, self.read_launchpad.called)
self.assertEquals('*independent', commit['company_name'])
self.assertEquals(None, commit['launchpad_id'])
def test_process_blueprint_one_draft_spawned_lp_doesnt_know_user(self):
# In: blueprint record
# LP doesn't know user
# Out: blueprint-draft record
# new user profile created
record_processor_inst = self.make_record_processor()
processed_records = list(record_processor_inst.process([
{'record_type': 'bp',
'self_link': 'http://launchpad.net/blueprint',
'owner': 'john_doe',
'date_created': 1234567890}
]))
self.assertRecordsMatch(
{'record_type': 'bpd',
'launchpad_id': 'john_doe',
'author_name': 'john_doe',
'company_name': '*independent'},
processed_records[0])
self.assertEquals(1, len(record_processor_inst.users_index))
user = record_processor_inst.users_index['john_doe']
self.assertEquals({
'user_id': 'john_doe',
'launchpad_id': 'john_doe',
'user_name': 'john_doe',
'emails': [],
'companies': [{'company_name': '*independent', 'end_date': 0}]
}, user)
def test_process_blueprint_one_draft_spawned_lp_knows_user(self):
# In: blueprint record
# LP knows user
# Out: blueprint-draft record
# new user profile created, name is taken from LP profile
record_processor_inst = self.make_record_processor(
lp_user_name={
'john_doe': {'name': 'john_doe', 'display_name': 'John Doe'}})
processed_records = list(record_processor_inst.process([
{'record_type': 'bp',
'self_link': 'http://launchpad.net/blueprint',
'owner': 'john_doe',
'date_created': 1234567890}
]))
self.assertRecordsMatch(
{'record_type': 'bpd',
'launchpad_id': 'john_doe',
'author_name': 'John Doe',
'company_name': '*independent'},
processed_records[0])
self.assertEquals(1, len(record_processor_inst.users_index))
user = record_processor_inst.users_index['john_doe']
self.assertEquals({
'user_id': 'john_doe',
'launchpad_id': 'john_doe',
'user_name': 'John Doe',
'emails': [],
'companies': [{'company_name': '*independent', 'end_date': 0}]
}, user)
def test_process_blueprint_then_review(self):
record_processor_inst = self.make_record_processor(
lp_user_name={
'john_doe': {'name': 'john_doe', 'display_name': 'John Doe'}})
processed_records = list(record_processor_inst.process([
{'record_type': 'bp',
'self_link': 'http://launchpad.net/blueprint',
'owner': 'john_doe',
'date_created': 1234567890},
{'record_type': 'review',
'id': 'I1045730e47e9e6ad31fcdfbaefdad77e2f3b2c3e',
'subject': 'Fix AttributeError in Keypair._add_details()',
'owner': {'name': 'John Doe',
'email': 'john_doe@gmail.com',
'username': 'john_doe'},
'createdOn': 1379404951,
'module': 'nova'}
]))
self.assertRecordsMatch(
{'record_type': 'bpd',
'launchpad_id': 'john_doe',
'author_name': 'John Doe',
'company_name': '*independent'},
processed_records[0])
self.assertRecordsMatch(
{'record_type': 'review',
'launchpad_id': 'john_doe',
'author_name': 'John Doe',
'author_email': 'john_doe@gmail.com',
'company_name': '*independent'},
processed_records[1])
user = {'user_id': 'john_doe',
'launchpad_id': 'john_doe',
'user_name': 'John Doe',
'emails': ['john_doe@gmail.com'],
'companies': [{'company_name': '*independent', 'end_date': 0}]}
self.assertEquals({'john_doe': user, 'john_doe@gmail.com': user},
record_processor_inst.users_index)
def test_process_blueprint_then_commit(self):
record_processor_inst = self.make_record_processor(
lp_user_name={
'john_doe': {'name': 'john_doe', 'display_name': 'John Doe'}},
lp_info={'john_doe@gmail.com':
{'name': 'john_doe', 'display_name': 'John Doe'}})
processed_records = list(record_processor_inst.process([
{'record_type': 'bp',
'self_link': 'http://launchpad.net/blueprint',
'owner': 'john_doe',
'date_created': 1234567890},
{'record_type': 'commit',
'commit_id': 'de7e8f297c193fb310f22815334a54b9c76a0be1',
'author_name': 'John Doe',
'author_email': 'john_doe@gmail.com',
'date': 1234567890,
'lines_added': 25,
'lines_deleted': 9,
'release_name': 'havana'}
]))
self.assertRecordsMatch(
{'record_type': 'bpd',
'launchpad_id': 'john_doe',
'author_name': 'John Doe',
'company_name': '*independent'},
processed_records[0])
self.assertRecordsMatch(
{'record_type': 'commit',
'launchpad_id': 'john_doe',
'author_name': 'John Doe',
'author_email': 'john_doe@gmail.com',
'company_name': '*independent'},
processed_records[1])
user = {'user_id': 'john_doe',
'launchpad_id': 'john_doe',
'user_name': 'John Doe',
'emails': ['john_doe@gmail.com'],
'companies': [{'company_name': '*independent', 'end_date': 0}]}
self.assertEquals({'john_doe': user, 'john_doe@gmail.com': user},
record_processor_inst.users_index)
# update records
def _generate_record_commit(self):
yield {'commit_id': u'0afdc64bfd041b03943ceda7849c4443940b6053',
@@ -272,68 +499,187 @@ class TestRecordProcessor(testtools.TestCase):
def test_update_record_no_changes(self):
commit_generator = self._generate_record_commit()
release_index = {'0afdc64bfd041b03943ceda7849c4443940b6053': 'havana'}
record_processor_inst = make_record_processor(
make_runtime_storage(users=[]))
record_processor_inst = self.make_record_processor(
users=[],
companies=[{'company_name': 'SuperCompany',
'domains': ['super.no']}])
updated = list(record_processor_inst.update(commit_generator,
release_index))
self.assertEquals(0, len(updated))
def test_process_mail(self):
record_processor_inst = make_record_processor()
commit_generator = generate_emails(
subject='[openstack-dev] [Stackalytics] Configuration files')
commit = list(record_processor_inst.process(commit_generator))[0]
# mail processing
self.assertEquals('SuperCompany', commit['company_name'])
self.assertEquals('john_doe', commit['launchpad_id'])
self.assertEquals('stackalytics', commit['module'])
def test_process_mail(self):
record_processor_inst = self.make_record_processor(
users=[
{
'user_id': 'john_doe',
'launchpad_id': 'john_doe',
'user_name': 'John Doe',
'emails': ['johndoe@gmail.com', 'johndoe@nec.co.jp'],
'companies': [
{'company_name': 'NEC', 'end_date': 0},
]
}
],
repos=[{"module": "stackalytics"}]
)
processed_commit = list(record_processor_inst.process(
generate_emails(
author_email='johndoe@gmail.com',
author_name='John Doe',
subject='[openstack-dev] [Stackalytics] Configuration files')
))[0]
expected_commit = {
'launchpad_id': 'john_doe',
'author_email': 'johndoe@gmail.com',
'author_name': 'John Doe',
'company_name': 'NEC',
'module': 'stackalytics',
}
self.assertRecordsMatch(expected_commit, processed_commit)
def test_process_mail_guessed(self):
runtime_storage_inst = make_runtime_storage(
repos=[{'module': 'nova'}, {'module': 'neutron'}])
record_processor_inst = make_record_processor(runtime_storage_inst)
commit_generator = generate_emails(
subject='[openstack-dev] [Neutron] [Nova] Integration issue')
commit = list(record_processor_inst.process(commit_generator))[0]
record_processor_inst = self.make_record_processor(
users=[
{
'user_id': 'john_doe',
'launchpad_id': 'john_doe',
'user_name': 'John Doe',
'emails': ['johndoe@gmail.com', 'johndoe@nec.co.jp'],
'companies': [
{'company_name': 'NEC', 'end_date': 0},
]
}
],
repos=[{'module': 'nova'}, {'module': 'neutron'}]
)
self.assertEquals('neutron', commit['module'])
processed_commit = list(record_processor_inst.process(
generate_emails(
author_email='johndoe@gmail.com',
author_name='John Doe',
subject='[openstack-dev] [Neutron] [Nova] Integration issue')
))[0]
expected_commit = {
'launchpad_id': 'john_doe',
'author_email': 'johndoe@gmail.com',
'author_name': 'John Doe',
'company_name': 'NEC',
'module': 'neutron',
}
self.assertRecordsMatch(expected_commit, processed_commit)
def test_process_mail_guessed_module_in_body_override(self):
runtime_storage_inst = make_runtime_storage(
repos=[{'module': 'nova'}, {'module': 'heat'}])
record_processor_inst = make_record_processor(runtime_storage_inst)
commit_generator = generate_emails(
subject='[openstack-dev] [heat] Comments/questions on the',
module='nova')
commit = list(record_processor_inst.process(commit_generator))[0]
record_processor_inst = self.make_record_processor(
users=[
{
'user_id': 'john_doe',
'launchpad_id': 'john_doe',
'user_name': 'John Doe',
'emails': ['johndoe@gmail.com', 'johndoe@nec.co.jp'],
'companies': [
{'company_name': 'NEC', 'end_date': 0},
]
}
],
repos=[{'module': 'nova'}, {'module': 'neutron'}]
)
self.assertEquals('heat', commit['module'])
processed_commit = list(record_processor_inst.process(
generate_emails(
author_email='johndoe@gmail.com',
author_name='John Doe',
module='nova',
subject='[openstack-dev] [neutron] Comments/questions on the')
))[0]
expected_commit = {
'launchpad_id': 'john_doe',
'author_email': 'johndoe@gmail.com',
'author_name': 'John Doe',
'company_name': 'NEC',
'module': 'neutron',
}
self.assertRecordsMatch(expected_commit, processed_commit)
def test_process_mail_guessed_module_in_body(self):
runtime_storage_inst = make_runtime_storage(
repos=[{'module': 'nova'}, {'module': 'heat'}])
record_processor_inst = make_record_processor(runtime_storage_inst)
commit_generator = generate_emails(
subject='[openstack-dev] Comments/questions on the heat',
module='nova')
commit = list(record_processor_inst.process(commit_generator))[0]
record_processor_inst = self.make_record_processor(
users=[
{
'user_id': 'john_doe',
'launchpad_id': 'john_doe',
'user_name': 'John Doe',
'emails': ['johndoe@gmail.com', 'johndoe@nec.co.jp'],
'companies': [
{'company_name': 'NEC', 'end_date': 0},
]
}
],
repos=[{'module': 'nova'}, {'module': 'neutron'}]
)
self.assertEquals('nova', commit['module'])
processed_commit = list(record_processor_inst.process(
generate_emails(
author_email='johndoe@gmail.com',
author_name='John Doe',
module='nova',
subject='[openstack-dev] Comments/questions on the')
))[0]
expected_commit = {
'launchpad_id': 'john_doe',
'author_email': 'johndoe@gmail.com',
'author_name': 'John Doe',
'company_name': 'NEC',
'module': 'nova',
}
self.assertRecordsMatch(expected_commit, processed_commit)
def test_process_mail_unmatched(self):
record_processor_inst = make_record_processor()
commit_generator = generate_emails(
subject='[openstack-dev] [Photon] Configuration files')
commit = list(record_processor_inst.process(commit_generator))[0]
record_processor_inst = self.make_record_processor(
users=[
{
'user_id': 'john_doe',
'launchpad_id': 'john_doe',
'user_name': 'John Doe',
'emails': ['johndoe@gmail.com', 'johndoe@nec.co.jp'],
'companies': [
{'company_name': 'NEC', 'end_date': 0},
]
}
],
repos=[{'module': 'nova'}, {'module': 'neutron'}]
)
self.assertEquals('SuperCompany', commit['company_name'])
self.assertEquals('john_doe', commit['launchpad_id'])
self.assertEquals('unknown', commit['module'])
processed_commit = list(record_processor_inst.process(
generate_emails(
author_email='johndoe@gmail.com',
author_name='John Doe',
subject='[openstack-dev] Comments/questions on the')
))[0]
expected_commit = {
'launchpad_id': 'john_doe',
'author_email': 'johndoe@gmail.com',
'author_name': 'John Doe',
'company_name': 'NEC',
'module': 'unknown',
}
self.assertRecordsMatch(expected_commit, processed_commit)
def test_get_modules(self):
record_processor_inst = make_record_processor()
record_processor_inst = self.make_record_processor()
with mock.patch('stackalytics.processor.utils.load_repos') as patch:
patch.return_value = [{'module': 'nova'},
{'module': 'python-novaclient'},
@@ -341,15 +687,36 @@ class TestRecordProcessor(testtools.TestCase):
modules = record_processor_inst._get_modules()
self.assertEqual(set(['nova', 'neutron']), set(modules))
def assertRecordsMatch(self, expected, actual):
for key, value in expected.iteritems():
self.assertEquals(value, actual[key],
'Values for key %s do not match' % key)
# Helpers
# Helpers
def generate_commits(email='johndoe@gmail.com', date=1999999999):
def make_record_processor(self, users=None, companies=None, releases=None,
repos=None, lp_info=None, lp_user_name=None):
rp = record_processor.RecordProcessor(make_runtime_storage(
users=users, companies=companies, releases=releases, repos=repos))
if lp_info is not None:
self.lp_profile_by_email.side_effect = (
lambda x: lp_info.get(x))
if lp_user_name is not None:
self.lp_profile_by_launchpad_id.side_effect = (
lambda x: lp_user_name.get(x))
return rp
def generate_commits(author_name='John Doe', author_email='johndoe@gmail.com',
date=1999999999):
yield {
'record_type': 'commit',
'commit_id': 'de7e8f297c193fb310f22815334a54b9c76a0be1',
'author_name': 'John Doe',
'author_email': email,
'author_name': author_name,
'author_email': author_email,
'date': date,
'lines_added': 25,
'lines_deleted': 9,
@@ -357,13 +724,13 @@ def generate_commits(email='johndoe@gmail.com', date=1999999999):
}
def generate_emails(email='johndoe@gmail.com', date=1999999999,
subject='[openstack-dev]', module=None):
def generate_emails(author_name='John Doe', author_email='johndoe@gmail.com',
date=1999999999, subject='[openstack-dev]', module=None):
yield {
'record_type': 'email',
'message_id': 'de7e8f297c193fb310f22815334a54b9c76a0be1',
'author_name': 'John Doe',
'author_email': email,
'author_name': author_name,
'author_email': author_email,
'date': date,
'subject': subject,
'module': module,
@@ -374,9 +741,11 @@ def make_runtime_storage(users=None, companies=None, releases=None,
repos=None):
def get_by_key(collection):
if collection == 'companies':
return _make_companies(companies or COMPANIES)
return _make_companies(companies or [
{"company_name": "*independent", "domains": [""]},
])
elif collection == 'users':
return _make_users(users or USERS)
return _make_users(users or [])
elif collection == 'releases':
return releases or RELEASES
elif collection == 'repos':
@@ -389,26 +758,6 @@ def make_runtime_storage(users=None, companies=None, releases=None,
return rs
def make_record_processor(runtime_storage_inst=None):
return record_processor.RecordProcessor(runtime_storage_inst or
make_runtime_storage())
def make_user():
return {
'user_id': 'john_doe',
'launchpad_id': 'john_doe',
'user_name': 'John Doe',
'emails': ['johndoe@gmail.com', 'jdoe@super.no'],
'companies': [
{'company_name': '*independent',
'end_date': 1234567890},
{'company_name': 'SuperCompany',
'end_date': 0},
]
}
def _make_users(users):
users_index = {}
for user in users: