Implemented report of blueprint activity
* Introduced report that shows blueprint details followed by list of commits, emails and reviews related to that blueprint * Mail body is stored in runtime storage * External links are opened in new window/tab Implements blueprint report-blueprint-activity Change-Id: I1515d7fc51a4b46932fc15551c94f0fa6b16e4ed
This commit is contained in:
@@ -33,6 +33,7 @@ class CachedMemoryStorage(MemoryStorage):
|
|||||||
self.user_id_index = {}
|
self.user_id_index = {}
|
||||||
self.company_index = {}
|
self.company_index = {}
|
||||||
self.release_index = {}
|
self.release_index = {}
|
||||||
|
self.blueprint_id_index = {}
|
||||||
|
|
||||||
self.indexes = {
|
self.indexes = {
|
||||||
'primary_key': self.primary_key_index,
|
'primary_key': self.primary_key_index,
|
||||||
@@ -49,6 +50,11 @@ class CachedMemoryStorage(MemoryStorage):
|
|||||||
self.records[record['record_id']] = record
|
self.records[record['record_id']] = record
|
||||||
for key, index in self.indexes.iteritems():
|
for key, index in self.indexes.iteritems():
|
||||||
self._add_to_index(index, record, key)
|
self._add_to_index(index, record, key)
|
||||||
|
for bp_id in (record.get('blueprint_id') or []):
|
||||||
|
if bp_id in self.blueprint_id_index:
|
||||||
|
self.blueprint_id_index[bp_id].add(record['record_id'])
|
||||||
|
else:
|
||||||
|
self.blueprint_id_index[bp_id] = set([record['record_id']])
|
||||||
|
|
||||||
def update(self, records):
|
def update(self, records):
|
||||||
have_updates = False
|
have_updates = False
|
||||||
@@ -100,6 +106,10 @@ class CachedMemoryStorage(MemoryStorage):
|
|||||||
def get_record_ids_by_releases(self, releases):
|
def get_record_ids_by_releases(self, releases):
|
||||||
return self._get_record_ids_from_index(releases, self.release_index)
|
return self._get_record_ids_from_index(releases, self.release_index)
|
||||||
|
|
||||||
|
def get_record_ids_by_blueprint_ids(self, blueprint_ids):
|
||||||
|
return self._get_record_ids_from_index(blueprint_ids,
|
||||||
|
self.blueprint_id_index)
|
||||||
|
|
||||||
def get_record_ids(self):
|
def get_record_ids(self):
|
||||||
return self.records.keys()
|
return self.records.keys()
|
||||||
|
|
||||||
|
75
dashboard/templates/blueprint_report.html
Normal file
75
dashboard/templates/blueprint_report.html
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>{{ blueprint.title }}</title>
|
||||||
|
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='css/style.css') }}">
|
||||||
|
<style>
|
||||||
|
.label {
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 135%;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
margin-top: 1em;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body style="margin: 2em;">
|
||||||
|
|
||||||
|
<h1>Blueprint “{{ blueprint.name }}”</h1>
|
||||||
|
|
||||||
|
<div><span class="label">Title:</span> {{ blueprint.title }}</div>
|
||||||
|
<div><span class="label">URL:</span> <a href="https://blueprints.launchpad.net/{{ blueprint.module }}/+spec/{{ blueprint.name }}">https://blueprints.launchpad.net/{{ blueprint.module }}/+spec/{{ blueprint.name }}</a></div>
|
||||||
|
<div><span class="label">Status:</span> <span class="status{{blueprint.lifecycle_status}}">{{blueprint.lifecycle_status}}</span></div>
|
||||||
|
<div><span class="label">Priority:</span> <span class="status{{blueprint.priority}}">{{blueprint.priority}}</span></div>
|
||||||
|
<div><span class="label">Definition Status:</span> <span class="specstatus{{blueprint.definition_status}}">{{blueprint.definition_status}}</span></div>
|
||||||
|
<div><span class="label">Implementation Status:</span> <span class="specdelivery{{blueprint.implementation_status}}">{{blueprint.implementation_status}}</span></div>
|
||||||
|
<div><span class="label">Direction:</span> {% if blueprint.direction_approved %} Approved {% else %} Needs Approval {% endif %}</div>
|
||||||
|
<div><span class="label">Registered By:</span> {{blueprint.author_name}} ({{ blueprint.company_name }})</div>
|
||||||
|
<div><span class="label">Registered On:</span> {{blueprint.date_str}}</div>
|
||||||
|
|
||||||
|
{% if blueprint.whiteboard %}
|
||||||
|
<h2>Whiteboard</h2>
|
||||||
|
<div class="message">{{ blueprint.whiteboard }}</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<h2>Activity Log</h2>
|
||||||
|
|
||||||
|
{% if not activity %}
|
||||||
|
<div>No activities related to this blueprint.</div>
|
||||||
|
{% endif %}
|
||||||
|
{% for item in activity %}
|
||||||
|
<div style="margin-bottom: 1em;">
|
||||||
|
<div style='float: left; '><img src="{{ item.author_email | gravatar(size=64) }}">
|
||||||
|
</div>
|
||||||
|
<div style="margin-left: 80px;">
|
||||||
|
<div style="font-weight: bold;">{{ item.date_str}}</div>
|
||||||
|
<div style="font-weight: bold;">{{ item.author_name }} ({{ item.company_name }})</div>
|
||||||
|
{% if item.record_type == "commit" %}
|
||||||
|
<div style='font-weight: bold;'>Commit “{{ item.subject }}”</div>
|
||||||
|
<div class="message">{{ item.message }}</div>
|
||||||
|
<div><span style="color: green">+<span>{{ item.lines_added }}</span></span>
|
||||||
|
<span style="color: blue">- <span>{{ item.lines_deleted }}</span></span>
|
||||||
|
</div>
|
||||||
|
{% if item.correction_comment %}
|
||||||
|
<div style='font-weight: bold; color: red;'>Commit corrected:
|
||||||
|
<span>{{ item.correction_comment }}</span></div>
|
||||||
|
{% endif %}
|
||||||
|
{% elif item.record_type == "mark" %}
|
||||||
|
<div style='font-weight: bold;'>Review “{{item.subject}}”</div>
|
||||||
|
<div>Patch submitted by {{ parent_author_link }}</div>
|
||||||
|
<div>Change Id: <a href="{{item.url}}">{{item.review_id}}</a></div>
|
||||||
|
<div style="color: {% if item.value > 0 %} green {% else %} blue {% endif %}">
|
||||||
|
{{item.description}}: <span class="review_mark">{{item.value}}</span></div>
|
||||||
|
{% elif item.record_type == "email" %}
|
||||||
|
<div style='font-weight: bold;'>Email “{{item.subject}}”</div>
|
||||||
|
{% if item.body %}
|
||||||
|
<div class="message">{{ item.body }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
</body>
|
||||||
|
</html>
|
@@ -43,6 +43,11 @@
|
|||||||
$('#activity_header').hide();
|
$('#activity_header').hide();
|
||||||
}
|
}
|
||||||
$("#activity_template").tmpl(data["activity"]).appendTo("#activity_container");
|
$("#activity_template").tmpl(data["activity"]).appendTo("#activity_container");
|
||||||
|
$('.ext_link').click(function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
window.open(this.href, '_blank');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -90,7 +95,7 @@
|
|||||||
<div style='float: left; '><img src="${gravatar}" style="width: 32px; height: 32px;"></div>
|
<div style='float: left; '><img src="${gravatar}" style="width: 32px; height: 32px;"></div>
|
||||||
<div style="margin-left: 40px;">
|
<div style="margin-left: 40px;">
|
||||||
<div style="font-weight: bold;">{%html author_link %} ({%html company_link %})</div>
|
<div style="font-weight: bold;">{%html author_link %} ({%html company_link %})</div>
|
||||||
<div style="font-weight: bold;">${date_str} to <a href="https://launchpad.net/${module}">${module}</a></div>
|
<div style="font-weight: bold;">${date_str} in {%html module_link%}</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-left: 40px;">
|
<div style="margin-left: 40px;">
|
||||||
{%if record_type == "commit" %}
|
{%if record_type == "commit" %}
|
||||||
@@ -125,7 +130,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{%/if%}
|
{%/if%}
|
||||||
{%elif ((record_type == "bpd") || (record_type == "bpc")) %}
|
{%elif ((record_type == "bpd") || (record_type == "bpc")) %}
|
||||||
<div style='font-weight: bold;'>${title} (<a href='${web_link}'>${name}</a>)</div>
|
<div style='font-weight: bold;'>${title} (<a href="/report/blueprint/${module}/${name}" class="ext_link">${name}</a>)</div>
|
||||||
<div style='white-space: pre-wrap;'>${summary}</div>
|
<div style='white-space: pre-wrap;'>${summary}</div>
|
||||||
|
|
||||||
<div>Priority: <span class="specpriority${priority}">${priority}</span></div>
|
<div>Priority: <span class="specpriority${priority}">${priority}</span></div>
|
||||||
|
@@ -49,7 +49,7 @@ METRIC_LABELS = {
|
|||||||
'commits': 'Commits',
|
'commits': 'Commits',
|
||||||
'marks': 'Reviews',
|
'marks': 'Reviews',
|
||||||
'emails': 'Emails',
|
'emails': 'Emails',
|
||||||
'bpd': 'New Blueprints',
|
'bpd': 'Drafted Blueprints',
|
||||||
'bpc': 'Completed Blueprints',
|
'bpc': 'Completed Blueprints',
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -615,6 +615,9 @@ def _extend_record(record):
|
|||||||
record['company_link'] = make_link(
|
record['company_link'] = make_link(
|
||||||
record['company_name'], '/',
|
record['company_name'], '/',
|
||||||
{'company': record['company_name'], 'user_id': ''})
|
{'company': record['company_name'], 'user_id': ''})
|
||||||
|
record['module_link'] = make_link(
|
||||||
|
record['module'], '/',
|
||||||
|
{'module': record['module'], 'company': '', 'user_id': ''})
|
||||||
record['gravatar'] = gravatar(record.get('author_email', 'stackalytics'))
|
record['gravatar'] = gravatar(record.get('author_email', 'stackalytics'))
|
||||||
record['blueprint_id_count'] = len(record.get('blueprint_id', []))
|
record['blueprint_id_count'] = len(record.get('blueprint_id', []))
|
||||||
record['bug_id_count'] = len(record.get('bug_id', []))
|
record['bug_id_count'] = len(record.get('bug_id', []))
|
||||||
@@ -662,7 +665,7 @@ def get_activity_json(records):
|
|||||||
blueprint = record.copy()
|
blueprint = record.copy()
|
||||||
_extend_record(blueprint)
|
_extend_record(blueprint)
|
||||||
if 'mention_date' in record:
|
if 'mention_date' in record:
|
||||||
record['mention_date_str'] = format_datetime(
|
blueprint['mention_date_str'] = format_datetime(
|
||||||
record['mention_date'])
|
record['mention_date'])
|
||||||
result.append(blueprint)
|
result.append(blueprint)
|
||||||
|
|
||||||
@@ -904,12 +907,46 @@ def get_commit_report(records):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/report/blueprint/<module>/<blueprint_name>')
|
||||||
|
@templated()
|
||||||
|
@exception_handler()
|
||||||
|
def blueprint_report(module, blueprint_name):
|
||||||
|
memory_storage_inst = get_vault()['memory_storage']
|
||||||
|
runtime_storage_inst = get_vault()['runtime_storage']
|
||||||
|
|
||||||
|
blueprint_id = module + ':' + blueprint_name
|
||||||
|
|
||||||
|
for bpd in memory_storage_inst.get_records(
|
||||||
|
memory_storage_inst.get_record_ids_by_type('bpd')):
|
||||||
|
if bpd['id'] == blueprint_id:
|
||||||
|
_extend_record(bpd)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
flask.abort(404)
|
||||||
|
return
|
||||||
|
|
||||||
|
record_ids = memory_storage_inst.get_record_ids_by_blueprint_ids(
|
||||||
|
[blueprint_id])
|
||||||
|
|
||||||
|
activity = []
|
||||||
|
for record in memory_storage_inst.get_records(record_ids):
|
||||||
|
_extend_record(record)
|
||||||
|
if record['record_type'] == 'email':
|
||||||
|
record['body'] = (runtime_storage_inst.get_by_key('email:%s' %
|
||||||
|
record['primary_key']))
|
||||||
|
activity.append(record)
|
||||||
|
|
||||||
|
activity.sort(key=lambda x: x['date'])
|
||||||
|
|
||||||
|
return {'blueprint': bpd, 'activity': activity}
|
||||||
|
|
||||||
|
|
||||||
# Jinja Filters ---------
|
# Jinja Filters ---------
|
||||||
|
|
||||||
@app.template_filter('datetimeformat')
|
@app.template_filter('datetimeformat')
|
||||||
def format_datetime(timestamp):
|
def format_datetime(timestamp):
|
||||||
return datetime.datetime.utcfromtimestamp(
|
return datetime.datetime.utcfromtimestamp(
|
||||||
timestamp).strftime('%d %b %Y @ %H:%M')
|
timestamp).strftime('%d %b %Y %H:%M:%S')
|
||||||
|
|
||||||
|
|
||||||
@app.template_filter('launchpadmodule')
|
@app.template_filter('launchpadmodule')
|
||||||
@@ -960,15 +997,18 @@ def make_commit_message(record):
|
|||||||
|
|
||||||
# clear text
|
# clear text
|
||||||
s = cgi.escape(re.sub(re.compile('\n{2,}', flags=re.MULTILINE), '\n', s))
|
s = cgi.escape(re.sub(re.compile('\n{2,}', flags=re.MULTILINE), '\n', s))
|
||||||
|
s = re.sub(r'([/\/]+)', r'\1​', s)
|
||||||
|
|
||||||
# insert links
|
# insert links
|
||||||
s = re.sub(re.compile('(blueprint\s+)([\w-]+)', flags=re.IGNORECASE),
|
s = re.sub(re.compile('(blueprint\s+)([\w-]+)', flags=re.IGNORECASE),
|
||||||
r'\1<a href="https://blueprints.launchpad.net/' +
|
r'\1<a href="https://blueprints.launchpad.net/' +
|
||||||
module + r'/+spec/\2">\2</a>', s)
|
module + r'/+spec/\2" class="ext_link">\2</a>', s)
|
||||||
s = re.sub(re.compile('(bug\s+)#?([\d]{5,7})', flags=re.IGNORECASE),
|
s = re.sub(re.compile('(bug[\s#:]*)([\d]{5,7})', flags=re.IGNORECASE),
|
||||||
r'\1<a href="https://bugs.launchpad.net/bugs/\2">\2</a>', s)
|
r'\1<a href="https://bugs.launchpad.net/bugs/\2" '
|
||||||
|
r'class="ext_link">\2</a>', s)
|
||||||
s = re.sub(r'\s+(I[0-9a-f]{40})',
|
s = re.sub(r'\s+(I[0-9a-f]{40})',
|
||||||
r' <a href="https://review.openstack.org/#q,\1,n,z">\1</a>', s)
|
r' <a href="https://review.openstack.org/#q,\1,n,z" '
|
||||||
|
r'class="ext_link">\1</a>', s)
|
||||||
|
|
||||||
s = unwrap_text(s)
|
s = unwrap_text(s)
|
||||||
return s
|
return s
|
||||||
|
@@ -49,6 +49,7 @@ def log(repo):
|
|||||||
record[field] = utils.iso8601_to_timestamp(date)
|
record[field] = utils.iso8601_to_timestamp(date)
|
||||||
|
|
||||||
record['module'] = module
|
record['module'] = module
|
||||||
|
record['id'] = module + ':' + record['name']
|
||||||
|
|
||||||
LOG.debug('New blueprint: %s', record)
|
LOG.debug('New blueprint: %s', record)
|
||||||
yield record
|
yield record
|
||||||
|
@@ -17,7 +17,6 @@ import urllib
|
|||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
import psutil
|
import psutil
|
||||||
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
|
||||||
@@ -42,8 +41,8 @@ def get_pids():
|
|||||||
if p.cmdline and p.cmdline[0].find('/uwsgi'):
|
if p.cmdline and p.cmdline[0].find('/uwsgi'):
|
||||||
if p.parent:
|
if p.parent:
|
||||||
uwsgi_dict[p.pid] = p.parent.pid
|
uwsgi_dict[p.pid] = p.parent.pid
|
||||||
except _error.NoSuchProcess:
|
except Exception as e:
|
||||||
# the process may disappear after get_pid_list call, ignore it
|
LOG.debug('Exception while iterating process list: %s', e)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
result = set()
|
result = set()
|
||||||
|
@@ -19,7 +19,6 @@ import StringIO
|
|||||||
|
|
||||||
from email import utils as email_utils
|
from email import utils as email_utils
|
||||||
import re
|
import re
|
||||||
import time
|
|
||||||
import urlparse
|
import urlparse
|
||||||
|
|
||||||
from stackalytics.openstack.common import log as logging
|
from stackalytics.openstack.common import log as logging
|
||||||
@@ -81,6 +80,9 @@ def _link_content_changed(link, runtime_storage_inst):
|
|||||||
def _retrieve_mails(uri):
|
def _retrieve_mails(uri):
|
||||||
LOG.debug('Retrieving mail archive from uri: %s', uri)
|
LOG.debug('Retrieving mail archive from uri: %s', uri)
|
||||||
content = utils.read_uri(uri)
|
content = utils.read_uri(uri)
|
||||||
|
if not content:
|
||||||
|
LOG.error('Error reading mail archive from uri: %s', uri)
|
||||||
|
return
|
||||||
gzip_fd = gzip.GzipFile(fileobj=StringIO.StringIO(content))
|
gzip_fd = gzip.GzipFile(fileobj=StringIO.StringIO(content))
|
||||||
content = gzip_fd.read()
|
content = gzip_fd.read()
|
||||||
LOG.debug('Mail archive is loaded, start processing')
|
LOG.debug('Mail archive is loaded, start processing')
|
||||||
@@ -94,7 +96,8 @@ def _retrieve_mails(uri):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
author_name = rec.group(2)
|
author_name = rec.group(2)
|
||||||
date = int(time.mktime(email_utils.parsedate(rec.group(3))))
|
date = int(email_utils.mktime_tz(
|
||||||
|
email_utils.parsedate_tz(rec.group(3))))
|
||||||
subject = rec.group(4)
|
subject = rec.group(4)
|
||||||
message_id = rec.group(5)
|
message_id = rec.group(5)
|
||||||
body = rec.group(6)
|
body = rec.group(6)
|
||||||
@@ -105,15 +108,18 @@ def _retrieve_mails(uri):
|
|||||||
'author_email': author_email,
|
'author_email': author_email,
|
||||||
'subject': subject,
|
'subject': subject,
|
||||||
'date': date,
|
'date': date,
|
||||||
|
'body': body,
|
||||||
}
|
}
|
||||||
|
|
||||||
for pattern_name, pattern in MESSAGE_PATTERNS.iteritems():
|
for pattern_name, pattern in MESSAGE_PATTERNS.iteritems():
|
||||||
collection = set()
|
collection = set()
|
||||||
for item in re.finditer(pattern, body):
|
for item in re.finditer(pattern, body):
|
||||||
groups = item.groupdict()
|
groups = item.groupdict()
|
||||||
collection.add(groups['id'])
|
item_id = groups['id']
|
||||||
if 'module' in groups:
|
if 'module' in groups:
|
||||||
|
item_id = groups['module'] + ':' + item_id
|
||||||
email['module'] = groups['module']
|
email['module'] = groups['module']
|
||||||
|
collection.add(item_id)
|
||||||
email[pattern_name] = list(collection)
|
email[pattern_name] = list(collection)
|
||||||
|
|
||||||
yield email
|
yield email
|
||||||
@@ -125,5 +131,5 @@ def log(uri, runtime_storage_inst):
|
|||||||
for link in links:
|
for link in links:
|
||||||
if _link_content_changed(link, runtime_storage_inst):
|
if _link_content_changed(link, runtime_storage_inst):
|
||||||
for mail in _retrieve_mails(link):
|
for mail in _retrieve_mails(link):
|
||||||
LOG.debug('New mail: %s', mail)
|
LOG.debug('New mail: %s', mail['message_id'])
|
||||||
yield mail
|
yield mail
|
||||||
|
@@ -282,14 +282,21 @@ class RecordProcessor(object):
|
|||||||
self._update_record_and_user(record)
|
self._update_record_and_user(record)
|
||||||
self._guess_module(record)
|
self._guess_module(record)
|
||||||
|
|
||||||
|
if record.get('blueprint_id'):
|
||||||
|
self.runtime_storage_inst.set_by_key(
|
||||||
|
'email:%s' % record['primary_key'], record['body'])
|
||||||
|
|
||||||
|
del record['body']
|
||||||
|
|
||||||
yield record
|
yield record
|
||||||
|
|
||||||
def _process_blueprint(self, record):
|
def _process_blueprint(self, record):
|
||||||
bpd_author = record.get('drafter') or record.get('owner')
|
bpd_author = record.get('drafter') or record.get('owner')
|
||||||
|
|
||||||
bpd = dict([(k, v) for k, v in record.iteritems()])
|
bpd = dict([(k, v) for k, v in record.iteritems()
|
||||||
|
if k.find('_link') < 0])
|
||||||
bpd['record_type'] = 'bpd'
|
bpd['record_type'] = 'bpd'
|
||||||
bpd['primary_key'] = 'bpd:' + record['self_link']
|
bpd['primary_key'] = 'bpd:' + record['id']
|
||||||
bpd['launchpad_id'] = bpd_author
|
bpd['launchpad_id'] = bpd_author
|
||||||
bpd['date'] = record['date_created']
|
bpd['date'] = record['date_created']
|
||||||
|
|
||||||
@@ -298,9 +305,10 @@ class RecordProcessor(object):
|
|||||||
yield bpd
|
yield bpd
|
||||||
|
|
||||||
if record.get('assignee') and record['date_completed']:
|
if record.get('assignee') and record['date_completed']:
|
||||||
bpc = dict([(k, v) for k, v in record.iteritems()])
|
bpc = dict([(k, v) for k, v in record.iteritems()
|
||||||
|
if k.find('_link') < 0])
|
||||||
bpc['record_type'] = 'bpc'
|
bpc['record_type'] = 'bpc'
|
||||||
bpc['primary_key'] = 'bpc:' + record['self_link']
|
bpc['primary_key'] = 'bpc:' + record['id']
|
||||||
bpc['launchpad_id'] = record['assignee']
|
bpc['launchpad_id'] = record['assignee']
|
||||||
bpc['date'] = record['date_completed']
|
bpc['date'] = record['date_completed']
|
||||||
|
|
||||||
@@ -381,7 +389,7 @@ class RecordProcessor(object):
|
|||||||
'date': record['date']
|
'date': record['date']
|
||||||
}
|
}
|
||||||
if record['record_type'] in ['bpd', 'bpi']:
|
if record['record_type'] in ['bpd', 'bpi']:
|
||||||
valid_blueprints[record['name']] = {
|
valid_blueprints[record['id']] = {
|
||||||
'primary_key': record['primary_key'],
|
'primary_key': record['primary_key'],
|
||||||
'count': 0,
|
'count': 0,
|
||||||
'date': record['date']
|
'date': record['date']
|
||||||
@@ -419,7 +427,7 @@ class RecordProcessor(object):
|
|||||||
record['blueprint_id'] = list(valid_bp)
|
record['blueprint_id'] = list(valid_bp)
|
||||||
|
|
||||||
if record['record_type'] in ['bpd', 'bpi']:
|
if record['record_type'] in ['bpd', 'bpi']:
|
||||||
bp = valid_blueprints[record['name']]
|
bp = valid_blueprints[record['id']]
|
||||||
if ((record.get('mention_count') != bp['count']) or
|
if ((record.get('mention_count') != bp['count']) or
|
||||||
(record.get('mention_date') != bp['date'])):
|
(record.get('mention_date') != bp['date'])):
|
||||||
record['mention_count'] = bp['count']
|
record['mention_count'] = bp['count']
|
||||||
|
@@ -175,6 +175,10 @@ class Git(Vcs):
|
|||||||
commit['release'] = self.release_index[commit['commit_id']]
|
commit['release'] = self.release_index[commit['commit_id']]
|
||||||
else:
|
else:
|
||||||
commit['release'] = None
|
commit['release'] = None
|
||||||
|
if 'blueprint_id' in commit:
|
||||||
|
commit['blueprint_id'] = [(commit['module'] + ':' + bp_name)
|
||||||
|
for bp_name
|
||||||
|
in commit['blueprint_id']]
|
||||||
|
|
||||||
yield commit
|
yield commit
|
||||||
|
|
||||||
|
@@ -325,6 +325,7 @@ class TestRecordProcessor(testtools.TestCase):
|
|||||||
|
|
||||||
processed_records = list(record_processor_inst.process([
|
processed_records = list(record_processor_inst.process([
|
||||||
{'record_type': 'bp',
|
{'record_type': 'bp',
|
||||||
|
'id': 'mod:blueprint',
|
||||||
'self_link': 'http://launchpad.net/blueprint',
|
'self_link': 'http://launchpad.net/blueprint',
|
||||||
'owner': 'john_doe',
|
'owner': 'john_doe',
|
||||||
'date_created': 1234567890}
|
'date_created': 1234567890}
|
||||||
@@ -358,6 +359,7 @@ class TestRecordProcessor(testtools.TestCase):
|
|||||||
|
|
||||||
processed_records = list(record_processor_inst.process([
|
processed_records = list(record_processor_inst.process([
|
||||||
{'record_type': 'bp',
|
{'record_type': 'bp',
|
||||||
|
'id': 'mod:blueprint',
|
||||||
'self_link': 'http://launchpad.net/blueprint',
|
'self_link': 'http://launchpad.net/blueprint',
|
||||||
'owner': 'john_doe',
|
'owner': 'john_doe',
|
||||||
'date_created': 1234567890}
|
'date_created': 1234567890}
|
||||||
@@ -387,6 +389,7 @@ class TestRecordProcessor(testtools.TestCase):
|
|||||||
|
|
||||||
processed_records = list(record_processor_inst.process([
|
processed_records = list(record_processor_inst.process([
|
||||||
{'record_type': 'bp',
|
{'record_type': 'bp',
|
||||||
|
'id': 'mod:blueprint',
|
||||||
'self_link': 'http://launchpad.net/blueprint',
|
'self_link': 'http://launchpad.net/blueprint',
|
||||||
'owner': 'john_doe',
|
'owner': 'john_doe',
|
||||||
'date_created': 1234567890},
|
'date_created': 1234567890},
|
||||||
@@ -432,6 +435,7 @@ class TestRecordProcessor(testtools.TestCase):
|
|||||||
|
|
||||||
processed_records = list(record_processor_inst.process([
|
processed_records = list(record_processor_inst.process([
|
||||||
{'record_type': 'bp',
|
{'record_type': 'bp',
|
||||||
|
'id': 'mod:blueprint',
|
||||||
'self_link': 'http://launchpad.net/blueprint',
|
'self_link': 'http://launchpad.net/blueprint',
|
||||||
'owner': 'john_doe',
|
'owner': 'john_doe',
|
||||||
'date_created': 1234567890},
|
'date_created': 1234567890},
|
||||||
@@ -483,6 +487,7 @@ class TestRecordProcessor(testtools.TestCase):
|
|||||||
'createdOn': 1379404951,
|
'createdOn': 1379404951,
|
||||||
'module': 'nova'},
|
'module': 'nova'},
|
||||||
{'record_type': 'bp',
|
{'record_type': 'bp',
|
||||||
|
'id': 'mod:blueprint',
|
||||||
'self_link': 'http://launchpad.net/blueprint',
|
'self_link': 'http://launchpad.net/blueprint',
|
||||||
'owner': 'john_doe',
|
'owner': 'john_doe',
|
||||||
'date_created': 1234567890}
|
'date_created': 1234567890}
|
||||||
@@ -777,6 +782,7 @@ def generate_emails(author_name='John Doe', author_email='johndoe@gmail.com',
|
|||||||
'date': date,
|
'date': date,
|
||||||
'subject': subject,
|
'subject': subject,
|
||||||
'module': module,
|
'module': module,
|
||||||
|
'body': 'lorem ipsum',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -122,7 +122,7 @@ diff_stat:
|
|||||||
self.assertEquals(0, commits[3]['files_changed'])
|
self.assertEquals(0, commits[3]['files_changed'])
|
||||||
self.assertEquals(0, commits[3]['lines_added'])
|
self.assertEquals(0, commits[3]['lines_added'])
|
||||||
self.assertEquals(0, commits[3]['lines_deleted'])
|
self.assertEquals(0, commits[3]['lines_deleted'])
|
||||||
self.assertEquals(set(['fix-me']),
|
self.assertEquals(set(['dummy:fix-me']),
|
||||||
set(commits[3]['blueprint_id']))
|
set(commits[3]['blueprint_id']))
|
||||||
|
|
||||||
self.assertEquals(0, commits[4]['files_changed'])
|
self.assertEquals(0, commits[4]['files_changed'])
|
||||||
|
@@ -44,9 +44,10 @@ Change-Id: Ie49ccd2138905e178843b375a9b16c3fe572d1db'''
|
|||||||
During finish_migration the manager calls initialize_connection but doesn't \
|
During finish_migration the manager calls initialize_connection but doesn't \
|
||||||
update the block_device_mapping with the potentially new connection_info \
|
update the block_device_mapping with the potentially new connection_info \
|
||||||
returned.
|
returned.
|
||||||
Fixes bug <a href="https://bugs.launchpad.net/bugs/1076801">1076801</a>
|
Fixes bug <a href="https://bugs.launchpad.net/bugs/1076801" class="ext_link">\
|
||||||
|
1076801</a>
|
||||||
''' + ('Change-Id: <a href="https://review.openstack.org/#q,'
|
''' + ('Change-Id: <a href="https://review.openstack.org/#q,'
|
||||||
'Ie49ccd2138905e178843b375a9b16c3fe572d1db,n,z">'
|
'Ie49ccd2138905e178843b375a9b16c3fe572d1db,n,z" class="ext_link">'
|
||||||
'Ie49ccd2138905e178843b375a9b16c3fe572d1db</a>')
|
'Ie49ccd2138905e178843b375a9b16c3fe572d1db</a>')
|
||||||
|
|
||||||
observed = web.make_commit_message(record)
|
observed = web.make_commit_message(record)
|
||||||
@@ -71,9 +72,9 @@ Change-Id: Ie49ccd2138905e178843b375a9b16c3fe572d1db'''
|
|||||||
Implemented new driver for Cinder <:
|
Implemented new driver for Cinder <:
|
||||||
Implements Blueprint ''' + (
|
Implements Blueprint ''' + (
|
||||||
'<a href="https://blueprints.launchpad.net/cinder/+spec/'
|
'<a href="https://blueprints.launchpad.net/cinder/+spec/'
|
||||||
'super-driver">super-driver</a>' + '\n' +
|
'super-driver" class="ext_link">super-driver</a>' + '\n' +
|
||||||
'Change-Id: <a href="https://review.openstack.org/#q,'
|
'Change-Id: <a href="https://review.openstack.org/#q,'
|
||||||
'Ie49ccd2138905e178843b375a9b16c3fe572d1db,n,z">'
|
'Ie49ccd2138905e178843b375a9b16c3fe572d1db,n,z" class="ext_link">'
|
||||||
'Ie49ccd2138905e178843b375a9b16c3fe572d1db</a>')
|
'Ie49ccd2138905e178843b375a9b16c3fe572d1db</a>')
|
||||||
|
|
||||||
observed = web.make_commit_message(record)
|
observed = web.make_commit_message(record)
|
||||||
@@ -87,6 +88,15 @@ Implements Blueprint ''' + (
|
|||||||
|
|
||||||
self.assertEqual(expected, web.unwrap_text(original))
|
self.assertEqual(expected, web.unwrap_text(original))
|
||||||
|
|
||||||
|
def test_unwrap_split_long_link(self):
|
||||||
|
original = ('https://blueprints.launchpad.net/stackalytics/+spec/'
|
||||||
|
'stackalytics-core')
|
||||||
|
expected = ('https://​blueprints.launchpad.net/​'
|
||||||
|
'stackalytics/​+spec/​stackalytics-core')
|
||||||
|
|
||||||
|
self.assertEqual(expected, web.make_commit_message(
|
||||||
|
{'message': original, 'module': 'none'}))
|
||||||
|
|
||||||
@mock.patch('dashboard.web.get_vault')
|
@mock.patch('dashboard.web.get_vault')
|
||||||
@mock.patch('dashboard.web.get_user_from_runtime_storage')
|
@mock.patch('dashboard.web.get_user_from_runtime_storage')
|
||||||
def test_make_page_title(self, user_patch, vault_patch):
|
def test_make_page_title(self, user_patch, vault_patch):
|
||||||
|
Reference in New Issue
Block a user