changeset 3070:cc7eedb5323c beta

final implementation of #210 journal filtering.
author Marcin Kuzminski <marcin@python-works.com>
date Thu, 06 Dec 2012 23:55:12 +0100
parents 9dca99ffd495
children 7b0c19b00629
files rhodecode/controllers/admin/admin.py rhodecode/controllers/journal.py rhodecode/lib/helpers.py rhodecode/templates/admin/admin.html rhodecode/templates/journal/journal.html rhodecode/tests/functional/test_admin.py
diffstat 6 files changed, 117 insertions(+), 54 deletions(-) [+]
line wrap: on
line diff
--- a/rhodecode/controllers/admin/admin.py	Thu Dec 06 21:57:24 2012 +0100
+++ b/rhodecode/controllers/admin/admin.py	Thu Dec 06 23:55:12 2012 +0100
@@ -30,19 +30,20 @@
 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
@@ -54,6 +55,7 @@
     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)
 
@@ -87,20 +89,25 @@
             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 = []
         for term in qry:
             field = term.fieldname
-            val = term.text
+            val = (term.text if not isinstance(term, query.DateRange)
+                   else [term.startdate, term.enddate])
             filters.append(get_filterion(field, val, term))
         user_log = user_log.filter(or_(*filters))
 
@@ -122,7 +129,7 @@
         #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
--- a/rhodecode/controllers/journal.py	Thu Dec 06 21:57:24 2012 +0100
+++ b/rhodecode/controllers/journal.py	Thu Dec 06 23:55:12 2012 +0100
@@ -42,6 +42,7 @@
 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
 
 log = logging.getLogger(__name__)
 
@@ -65,9 +66,14 @@
             .options(joinedload(UserFollowing.follows_repository))\
             .all()
 
+        #FILTERING
+        c.search_term = request.GET.get('filter')
         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')
@@ -141,9 +147,15 @@
         if filtering_criterion is not None:
             journal = self.sa.query(UserLog)\
                 .options(joinedload(UserLog.user))\
-                .options(joinedload(UserLog.repository))\
-                .filter(filtering_criterion)\
-                .order_by(UserLog.action_date.desc())
+                .options(joinedload(UserLog.repository))
+            #filter
+            try:
+                journal = _journal_filter(journal, c.search_term)
+            except:
+                # we want this to crash for now
+                raise
+            journal = journal.filter(filtering_criterion)\
+                        .order_by(UserLog.action_date.desc())
         else:
             journal = []
 
--- a/rhodecode/lib/helpers.py	Thu Dec 06 21:57:24 2012 +0100
+++ b/rhodecode/lib/helpers.py	Thu Dec 06 23:55:12 2012 +0100
@@ -11,6 +11,7 @@
 import logging
 import re
 import urlparse
+import textwrap
 
 from datetime import datetime
 from pygments.formatters.html import HtmlFormatter
@@ -1135,3 +1136,23 @@
 
 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*"
+    '''))
--- a/rhodecode/templates/admin/admin.html	Thu Dec 06 21:57:24 2012 +0100
+++ b/rhodecode/templates/admin/admin.html	Thu Dec 06 23:55:12 2012 +0100
@@ -7,25 +7,8 @@
 
 <%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 '*'
-            "repositroy:vcs*" - search everything starting with 'vcs'
-            "repository:*vcs*" - search for repository containing 'vcs'
-        Use AND / OR operators in queries
-            "repository:vcs OR repository:test"
-            "username:test AND repository:test*"
-        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>
@@ -50,24 +33,24 @@
 </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);
 });
-fix_q_filter_width(YUD.get('q_filter').value.length);
+fix_j_filter_width(YUD.get('j_filter').value.length);
 </script>
 </%def>
 
--- a/rhodecode/templates/journal/journal.html	Thu Dec 06 21:57:24 2012 +0100
+++ b/rhodecode/templates/journal/journal.html	Thu Dec 06 23:55:12 2012 +0100
@@ -4,7 +4,15 @@
     ${_('Journal')} - ${c.rhodecode_name}
 </%def>
 <%def name="breadcrumbs()">
-	${c.rhodecode_name}
+    <h5>
+    <form id="filter_form">
+    <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...')}"/>
+    <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"/>
+    ${_('journal')} - ${ungettext('%s entry', '%s entries', c.journal_pager.item_count) % (c.journal_pager.item_count)}
+    </form>
+    ${h.end_form()}
+    </h5>
 </%def>
 <%def name="page_nav()">
 	${self.menu('home')}
@@ -18,18 +26,18 @@
     <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>
-               <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>
-               </li>
-               <li>
-                 <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>
-               </li>
-             </ul>
+         ${self.breadcrumbs()}
+         <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>
+           <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>
+           </li>
+           <li>
+             <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>
+           </li>
+         </ul>
 	    </div>
 	    <div id="journal">${c.journal_data}</div>
     </div>
@@ -106,6 +114,26 @@
     </div>
 
     <script type="text/javascript">
+    
+    YUE.on('j_filter','click',function(){
+        var jfilter = YUD.get('j_filter');
+        if(YUD.hasClass(jfilter, 'initial')){
+            jfilter.value = '';
+        }
+    });
+    var fix_j_filter_width = function(len){
+        YUD.setStyle(YUD.get('j_filter'),'width',Math.max(80, len*6.50)+'px');
+    }
+    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('j_filter').value;
+        window.location = "${url.current(filter='__FILTER__')}".replace('__FILTER__',val);
+    });
+    fix_j_filter_width(YUD.get('j_filter').value.length);    
+    
     var show_my = function(e){
         YUD.setStyle('watched','display','none');
         YUD.setStyle('my','display','');
@@ -153,7 +181,7 @@
     }
 
     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();
--- a/rhodecode/tests/functional/test_admin.py	Thu Dec 06 21:57:24 2012 +0100
+++ b/rhodecode/tests/functional/test_admin.py	Thu Dec 06 23:55:12 2012 +0100
@@ -100,4 +100,16 @@
         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
+        response.mustcontain('187 entries')
+
+    def test_filter_journal_filter_on_date(self):
+        self.log_user()
+        response = self.app.get(url(controller='admin/admin', action='index',
+                                    filter='date:20121010'))
+        response.mustcontain('47 entries')
+
+    def test_filter_journal_filter_on_date_2(self):
+        self.log_user()
+        response = self.app.get(url(controller='admin/admin', action='index',
+                                    filter='date:20121020'))
+        response.mustcontain('17 entries')
\ No newline at end of file