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,8 +147,14 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)\ | |
|
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)\ | |
|
146 | 158 | .order_by(UserLog.action_date.desc()) |
|
147 | 159 | else: |
|
148 | 160 | journal = [] |
@@ -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=" |
|
|
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(' |
|
|
54 |
var |
|
|
55 |
if(YUD.hasClass( |
|
|
56 |
|
|
|
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_ |
|
|
60 |
YUD.setStyle(YUD.get(' |
|
|
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(' |
|
|
63 |
fix_ |
|
|
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(' |
|
|
50 | var val = YUD.get('j_filter').value; | |
|
68 | 51 | window.location = "${url.current(filter='__FILTER__')}".replace('__FILTER__',val); |
|
69 | 52 | }); |
|
70 |
fix_ |
|
|
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,7 +26,7 b'' | |||
|
18 | 26 | <div class="box box-left"> |
|
19 | 27 | <!-- box / title --> |
|
20 | 28 | <div class="title"> |
|
21 | <h5>${_('Journal')}</h5> | |
|
29 | ${self.breadcrumbs()} | |
|
22 | 30 |
|
|
23 | 31 |
|
|
24 | 32 |
|
@@ -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( |
|
|
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