Unify files/history/etc into one box (bug 651151-2)
This commit is contained in:
Родитель
6f175b5113
Коммит
2d71e533bd
|
@ -232,6 +232,14 @@ class REQUEST_SUPER_REVIEW(_LOG):
|
|||
review_queue = True
|
||||
|
||||
|
||||
class COMMENT_VERSION(_LOG):
|
||||
id = 49
|
||||
action_class = None
|
||||
format = _(u'Comment on {addon} {version}.')
|
||||
keep = True
|
||||
review_queue = True
|
||||
|
||||
|
||||
class ADD_TAG(_LOG):
|
||||
id = 25
|
||||
action_class = 'tag'
|
||||
|
@ -392,9 +400,11 @@ def log(action, *args, **kw):
|
|||
e.g. amo.log(amo.LOG.CREATE_ADDON, []),
|
||||
amo.log(amo.LOG.ADD_FILE_TO_VERSION, file, version)
|
||||
"""
|
||||
from devhub.models import ActivityLog, AddonLog, UserLog, CommentLog
|
||||
from devhub.models import (ActivityLog, AddonLog, UserLog,
|
||||
CommentLog, VersionLog)
|
||||
from addons.models import Addon
|
||||
from users.models import UserProfile
|
||||
from versions.models import Version
|
||||
from amo import get_user, logger_log
|
||||
|
||||
user = kw.get('user', get_user())
|
||||
|
@ -422,11 +432,15 @@ def log(action, *args, **kw):
|
|||
if isinstance(arg, tuple):
|
||||
if arg[0] == Addon:
|
||||
AddonLog(addon_id=arg[1], activity_log=al).save()
|
||||
elif arg[0] == Version:
|
||||
VersionLog(version_id=arg[1], activity_log=al).save()
|
||||
elif arg[0] == UserProfile:
|
||||
UserLog(user_id=arg[1], activity_log=al).save()
|
||||
|
||||
if isinstance(arg, Addon):
|
||||
AddonLog(addon=arg, activity_log=al).save()
|
||||
elif isinstance(arg, Version):
|
||||
VersionLog(version=arg, activity_log=al).save()
|
||||
elif isinstance(arg, UserProfile):
|
||||
# Index by any user who is mentioned as an argument.
|
||||
UserLog(activity_log=al, user=arg).save()
|
||||
|
|
|
@ -86,8 +86,7 @@ class AddonLog(amo.models.ModelBase):
|
|||
|
||||
class CommentLog(amo.models.ModelBase):
|
||||
"""
|
||||
This table is for indexing the activity log by user.
|
||||
Note: This includes activity performed unto the user.
|
||||
This table is for indexing the activity log by comment.
|
||||
"""
|
||||
activity_log = models.ForeignKey('ActivityLog')
|
||||
comments = models.CharField(max_length=255)
|
||||
|
@ -97,6 +96,18 @@ class CommentLog(amo.models.ModelBase):
|
|||
ordering = ('-created',)
|
||||
|
||||
|
||||
class VersionLog(amo.models.ModelBase):
|
||||
"""
|
||||
This table is for indexing the activity log by version.
|
||||
"""
|
||||
activity_log = models.ForeignKey('ActivityLog')
|
||||
version = models.ForeignKey(Version)
|
||||
|
||||
class Meta:
|
||||
db_table = 'log_activity_version'
|
||||
ordering = ('-created',)
|
||||
|
||||
|
||||
class UserLog(amo.models.ModelBase):
|
||||
"""
|
||||
This table is for indexing the activity log by user.
|
||||
|
@ -123,6 +134,11 @@ class ActivityLogManager(amo.models.ManagerBase):
|
|||
else:
|
||||
return self.none()
|
||||
|
||||
def for_version(self, version):
|
||||
vals = (VersionLog.objects.filter(version=version)
|
||||
.values_list('activity_log', flat=True))
|
||||
return self.filter(pk__in=list(vals))
|
||||
|
||||
def for_user(self, user):
|
||||
vals = (UserLog.objects.filter(user=user)
|
||||
.values_list('activity_log', flat=True))
|
||||
|
|
|
@ -117,6 +117,15 @@ class TestActivityLog(test_utils.TestCase):
|
|||
entries = ActivityLog.objects.for_user(u)
|
||||
eq_(len(entries), 1)
|
||||
|
||||
def test_version_log(self):
|
||||
request = self.request
|
||||
amo.log(amo.LOG['CUSTOM_TEXT'], 'hi there')
|
||||
version = Version.objects.all()[0]
|
||||
amo.log(amo.LOG.REJECT_VERSION, version.addon, version,
|
||||
user=self.request.amo_user)
|
||||
entries = ActivityLog.objects.for_version(version)
|
||||
eq_(len(entries), 1)
|
||||
|
||||
def test_xss_arguments(self):
|
||||
addon = Addon.objects.get()
|
||||
au = AddonUser(addon=addon, user=self.user)
|
||||
|
|
|
@ -1,38 +1,15 @@
|
|||
<tr>
|
||||
<td>
|
||||
{% if record.arguments.1 %}
|
||||
{{ record.arguments.1.version }}
|
||||
{% else %}
|
||||
{{ _('Deleted version') }}
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
<td>
|
||||
{{ record.user.name }}<br>
|
||||
<span class="light">{{ record.created|datetime }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<strong>{{ record }}</strong>
|
||||
{% if record.details %}
|
||||
<div class="history_comment">
|
||||
{{ record.details.comments|nl2br }}
|
||||
</div>
|
||||
{% if record.details.files %}
|
||||
<ul>
|
||||
{% for file_id in record.details.files %}
|
||||
{% if file_id in files: %}
|
||||
{% set file = files[file_id] %}
|
||||
<li>
|
||||
<strong>{{ file.platform }}</strong>
|
||||
[<a href="{{ file.get_url_path(addon.app, 'editor') }}" class="install"
|
||||
data-type="{{ amo.ADDON_SLUGS[addon.type] }}">{{ file.filename }}</a>]
|
||||
</li>
|
||||
{% else %}
|
||||
<li><em>{{ _('File deleted') }}</em></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<li>
|
||||
<strong>{{ record }}</strong>
|
||||
{% if record.details %}
|
||||
<div class="history_comment">
|
||||
{{ record.details.comments|nl2br }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="light">
|
||||
<em>
|
||||
{% trans user = record.user|user_link, date = record.created|babel_datetime %}
|
||||
By {{ user }} on {{ date }}
|
||||
{% endtrans %}
|
||||
</em>
|
||||
</div>
|
||||
</li>
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
{% if num_pages > 1 %}
|
||||
<ol class="pagination">
|
||||
{% if pager.has_next() %}
|
||||
<li>
|
||||
<a rel="prev" href="{{ pager.url|urlparams(page=pager.next_page_number()) }}#history">
|
||||
{{ _('Older') }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
{% trans begin=pager.start_index(), end=pager.end_index(),
|
||||
count=count|numberfmt %}
|
||||
Results <strong>{{ begin }}</strong>–<strong>{{ end }}</strong>
|
||||
of <strong>{{ count }}</strong>
|
||||
{% endtrans %}
|
||||
</li>
|
||||
{% if pager.has_previous() %}
|
||||
<li>
|
||||
<a rel="next" href="{{ pager.url|urlparams(page=pager.previous_page_number()) }}#history">
|
||||
{{ _('Newer') }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% for x in pager.page_range %}
|
||||
<li {{ x|class_selected(pager.number) }}>
|
||||
<a href="{{ pager.url|urlparams(page=x) }}">{{ x }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
{% endif %}
|
|
@ -43,82 +43,75 @@
|
|||
<p>{{ version.approvalnotes|nl2br }}</p>
|
||||
{% endif %}
|
||||
|
||||
<h3 id="file-history">{{ _('Item History') }}</h3>
|
||||
{% if history %}
|
||||
<div id="review-files-header">
|
||||
<h3 id="history">{{ _('Add-on History') }}</h3>
|
||||
<div id="review-files-paginate">
|
||||
{% include "editors/includes/paginator_history.html" %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="results">
|
||||
<div class="results-inner">
|
||||
<table id="review-files" class="data-grid">
|
||||
<thead>
|
||||
<tr class="listing-header">
|
||||
<th width="15%">{{ _('Version') }}</th>
|
||||
<th width="20%">{{ _('Review') }}</th>
|
||||
<th>{{ _('Comments') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for record in history %}
|
||||
{% include 'editors/includes/history.html' %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
<table id="review-files" class="item-history">
|
||||
{% for i in range(pager.object_list.count(), 0, -1) %}
|
||||
{% set version = pager.object_list[i-1] %}
|
||||
<tr class="listing-header">
|
||||
<th colspan="2">
|
||||
{% trans version = version.version, created = version.created|datetime %}
|
||||
Version {{ version }} · {{ created }}
|
||||
{% endtrans %}
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="files">
|
||||
<div><strong>{{ _('Files in this version:') }}</strong></div>
|
||||
{% for file in version.all_files %}
|
||||
<div>
|
||||
<span class="light">
|
||||
<strong><a href="{{ file.get_url_path(addon.app, 'editor') }}" class="install"
|
||||
data-type="{{ amo.ADDON_SLUGS[addon.type] }}">{{ file.platform }}</a></strong>
|
||||
<div>
|
||||
{{ file_review_status(addon, file) }}
|
||||
</div>
|
||||
<a href="{{ url('devhub.file_validation', addon.slug, file.id) }}">{{ _('Validation') }}</a>
|
||||
·
|
||||
{% if waffle.switch('zamboni-file-viewer') %}
|
||||
<a href="{{ url('files.list', file.id) }}">{{ _('Contents') }}</a>
|
||||
{% else %}
|
||||
<a href="{{ remora_url('/files/browse/%d/1' % file.id) }}">{{ _('Contents') }}</a>
|
||||
{% endif %}
|
||||
{% if show_diff %}
|
||||
·
|
||||
{% if waffle.switch('zamboni-file-viewer') %}
|
||||
<a href="{{ url('files.compare', file.id, file_compare(file, show_diff)) }}">{{ _('Compare') }}</a>
|
||||
{% else %}
|
||||
<a href="{{ remora_url('/files/diff/%d/' % file.id) }}">{{ _('Compare') }}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>
|
||||
{% set records = version.all_activity %}
|
||||
{% if records %}
|
||||
<ul class="activity">
|
||||
{% for record_version in records %}
|
||||
{% set record = record_version.activity_log %}
|
||||
{% include 'editors/includes/history.html' %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<div class="no-activity">
|
||||
{{ _('This version has no activity yet.') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="article">
|
||||
<em>
|
||||
{{ _('No previous review entries could be found.') }}
|
||||
</em>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<h3 id="compatibility">{{ _('Compatibility') }}</h3>
|
||||
<div class="article">
|
||||
<ul>
|
||||
{% for compat in version.compatible_apps.values() %}
|
||||
<li>
|
||||
{% if compat.application_id in amo.APPS_ALL %}
|
||||
<div class="app-icon ed-sprite-{{ amo.APPS_ALL[compat.application_id].short }}"
|
||||
title="{{ amo.APPS_ALL[compat.application_id].pretty}}"></div>
|
||||
{% endif %}
|
||||
{{ compat }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h3 id="validation">
|
||||
{{ _('Files and Validation Results') }}
|
||||
<span>[<a href="{{ remora_url('pages/validation') }}">{{ _('help') }}</a>]</span>
|
||||
</h3>
|
||||
<div class="article">
|
||||
<ul class="files">
|
||||
{% for file in version.files.all() %}
|
||||
<li>
|
||||
<strong>{{ file.platform }}</strong>
|
||||
[<a href="{{ file.get_url_path(addon.app, 'editor') }}" class="install"
|
||||
data-type="{{ amo.ADDON_SLUGS[addon.type] }}">{{ file.filename }}</a>]
|
||||
<br>
|
||||
{{ file_review_status(addon, file) }}
|
||||
·
|
||||
<a href="{{ url('devhub.file_validation', addon.slug, file.id) }}">{{ _('Validation Results') }}</a>
|
||||
·
|
||||
{% if waffle.switch('zamboni-file-viewer') %}
|
||||
<a href="{{ url('files.list', file.id) }}">{{ _('View Contents') }}</a>
|
||||
{% else %}
|
||||
<a href="{{ remora_url('/files/browse/%d/1' % file.id) }}">{{ _('View Contents') }}</a>
|
||||
{% endif %}
|
||||
{% if show_diff %}
|
||||
·
|
||||
{% if waffle.switch('zamboni-file-viewer') %}
|
||||
<a href="{{ url('files.compare', file.id, file_compare(file, show_diff)) }}">{{ _('Compare With Public Version') }}</a>
|
||||
{% else %}
|
||||
<a href="{{ remora_url('/files/diff/%d/' % file.id) }}">{{ _('Compare With Public Version') }}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="#review-actions">
|
||||
{{ csrf() }}
|
||||
|
@ -218,26 +211,20 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
<strong>{{ _('This Add-on') }}</strong>
|
||||
<strong>{{ _('Add-on Actions ') }}</strong>
|
||||
<ul id="actions-addon">
|
||||
<li><a href="{{ addon.get_url_path() }}">{{ _('View Listing') }}</a></li>
|
||||
<li><a href="{{ addon.get_url_path() }}">{{ _('View Add-on Listing') }}</a></li>
|
||||
{% if is_admin %}
|
||||
<li><a href="{{ url('devhub.addons.edit', addon.slug) }}">{{ _('Edit Add-on') }}</a> <em>{{ _('(admin)') }}</em></li>
|
||||
<li><a href="{{ remora_url('admin/addons/status/%s' % addon.id) }}">{{ _('Admin Page') }}</a> <em>{{ _('(admin)') }}</em></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
{% if is_admin %}
|
||||
<strong>{{ _('Admin Tools') }}</strong>
|
||||
<ul id="actions-admin">
|
||||
<li><a href="{{ url('devhub.addons.edit', addon.slug) }}">{{ _('Edit Add-on') }}</a></li>
|
||||
<li><a href="{{ remora_url('admin/addons/status/%s' % addon.id) }}">{{ _('Admin Page') }}</a></li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
<strong>{{ _('Review This Add-on') }}</strong>
|
||||
<ul>
|
||||
<li><a href="#more-about">{{ _('More Information') }}</a></li>
|
||||
<li><a href="#file-about">{{ _('Item History') }}</a></li>
|
||||
<li><a href="#compatibility">{{ _('Compatibility') }}</a></li>
|
||||
<li><a href="#validation">{{ _('Add-on Files') }}</a></li>
|
||||
<li><a href="#review-actions">{{ _('Editor Actions') }}</a></li>
|
||||
<li><a href="#history">{{ _('Add-on History') }}</a></li>
|
||||
</ul>
|
||||
|
||||
<strong>{{ _('Authors') }}</strong>
|
||||
|
@ -254,6 +241,19 @@
|
|||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<strong>{{ _('Compatibility') }}</strong>
|
||||
<ul>
|
||||
{% for compat in version.compatible_apps.values() %}
|
||||
<li>
|
||||
{% if compat.application_id in amo.APPS_ALL %}
|
||||
<div class="app-icon ed-sprite-{{ amo.APPS_ALL[compat.application_id].short }}"
|
||||
title="{{ amo.APPS_ALL[compat.application_id].pretty}}"></div>
|
||||
{% endif %}
|
||||
{{ compat }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
{% if flags: %}
|
||||
<strong>{{ _('Flags') }}</strong>
|
||||
<ul>
|
||||
|
|
|
@ -1355,16 +1355,52 @@ class TestReview(ReviewBase):
|
|||
response = self.client.get(self.url)
|
||||
eq_(response.status_code, 200)
|
||||
|
||||
validation = pq(response.content).find('#validation').next()
|
||||
doc = pq(response.content)
|
||||
validation = doc('#review-files .files div').eq(1)
|
||||
eq_(validation.children().length, 1)
|
||||
|
||||
eq_(validation.find('a').eq(0).text(), 'Public.xpi')
|
||||
eq_(validation.find('a').eq(0).text(), 'All Platforms')
|
||||
|
||||
eq_(validation.find('a').eq(1).text(), "Validation Results")
|
||||
eq_(validation.find('a').eq(2).text(), "View Contents")
|
||||
eq_(validation.find('a').eq(1).text(), "Validation")
|
||||
eq_(validation.find('a').eq(2).text(), "Contents")
|
||||
|
||||
eq_(validation.find('a').length, 3)
|
||||
|
||||
def test_item_history(self):
|
||||
self.addon_file(u'something', u'0.2', amo.STATUS_PUBLIC,
|
||||
amo.STATUS_UNREVIEWED)
|
||||
|
||||
eq_(self.addon.versions.count(), 1)
|
||||
url = reverse('editors.review', args=[self.addon.slug])
|
||||
|
||||
self.review_version(self.version, url)
|
||||
|
||||
v2 = self.versions['something']
|
||||
v2.addon = self.addon
|
||||
v2.created = v2.created + timedelta(days=1)
|
||||
v2.save()
|
||||
self.review_version(v2, url)
|
||||
eq_(self.addon.versions.count(), 2)
|
||||
|
||||
r = self.client.get(url)
|
||||
doc = pq(r.content)
|
||||
|
||||
# View the history verify two versions:
|
||||
ths = doc('table#review-files tr th:first-child')
|
||||
assert '0.1' in ths.eq(0).text()
|
||||
assert '0.2' in ths.eq(1).text()
|
||||
|
||||
for i in [0, 2]:
|
||||
tds = doc('table#review-files tr td')
|
||||
eq_(tds.eq(i).find('strong').eq(0).text(), "Files in this version:")
|
||||
eq_(tds.eq(i).find('div').length, 3)
|
||||
|
||||
|
||||
eq_(tds.eq(1).find('ul li').length, 1)
|
||||
eq_(tds.eq(1).find('ul li a').length, 3)
|
||||
eq_(tds.eq(1).find('ul li .history_comment').text(), "something")
|
||||
eq_(tds.eq(1).find('ul li em a').text(), "An editor")
|
||||
|
||||
def test_files_in_item_history(self):
|
||||
data = {'action': 'public', 'operating_systems': 'win',
|
||||
'applications': 'something', 'comments': 'something',
|
||||
|
@ -1375,20 +1411,21 @@ class TestReview(ReviewBase):
|
|||
r = self.client.get(self.url)
|
||||
doc = pq(r.content)
|
||||
|
||||
eq_(doc('#review-files tbody tr').length, 1)
|
||||
li = doc('#review-files .history_comment').parent().find('li')
|
||||
eq_(li.length, 1)
|
||||
eq_(li.find('strong').text(), "All Platforms")
|
||||
eq_(doc('#review-files .files > div').length, 2)
|
||||
div = doc('#review-files .files div').eq(1)
|
||||
eq_(div.length, 1)
|
||||
eq_(div.find('a.install').text(), "All Platforms")
|
||||
|
||||
def test_no_items(self):
|
||||
response = self.client.get(self.url)
|
||||
eq_(pq(response.content).find('#file-history').next().text(),
|
||||
"No previous review entries could be found.")
|
||||
doc = pq(response.content)
|
||||
div = doc('#review-files-header').next().find('td').eq(1).find('div')
|
||||
eq_(div.text(), "This version has no activity yet.")
|
||||
|
||||
def test_listing_link(self):
|
||||
response = self.client.get(self.url)
|
||||
text = pq(response.content).find('#actions-addon li a').eq(0).text()
|
||||
eq_(text, "View Listing")
|
||||
eq_(text, "View Add-on Listing")
|
||||
|
||||
def test_admin_links_as_admin(self):
|
||||
self.login_as_admin()
|
||||
|
@ -1396,14 +1433,14 @@ class TestReview(ReviewBase):
|
|||
|
||||
doc = pq(response.content)
|
||||
|
||||
admin = doc('#actions-admin')
|
||||
assert admin.length > 0
|
||||
admin = doc('#actions-addon li')
|
||||
assert admin.length == 3
|
||||
|
||||
a = admin.find('a').eq(0)
|
||||
a = admin.find('a').eq(1)
|
||||
eq_(a.text(), "Edit Add-on")
|
||||
assert "developers/addon/%s" % self.addon.slug in a.attr('href')
|
||||
|
||||
a = admin.find('a').eq(1)
|
||||
a = admin.find('a').eq(2)
|
||||
eq_(a.text(), "Admin Page")
|
||||
assert "admin/addons/status/%s" % self.addon.id in a.attr('href')
|
||||
|
||||
|
@ -1412,8 +1449,8 @@ class TestReview(ReviewBase):
|
|||
response = self.client.get(self.url)
|
||||
|
||||
doc = pq(response.content)
|
||||
admin = doc('#actions-admin')
|
||||
eq_(admin.length, 0)
|
||||
admin = doc('#actions-addon li')
|
||||
eq_(admin.length, 1)
|
||||
|
||||
def test_no_public(self):
|
||||
s = amo.STATUS_PUBLIC
|
||||
|
@ -1430,9 +1467,9 @@ class TestReview(ReviewBase):
|
|||
|
||||
response = self.client.get(self.url)
|
||||
|
||||
validation = pq(response.content).find('#validation').next()
|
||||
eq_(validation.find('a').eq(1).text(), "Validation Results")
|
||||
eq_(validation.find('a').eq(2).text(), "View Contents")
|
||||
validation = pq(response.content).find('.files')
|
||||
eq_(validation.find('a').eq(1).text(), "Validation")
|
||||
eq_(validation.find('a').eq(2).text(), "Contents")
|
||||
|
||||
eq_(validation.find('a').length, 3)
|
||||
|
||||
|
@ -1456,9 +1493,9 @@ class TestReview(ReviewBase):
|
|||
|
||||
response = self.client.get(self.url)
|
||||
|
||||
validation = pq(response.content).find('#validation').next()
|
||||
eq_(validation.find('a').eq(1).text(), "Validation Results")
|
||||
eq_(validation.find('a').eq(2).text(), "View Contents")
|
||||
validation = pq(response.content).find('.files')
|
||||
eq_(validation.find('a').eq(1).text(), "Validation")
|
||||
eq_(validation.find('a').eq(2).text(), "Contents")
|
||||
|
||||
eq_(validation.find('a').length, 3)
|
||||
|
||||
|
@ -1486,17 +1523,19 @@ class TestReview(ReviewBase):
|
|||
doc = pq(r.content)
|
||||
|
||||
# View the history verify two versions:
|
||||
eq_(doc('table#review-files tr td:first-child').eq(0).text(), '0.1')
|
||||
eq_(doc('table#review-files tr td:first-child').eq(1).text(), '0.2')
|
||||
ths = doc('table#review-files tr th:first-child')
|
||||
assert '0.1' in ths.eq(0).text()
|
||||
assert '0.2' in ths.eq(1).text()
|
||||
|
||||
# Delete a version:
|
||||
v2.delete()
|
||||
# Verify two versions, one deleted:
|
||||
r = self.client.get(url)
|
||||
doc = pq(r.content)
|
||||
ths = doc('table#review-files tr th:first-child')
|
||||
|
||||
eq_(doc('table#review-files tr td:first-child').eq(1).text(),
|
||||
'Deleted version')
|
||||
eq_(doc('table#review-files tr td:first-child').eq(0).text(), '0.1')
|
||||
eq_(doc('table#review-files tr th:first-child').length, 1)
|
||||
assert '0.1' in ths.eq(0).text()
|
||||
|
||||
def review_version(self, version, url):
|
||||
version.files.all()[0].update(status=amo.STATUS_UNREVIEWED)
|
||||
|
@ -1586,8 +1625,9 @@ class TestReview(ReviewBase):
|
|||
doc = pq(r.content)
|
||||
|
||||
assert r.context['show_diff']
|
||||
eq_(doc('.files a:last-child').text(), 'Compare With Public Version')
|
||||
eq_(doc('.files a:last-child').attr('href'),
|
||||
|
||||
eq_(doc('.files').eq(1).find('a').eq(3).text(), 'Compare')
|
||||
eq_(doc('.files').eq(1).find('a').eq(3).attr('href'),
|
||||
reverse('files.compare', args=(next_file.pk,
|
||||
first_file.pk)))
|
||||
|
||||
|
@ -1769,7 +1809,7 @@ class TestStatusFile(ReviewBase):
|
|||
for status in [amo.STATUS_UNREVIEWED, amo.STATUS_LITE]:
|
||||
self.addon.update(status=status)
|
||||
res = self.client.get(self.url)
|
||||
node = pq(res.content)('ul.files li:first-child')
|
||||
node = pq(res.content)('td.files div').eq(1)
|
||||
assert 'Pending Preliminary Review' in node.text()
|
||||
|
||||
def test_status_full(self):
|
||||
|
@ -1777,7 +1817,7 @@ class TestStatusFile(ReviewBase):
|
|||
amo.STATUS_PUBLIC]:
|
||||
self.addon.update(status=status)
|
||||
res = self.client.get(self.url)
|
||||
node = pq(res.content)('ul.files li:first-child')
|
||||
node = pq(res.content)('td.files div').eq(1)
|
||||
assert 'Pending Full Review' in node.text()
|
||||
|
||||
def test_status_full_reviewed(self):
|
||||
|
@ -1788,11 +1828,12 @@ class TestStatusFile(ReviewBase):
|
|||
amo.STATUS_NOMINATED, amo.STATUS_LITE_AND_NOMINATED]:
|
||||
self.addon.update(status=status)
|
||||
res = self.client.get(self.url)
|
||||
node = pq(res.content)('ul.files li:first-child')
|
||||
node = pq(res.content)('td.files div').eq(1)
|
||||
assert 'Fully Reviewed' in node.text()
|
||||
|
||||
def test_other(self):
|
||||
self.addon.update(status=amo.STATUS_BETA)
|
||||
res = self.client.get(self.url)
|
||||
node = pq(res.content)('ul.files li:first-child')
|
||||
node = pq(res.content)('td.files div').eq(1)
|
||||
|
||||
assert unicode(amo.STATUS_CHOICES[self.file.status]) in node.text()
|
||||
|
|
|
@ -121,18 +121,6 @@ def _editor_progress():
|
|||
return (progress, percentage)
|
||||
|
||||
|
||||
def _get_history(addon):
|
||||
files = []
|
||||
history = (ActivityLog.objects.for_addons(addon).order_by('created')
|
||||
.filter(action__in=amo.LOG_REVIEW_QUEUE))
|
||||
|
||||
for h in history:
|
||||
if 'files' in h.details:
|
||||
files.extend(h.details['files'])
|
||||
|
||||
return history, File.objects.in_bulk(files)
|
||||
|
||||
|
||||
@editor_required
|
||||
def performance(request, user_id=False):
|
||||
user = request.amo_user
|
||||
|
@ -442,15 +430,21 @@ def review(request, addon):
|
|||
# We only allow the user to check/uncheck files for "pending"
|
||||
allow_unchecking_files = form.helper.review_type == "pending"
|
||||
|
||||
history, files = _get_history(addon)
|
||||
versions = (Version.objects.filter(addon=addon).order_by('-created')
|
||||
.transform(Version.transformer_activity)
|
||||
.transform(Version.transformer))
|
||||
pager = amo.utils.paginate(request, versions, 5)
|
||||
|
||||
num_pages = pager.paginator.num_pages
|
||||
count = pager.paginator.count
|
||||
|
||||
ctx = context(version=version, addon=addon,
|
||||
pager=pager, num_pages=num_pages, count=count,
|
||||
flags=Review.objects.filter(addon=addon, flag=True),
|
||||
form=form, paging=paging, canned=canned, is_admin=is_admin,
|
||||
status_types=amo.STATUS_CHOICES, show_diff=show_diff,
|
||||
allow_unchecking_files=allow_unchecking_files,
|
||||
actions=actions, actions_minimal=actions_minimal,
|
||||
history=history, files=files)
|
||||
actions=actions, actions_minimal=actions_minimal)
|
||||
|
||||
return jingo.render(request, 'editors/review.html', ctx)
|
||||
|
||||
|
|
|
@ -150,6 +150,13 @@ class Version(amo.models.ModelBase):
|
|||
amo.log(amo.LOG.DELETE_VERSION, self.addon, str(self.version))
|
||||
super(Version, self).delete()
|
||||
|
||||
@amo.cached_property(writable=True)
|
||||
def all_activity(self):
|
||||
from devhub.models import VersionLog # yucky
|
||||
al = (VersionLog.objects.filter(version=self.id).order_by('created')
|
||||
.select_related(depth=1).no_cache())
|
||||
return al
|
||||
|
||||
@amo.cached_property(writable=True)
|
||||
def compatible_apps(self):
|
||||
"""Get a mapping of {APP: ApplicationVersion}."""
|
||||
|
@ -259,6 +266,28 @@ class Version(amo.models.ModelBase):
|
|||
version.compatible_apps = cls._compat_map(av_dict.get(v_id, []))
|
||||
version.all_files = file_dict.get(v_id, [])
|
||||
|
||||
@classmethod
|
||||
def transformer_activity(cls, versions):
|
||||
"""Attach all the activity to the versions."""
|
||||
from devhub.models import VersionLog # yucky
|
||||
|
||||
ids = set(v.id for v in versions)
|
||||
if not versions:
|
||||
return
|
||||
|
||||
al = (VersionLog.objects.filter(version__in=ids).order_by('created')
|
||||
.select_related(depth=1).no_cache())
|
||||
|
||||
def rollup(xs):
|
||||
groups = amo.utils.sorted_groupby(xs, 'version_id')
|
||||
return dict((k, list(vs)) for k, vs in groups)
|
||||
|
||||
al_dict = rollup(al)
|
||||
|
||||
for version in versions:
|
||||
v_id = version.id
|
||||
version.all_activity = al_dict.get(v_id, [])
|
||||
|
||||
def disable_old_files(self):
|
||||
if not self.files.filter(status=amo.STATUS_BETA).exists():
|
||||
qs = File.objects.filter(version__addon=self.addon_id,
|
||||
|
|
|
@ -507,17 +507,105 @@ div.editor-stats-table > div.editor-stats-dark {
|
|||
/* Review Page */
|
||||
|
||||
.primary {
|
||||
width: 85%;
|
||||
width: 83.5%;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
width: 13.49%;
|
||||
width: 15%;
|
||||
}
|
||||
|
||||
.secondary.scroll_sidebar_parent {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.scroll_sidebar_parent #scroll_sidebar li a {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#review-files {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
#review-files .files {
|
||||
background-color: #FAFAFA;
|
||||
border-right: 1px dotted #DDDDDD;
|
||||
border-top: 0 none;
|
||||
width: 255px;
|
||||
padding: 15px;
|
||||
}
|
||||
#review-files-header h3 {
|
||||
float: left;
|
||||
}
|
||||
#review-files-header #review-files-paginate {
|
||||
float: right;
|
||||
margin-top: 2em;
|
||||
}
|
||||
#review-files-paginate ol {
|
||||
float: right;
|
||||
margin: 0;
|
||||
padding-left: 10px;
|
||||
text-align: right;
|
||||
top: -20px;
|
||||
width: auto;
|
||||
}
|
||||
#review-files td .activity li {
|
||||
border-top: 1px dotted #DDDDDD;
|
||||
margin: 1em 1em 0;
|
||||
padding-top: 1em;
|
||||
}
|
||||
|
||||
#review-files td .activity li:first-child {
|
||||
border-top: 0 none;
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
#review-files tr.listing-header:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
#review-files tr.listing-header {
|
||||
border-top: 1px solid #A5BFCE;
|
||||
}
|
||||
#review-files tr.listing-header:hover {
|
||||
cursor: pointer;
|
||||
background-color: #CCE6FF;
|
||||
background: -webkit-gradient(
|
||||
linear,
|
||||
left bottom,
|
||||
left top,
|
||||
from(#FFFFFF),
|
||||
to(#CCE6FF)
|
||||
);
|
||||
background: -moz-linear-gradient(#FFFFFF, #CCE6FF);
|
||||
}
|
||||
#review-files tr.listing-header:active {
|
||||
cursor: pointer;
|
||||
background-color: #FFF;
|
||||
background: -webkit-gradient(
|
||||
linear,
|
||||
left bottom,
|
||||
left top,
|
||||
from(#CCE6FF),
|
||||
to(#FFFFFF)
|
||||
);
|
||||
background: -moz-linear-gradient(#E0EFFD, #FFF);
|
||||
}
|
||||
#review-files .files div:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
#review-files .files > div {
|
||||
padding-top: 5px;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
#review-files .no-activity {
|
||||
color: #777777;
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
}
|
||||
#review-files .install {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#review-actions-form textarea,
|
||||
#review-actions-form input[type=text],
|
||||
#review-actions-form select {
|
||||
|
@ -662,7 +750,6 @@ div.editor-stats-table > div.editor-stats-dark {
|
|||
|
||||
.history_comment {
|
||||
overflow: auto;
|
||||
width: 613px;
|
||||
}
|
||||
|
||||
.review-paging {
|
||||
|
@ -705,3 +792,22 @@ span.currently_viewing_warning {
|
|||
}
|
||||
|
||||
|
||||
.paginator {
|
||||
margin: 0pt;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.paginator li {
|
||||
float: left;
|
||||
width: 33%;
|
||||
}
|
||||
|
||||
.paginator .next {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.paginator .page {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -131,6 +131,12 @@ function initReviewActions() {
|
|||
});
|
||||
}
|
||||
check_currently_viewing();
|
||||
|
||||
/* Item History */
|
||||
$('#review-files tr:not(.listing-header, :last-child)').hide();
|
||||
$('#review-files tr.listing-header').click(function() {
|
||||
$(this).next().toggle();
|
||||
});
|
||||
}
|
||||
|
||||
function insertAtCursor(textarea, text) {
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
DROP TABLE IF EXISTS log_activity_version;
|
||||
|
||||
CREATE TABLE `log_activity_version` (
|
||||
`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
|
||||
`created` datetime NOT NULL,
|
||||
`modified` datetime NOT NULL,
|
||||
`activity_log_id` int(11) NOT NULL,
|
||||
`version_id` int(11) UNSIGNED NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8
|
||||
;
|
||||
|
||||
ALTER TABLE `log_activity_version`
|
||||
ADD CONSTRAINT FOREIGN KEY (`activity_log_id`) REFERENCES `log_activity` (`id`);
|
||||
|
||||
ALTER TABLE `log_activity_version`
|
||||
ADD CONSTRAINT FOREIGN KEY (`version_id`) REFERENCES `versions` (`id`);
|
Загрузка…
Ссылка в новой задаче