@@ -27,36 +27,38 @@ import logging
from pylons import request, tmpl_context as c, url
from sqlalchemy.orm import joinedload
from webhelpers.paginate import Page
from whoosh.qparser.default import QueryParser
from whoosh import query
from sqlalchemy.sql.expression import or_
from sqlalchemy.sql.expression import or_, and_
from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
from rhodecode.lib.base import BaseController, render
from rhodecode.model.db import UserLog, User
from rhodecode.lib.utils2 import safe_int, remove_prefix, remove_suffix
from rhodecode.lib.indexers import JOURNAL_SCHEMA
from whoosh.qparser.dateparse import DateParserPlugin
log = logging.getLogger(__name__)
def _filter(user_log, search_term):
def _journal_filter(user_log, search_term):
"""
Filters sqlalchemy user_log based on search_term with whoosh Query language
http://packages.python.org/Whoosh/querylang.html
:param user_log:
:param search_term:
log.debug('Initial search term: %r' % search_term)
qry = None
if search_term:
qp = QueryParser('repository', schema=JOURNAL_SCHEMA)
qp.add_plugin(DateParserPlugin())
qry = qp.parse(unicode(search_term))
log.debug('Filtering using parsed query %r' % qry)
def wildcard_handler(col, wc_term):
if wc_term.startswith('*') and not wc_term.endswith('*'):
#postfix == endswith
@@ -84,26 +86,31 @@ def _filter(user_log, search_term):
#sql filtering
if isinstance(term, query.Wildcard):
return wildcard_handler(field, val)
elif isinstance(term, query.Prefix):
return field.startswith(val)
elif isinstance(term, query.DateRange):
return and_(field >= val[0], field <= val[1])
return field == val
if isinstance(qry, (query.And, query.Term, query.Prefix, query.Wildcard)):
if isinstance(qry, (query.And, query.Term, query.Prefix, query.Wildcard,
query.DateRange)):
if not isinstance(qry, query.And):
qry = [qry]
for term in qry:
field = term.fieldname
val = term.text
val = (term.text if not isinstance(term, query.DateRange)
else [term.startdate, term.enddate])
user_log = user_log.filter(get_filterion(field, val, term))
elif isinstance(qry, query.Or):
filters = []
filters.append(get_filterion(field, val, term))
user_log = user_log.filter(or_(*filters))
return user_log
@@ -119,13 +126,13 @@ class AdminController(BaseController):
.options(joinedload(UserLog.user))\
.options(joinedload(UserLog.repository))
#FILTERING
c.search_term = request.GET.get('filter')
try:
users_log = _filter(users_log, c.search_term)
users_log = _journal_filter(users_log, c.search_term)
except:
# we want this to crash for now
raise
users_log = users_log.order_by(UserLog.action_date.desc())
@@ -39,12 +39,13 @@ from rhodecode.lib.auth import LoginRequ
from rhodecode.model.db import UserLog, UserFollowing, Repository, User
from rhodecode.model.meta import Session
from sqlalchemy.sql.expression import func
from rhodecode.model.scm import ScmModel
from rhodecode.lib.utils2 import safe_int
from rhodecode.controllers.admin.admin import _journal_filter
class JournalController(BaseController):
@@ -62,15 +63,20 @@ class JournalController(BaseController):
c.user = User.get(self.rhodecode_user.user_id)
c.following = self.sa.query(UserFollowing)\
.filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
.options(joinedload(UserFollowing.follows_repository))\
.all()
journal = self._get_journal_data(c.following)
c.journal_pager = Page(journal, page=p, items_per_page=20)
def url_generator(**kw):
return url.current(filter=c.search_term, **kw)
c.journal_pager = Page(journal, page=p, items_per_page=20, url=url_generator)
c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
c.journal_data = render('journal/journal_data.html')
if request.environ.get('HTTP_X_PARTIAL_XHR'):
return c.journal_data
return render('journal/journal.html')
@@ -138,15 +144,21 @@ class JournalController(BaseController):
filtering_criterion = UserLog.repository_id.in_(repo_ids)
if not repo_ids and user_ids:
filtering_criterion = UserLog.user_id.in_(user_ids)
if filtering_criterion is not None:
journal = self.sa.query(UserLog)\
.options(joinedload(UserLog.repository))\
.filter(filtering_criterion)\
.order_by(UserLog.action_date.desc())
#filter
journal = _journal_filter(journal, c.search_term)
journal = journal.filter(filtering_criterion)\
else:
journal = []
return journal
@LoginRequired()
@@ -8,12 +8,13 @@ import hashlib
import StringIO
import urllib
import math
import logging
import re
import urlparse
import textwrap
from datetime import datetime
from pygments.formatters.html import HtmlFormatter
from pygments import highlight as code_highlight
from pylons import url, request, config
from pylons.i18n.translation import _, ungettext
@@ -1132,6 +1133,26 @@ def changeset_status(repo, revision):
def changeset_status_lbl(changeset_status):
return dict(ChangesetStatus.STATUSES).get(changeset_status)
def get_permission_name(key):
return dict(Permission.PERMS).get(key)
def journal_filter_help():
return _(textwrap.dedent('''
Example filter terms:
repository:vcs
username:marcin
action:*push*
ip:127.0.0.1
date:20120101
date:[20120101100000 TO 20120102]
Generate wildcards using '*' character:
"repositroy:vcs*" - search everything starting with 'vcs'
"repository:*vcs*" - search for repository containing 'vcs'
Optional AND / OR operators in queries
"repository:vcs OR repository:test"
"username:test AND repository:test*"
'''))
@@ -4,31 +4,14 @@
<%def name="title()">
${_('Admin journal')} - ${c.rhodecode_name}
</%def>
<%def name="breadcrumbs_links()">
<form id="filter_form">
<input class="q_filter_box ${'' if c.search_term else 'initial'}" id="q_filter" size="15" type="text" name="filter" value="${c.search_term or _('quick filter...')}"/>
<span class="tooltip" title="${h.tooltip(_('''
Example search query:
"repository:vcs"
"username:marcin"
You can use wildcards using '*'
Use AND / OR operators in queries
List of valid search filters:
repository:
username:
action:
ip:
date:
'''))}">?</span>
<input class="q_filter_box ${'' if c.search_term else 'initial'}" id="j_filter" size="15" type="text" name="filter" value="${c.search_term or _('journal filter...')}"/>
<span class="tooltip" title="${h.tooltip(h.journal_filter_help())}">?</span>
<input type='submit' value="${_('filter')}" class="ui-btn" style="padding:0px 2px 0px 2px;margin:0px"/>
${_('Admin journal')} - ${ungettext('%s entry', '%s entries', c.users_log.item_count) % (c.users_log.item_count)}
</form>
${h.end_form()}
@@ -47,27 +30,27 @@
${c.log_data}
</div>
<script>
YUE.on('q_filter','click',function(){
var qfilter = YUD.get('q_filter');
if(YUD.hasClass(qfilter, 'initial')){
qfilter.value = '';
YUE.on('j_filter','click',function(){
var jfilter = YUD.get('j_filter');
if(YUD.hasClass(jfilter, 'initial')){
jfilter.value = '';
}
});
var fix_q_filter_width = function(len){
YUD.setStyle(YUD.get('q_filter'),'width',Math.max(80, len*6.50)+'px');
var fix_j_filter_width = function(len){
YUD.setStyle(YUD.get('j_filter'),'width',Math.max(80, len*6.50)+'px');
YUE.on('q_filter','keyup',function(){
fix_q_filter_width(YUD.get('q_filter').value.length);
YUE.on('j_filter','keyup',function(){
fix_j_filter_width(YUD.get('j_filter').value.length);
YUE.on('filter_form','submit',function(e){
YUE.preventDefault(e)
var val = YUD.get('q_filter').value;
var val = YUD.get('j_filter').value;
window.location = "${url.current(filter='__FILTER__')}".replace('__FILTER__',val);
</script>
## -*- coding: utf-8 -*-
<%inherit file="/base/base.html"/>
${_('Journal')} - ${c.rhodecode_name}
<%def name="breadcrumbs()">
${c.rhodecode_name}
<h5>
<input class="q_filter_box ${'' if c.search_term else 'initial'}" id="j_filter" size="15" type="text" name="filter" value="${c.search_term or _('quick filter...')}"/>
${_('journal')} - ${ungettext('%s entry', '%s entries', c.journal_pager.item_count) % (c.journal_pager.item_count)}
</h5>
<%def name="page_nav()">
${self.menu('home')}
<%def name="head_extra()">
<link href="${h.url('journal_atom', api_key=c.rhodecode_user.api_key)}" rel="alternate" title="${_('ATOM journal feed')}" type="application/atom+xml" />
@@ -15,24 +23,24 @@
<%def name="main()">
<div class="box box-left">
<!-- box / title -->
<div class="title">
<h5>${_('Journal')}</h5>
<ul class="links">
<li>
<span><a id="refresh" href="${h.url('journal')}"><img class="icon" title="${_('Refresh')}" alt="${_('Refresh')}" src="${h.url('/images/icons/arrow_refresh.png')}"/></a></span>
</li>
<span><a href="${h.url('journal_rss', api_key=c.rhodecode_user.api_key)}"><img class="icon" title="${_('RSS feed')}" alt="${_('RSS feed')}" src="${h.url('/images/icons/rss_16.png')}"/></a></span>
<span><a href="${h.url('journal_atom', api_key=c.rhodecode_user.api_key)}"><img class="icon" title="${_('ATOM feed')}" alt="${_('ATOM feed')}" src="${h.url('/images/icons/atom.png')}"/></a></span>
</ul>
${self.breadcrumbs()}
<div id="journal">${c.journal_data}</div>
<div class="box box-right">
@@ -103,12 +111,32 @@
%endif
<script type="text/javascript">
var show_my = function(e){
YUD.setStyle('watched','display','none');
YUD.setStyle('my','display','');
var url = "${h.url('admin_settings_my_repos')}";
ypjax(url, 'my', function(){
@@ -150,13 +178,13 @@
//We have a hash
var tabHash = url[1];
tabs[tabHash]();
YUE.on('refresh','click',function(e){
ypjax(e.currentTarget.href,"journal",function(){
ypjax("${h.url.current(filter=c.search_term)}","journal",function(){
show_more_event();
tooltip_activate();
show_changeset_tooltip();
YUE.preventDefault(e);
@@ -97,7 +97,19 @@ class TestAdminController(TestController
response.mustcontain('1095 entries') # 1087 + 8
def test_filter_journal_filter_wildcard_on_action(self):
self.log_user()
response = self.app.get(url(controller='admin/admin', action='index',
filter='action:*pull_request*'))
response.mustcontain('187 entries')
\ No newline at end of file
def test_filter_journal_filter_on_date(self):
filter='date:20121010'))
response.mustcontain('47 entries')
def test_filter_journal_filter_on_date_2(self):
filter='date:20121020'))
response.mustcontain('17 entries')
Status change: