##// END OF EJS Templates
final implementation of #210 journal filtering.
marcink -
r3070:cc7eedb5 beta
parent child Browse files
Show More
@@ -30,19 +30,20 b' from sqlalchemy.orm import joinedload'
30 30 from webhelpers.paginate import Page
31 31 from whoosh.qparser.default import QueryParser
32 32 from whoosh import query
33 from sqlalchemy.sql.expression import or_
33 from sqlalchemy.sql.expression import or_, and_
34 34
35 35 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
36 36 from rhodecode.lib.base import BaseController, render
37 37 from rhodecode.model.db import UserLog, User
38 38 from rhodecode.lib.utils2 import safe_int, remove_prefix, remove_suffix
39 39 from rhodecode.lib.indexers import JOURNAL_SCHEMA
40 from whoosh.qparser.dateparse import DateParserPlugin
40 41
41 42
42 43 log = logging.getLogger(__name__)
43 44
44 45
45 def _filter(user_log, search_term):
46 def _journal_filter(user_log, search_term):
46 47 """
47 48 Filters sqlalchemy user_log based on search_term with whoosh Query language
48 49 http://packages.python.org/Whoosh/querylang.html
@@ -54,6 +55,7 b' def _filter(user_log, search_term):'
54 55 qry = None
55 56 if search_term:
56 57 qp = QueryParser('repository', schema=JOURNAL_SCHEMA)
58 qp.add_plugin(DateParserPlugin())
57 59 qry = qp.parse(unicode(search_term))
58 60 log.debug('Filtering using parsed query %r' % qry)
59 61
@@ -87,20 +89,25 b' def _filter(user_log, search_term):'
87 89 return wildcard_handler(field, val)
88 90 elif isinstance(term, query.Prefix):
89 91 return field.startswith(val)
92 elif isinstance(term, query.DateRange):
93 return and_(field >= val[0], field <= val[1])
90 94 return field == val
91 95
92 if isinstance(qry, (query.And, query.Term, query.Prefix, query.Wildcard)):
96 if isinstance(qry, (query.And, query.Term, query.Prefix, query.Wildcard,
97 query.DateRange)):
93 98 if not isinstance(qry, query.And):
94 99 qry = [qry]
95 100 for term in qry:
96 101 field = term.fieldname
97 val = term.text
102 val = (term.text if not isinstance(term, query.DateRange)
103 else [term.startdate, term.enddate])
98 104 user_log = user_log.filter(get_filterion(field, val, term))
99 105 elif isinstance(qry, query.Or):
100 106 filters = []
101 107 for term in qry:
102 108 field = term.fieldname
103 val = term.text
109 val = (term.text if not isinstance(term, query.DateRange)
110 else [term.startdate, term.enddate])
104 111 filters.append(get_filterion(field, val, term))
105 112 user_log = user_log.filter(or_(*filters))
106 113
@@ -122,7 +129,7 b' class AdminController(BaseController):'
122 129 #FILTERING
123 130 c.search_term = request.GET.get('filter')
124 131 try:
125 users_log = _filter(users_log, c.search_term)
132 users_log = _journal_filter(users_log, c.search_term)
126 133 except:
127 134 # we want this to crash for now
128 135 raise
@@ -42,6 +42,7 b' from rhodecode.model.meta import Session'
42 42 from sqlalchemy.sql.expression import func
43 43 from rhodecode.model.scm import ScmModel
44 44 from rhodecode.lib.utils2 import safe_int
45 from rhodecode.controllers.admin.admin import _journal_filter
45 46
46 47 log = logging.getLogger(__name__)
47 48
@@ -65,9 +66,14 b' class JournalController(BaseController):'
65 66 .options(joinedload(UserFollowing.follows_repository))\
66 67 .all()
67 68
69 #FILTERING
70 c.search_term = request.GET.get('filter')
68 71 journal = self._get_journal_data(c.following)
69 72
70 c.journal_pager = Page(journal, page=p, items_per_page=20)
73 def url_generator(**kw):
74 return url.current(filter=c.search_term, **kw)
75
76 c.journal_pager = Page(journal, page=p, items_per_page=20, url=url_generator)
71 77 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
72 78
73 79 c.journal_data = render('journal/journal_data.html')
@@ -141,9 +147,15 b' class JournalController(BaseController):'
141 147 if filtering_criterion is not None:
142 148 journal = self.sa.query(UserLog)\
143 149 .options(joinedload(UserLog.user))\
144 .options(joinedload(UserLog.repository))\
145 .filter(filtering_criterion)\
146 .order_by(UserLog.action_date.desc())
150 .options(joinedload(UserLog.repository))
151 #filter
152 try:
153 journal = _journal_filter(journal, c.search_term)
154 except:
155 # we want this to crash for now
156 raise
157 journal = journal.filter(filtering_criterion)\
158 .order_by(UserLog.action_date.desc())
147 159 else:
148 160 journal = []
149 161
@@ -11,6 +11,7 b' import math'
11 11 import logging
12 12 import re
13 13 import urlparse
14 import textwrap
14 15
15 16 from datetime import datetime
16 17 from pygments.formatters.html import HtmlFormatter
@@ -1135,3 +1136,23 b' def changeset_status_lbl(changeset_statu'
1135 1136
1136 1137 def get_permission_name(key):
1137 1138 return dict(Permission.PERMS).get(key)
1139
1140
1141 def journal_filter_help():
1142 return _(textwrap.dedent('''
1143 Example filter terms:
1144 repository:vcs
1145 username:marcin
1146 action:*push*
1147 ip:127.0.0.1
1148 date:20120101
1149 date:[20120101100000 TO 20120102]
1150
1151 Generate wildcards using '*' character:
1152 "repositroy:vcs*" - search everything starting with 'vcs'
1153 "repository:*vcs*" - search for repository containing 'vcs'
1154
1155 Optional AND / OR operators in queries
1156 "repository:vcs OR repository:test"
1157 "username:test AND repository:test*"
1158 '''))
@@ -7,25 +7,8 b''
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 <form id="filter_form">
10 <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...')}"/>
11 <span class="tooltip" title="${h.tooltip(_('''
12 Example search query:
13 "repository:vcs"
14 "username:marcin"
15
16 You can use wildcards using '*'
17 "repositroy:vcs*" - search everything starting with 'vcs'
18 "repository:*vcs*" - search for repository containing 'vcs'
19 Use AND / OR operators in queries
20 "repository:vcs OR repository:test"
21 "username:test AND repository:test*"
22 List of valid search filters:
23 repository:
24 username:
25 action:
26 ip:
27 date:
28 '''))}">?</span>
10 <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...')}"/>
11 <span class="tooltip" title="${h.tooltip(h.journal_filter_help())}">?</span>
29 12 <input type='submit' value="${_('filter')}" class="ui-btn" style="padding:0px 2px 0px 2px;margin:0px"/>
30 13 ${_('Admin journal')} - ${ungettext('%s entry', '%s entries', c.users_log.item_count) % (c.users_log.item_count)}
31 14 </form>
@@ -50,24 +33,24 b''
50 33 </div>
51 34
52 35 <script>
53 YUE.on('q_filter','click',function(){
54 var qfilter = YUD.get('q_filter');
55 if(YUD.hasClass(qfilter, 'initial')){
56 qfilter.value = '';
36 YUE.on('j_filter','click',function(){
37 var jfilter = YUD.get('j_filter');
38 if(YUD.hasClass(jfilter, 'initial')){
39 jfilter.value = '';
57 40 }
58 41 });
59 var fix_q_filter_width = function(len){
60 YUD.setStyle(YUD.get('q_filter'),'width',Math.max(80, len*6.50)+'px');
42 var fix_j_filter_width = function(len){
43 YUD.setStyle(YUD.get('j_filter'),'width',Math.max(80, len*6.50)+'px');
61 44 }
62 YUE.on('q_filter','keyup',function(){
63 fix_q_filter_width(YUD.get('q_filter').value.length);
45 YUE.on('j_filter','keyup',function(){
46 fix_j_filter_width(YUD.get('j_filter').value.length);
64 47 });
65 48 YUE.on('filter_form','submit',function(e){
66 49 YUE.preventDefault(e)
67 var val = YUD.get('q_filter').value;
50 var val = YUD.get('j_filter').value;
68 51 window.location = "${url.current(filter='__FILTER__')}".replace('__FILTER__',val);
69 52 });
70 fix_q_filter_width(YUD.get('q_filter').value.length);
53 fix_j_filter_width(YUD.get('j_filter').value.length);
71 54 </script>
72 55 </%def>
73 56
@@ -4,7 +4,15 b''
4 4 ${_('Journal')} - ${c.rhodecode_name}
5 5 </%def>
6 6 <%def name="breadcrumbs()">
7 ${c.rhodecode_name}
7 <h5>
8 <form id="filter_form">
9 <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...')}"/>
10 <span class="tooltip" title="${h.tooltip(h.journal_filter_help())}">?</span>
11 <input type='submit' value="${_('filter')}" class="ui-btn" style="padding:0px 2px 0px 2px;margin:0px"/>
12 ${_('journal')} - ${ungettext('%s entry', '%s entries', c.journal_pager.item_count) % (c.journal_pager.item_count)}
13 </form>
14 ${h.end_form()}
15 </h5>
8 16 </%def>
9 17 <%def name="page_nav()">
10 18 ${self.menu('home')}
@@ -18,18 +26,18 b''
18 26 <div class="box box-left">
19 27 <!-- box / title -->
20 28 <div class="title">
21 <h5>${_('Journal')}</h5>
22 <ul class="links">
23 <li>
24 <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>
25 </li>
26 <li>
27 <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>
28 </li>
29 <li>
30 <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>
31 </li>
32 </ul>
29 ${self.breadcrumbs()}
30 <ul class="links">
31 <li>
32 <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>
33 </li>
34 <li>
35 <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>
36 </li>
37 <li>
38 <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>
39 </li>
40 </ul>
33 41 </div>
34 42 <div id="journal">${c.journal_data}</div>
35 43 </div>
@@ -106,6 +114,26 b''
106 114 </div>
107 115
108 116 <script type="text/javascript">
117
118 YUE.on('j_filter','click',function(){
119 var jfilter = YUD.get('j_filter');
120 if(YUD.hasClass(jfilter, 'initial')){
121 jfilter.value = '';
122 }
123 });
124 var fix_j_filter_width = function(len){
125 YUD.setStyle(YUD.get('j_filter'),'width',Math.max(80, len*6.50)+'px');
126 }
127 YUE.on('j_filter','keyup',function(){
128 fix_j_filter_width(YUD.get('j_filter').value.length);
129 });
130 YUE.on('filter_form','submit',function(e){
131 YUE.preventDefault(e)
132 var val = YUD.get('j_filter').value;
133 window.location = "${url.current(filter='__FILTER__')}".replace('__FILTER__',val);
134 });
135 fix_j_filter_width(YUD.get('j_filter').value.length);
136
109 137 var show_my = function(e){
110 138 YUD.setStyle('watched','display','none');
111 139 YUD.setStyle('my','display','');
@@ -153,7 +181,7 b''
153 181 }
154 182
155 183 YUE.on('refresh','click',function(e){
156 ypjax(e.currentTarget.href,"journal",function(){
184 ypjax("${h.url.current(filter=c.search_term)}","journal",function(){
157 185 show_more_event();
158 186 tooltip_activate();
159 187 show_changeset_tooltip();
@@ -100,4 +100,16 b' class TestAdminController(TestController'
100 100 self.log_user()
101 101 response = self.app.get(url(controller='admin/admin', action='index',
102 102 filter='action:*pull_request*'))
103 response.mustcontain('187 entries') No newline at end of file
103 response.mustcontain('187 entries')
104
105 def test_filter_journal_filter_on_date(self):
106 self.log_user()
107 response = self.app.get(url(controller='admin/admin', action='index',
108 filter='date:20121010'))
109 response.mustcontain('47 entries')
110
111 def test_filter_journal_filter_on_date_2(self):
112 self.log_user()
113 response = self.app.get(url(controller='admin/admin', action='index',
114 filter='date:20121020'))
115 response.mustcontain('17 entries') No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now