Show More
@@ -30,19 +30,20 b' from sqlalchemy.orm import joinedload' | |||||
30 | from webhelpers.paginate import Page |
|
30 | from webhelpers.paginate import Page | |
31 | from whoosh.qparser.default import QueryParser |
|
31 | from whoosh.qparser.default import QueryParser | |
32 | from whoosh import query |
|
32 | from whoosh import query | |
33 | from sqlalchemy.sql.expression import or_ |
|
33 | from sqlalchemy.sql.expression import or_, and_ | |
34 |
|
34 | |||
35 | from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator |
|
35 | from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator | |
36 | from rhodecode.lib.base import BaseController, render |
|
36 | from rhodecode.lib.base import BaseController, render | |
37 | from rhodecode.model.db import UserLog, User |
|
37 | from rhodecode.model.db import UserLog, User | |
38 | from rhodecode.lib.utils2 import safe_int, remove_prefix, remove_suffix |
|
38 | from rhodecode.lib.utils2 import safe_int, remove_prefix, remove_suffix | |
39 | from rhodecode.lib.indexers import JOURNAL_SCHEMA |
|
39 | from rhodecode.lib.indexers import JOURNAL_SCHEMA | |
|
40 | from whoosh.qparser.dateparse import DateParserPlugin | |||
40 |
|
41 | |||
41 |
|
42 | |||
42 | log = logging.getLogger(__name__) |
|
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 | Filters sqlalchemy user_log based on search_term with whoosh Query language |
|
48 | Filters sqlalchemy user_log based on search_term with whoosh Query language | |
48 | http://packages.python.org/Whoosh/querylang.html |
|
49 | http://packages.python.org/Whoosh/querylang.html | |
@@ -54,6 +55,7 b' def _filter(user_log, search_term):' | |||||
54 | qry = None |
|
55 | qry = None | |
55 | if search_term: |
|
56 | if search_term: | |
56 | qp = QueryParser('repository', schema=JOURNAL_SCHEMA) |
|
57 | qp = QueryParser('repository', schema=JOURNAL_SCHEMA) | |
|
58 | qp.add_plugin(DateParserPlugin()) | |||
57 | qry = qp.parse(unicode(search_term)) |
|
59 | qry = qp.parse(unicode(search_term)) | |
58 | log.debug('Filtering using parsed query %r' % qry) |
|
60 | log.debug('Filtering using parsed query %r' % qry) | |
59 |
|
61 | |||
@@ -87,20 +89,25 b' def _filter(user_log, search_term):' | |||||
87 | return wildcard_handler(field, val) |
|
89 | return wildcard_handler(field, val) | |
88 | elif isinstance(term, query.Prefix): |
|
90 | elif isinstance(term, query.Prefix): | |
89 | return field.startswith(val) |
|
91 | return field.startswith(val) | |
|
92 | elif isinstance(term, query.DateRange): | |||
|
93 | return and_(field >= val[0], field <= val[1]) | |||
90 | return field == val |
|
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 | if not isinstance(qry, query.And): |
|
98 | if not isinstance(qry, query.And): | |
94 | qry = [qry] |
|
99 | qry = [qry] | |
95 | for term in qry: |
|
100 | for term in qry: | |
96 | field = term.fieldname |
|
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 | user_log = user_log.filter(get_filterion(field, val, term)) |
|
104 | user_log = user_log.filter(get_filterion(field, val, term)) | |
99 | elif isinstance(qry, query.Or): |
|
105 | elif isinstance(qry, query.Or): | |
100 | filters = [] |
|
106 | filters = [] | |
101 | for term in qry: |
|
107 | for term in qry: | |
102 | field = term.fieldname |
|
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 | filters.append(get_filterion(field, val, term)) |
|
111 | filters.append(get_filterion(field, val, term)) | |
105 | user_log = user_log.filter(or_(*filters)) |
|
112 | user_log = user_log.filter(or_(*filters)) | |
106 |
|
113 | |||
@@ -122,7 +129,7 b' class AdminController(BaseController):' | |||||
122 | #FILTERING |
|
129 | #FILTERING | |
123 | c.search_term = request.GET.get('filter') |
|
130 | c.search_term = request.GET.get('filter') | |
124 | try: |
|
131 | try: | |
125 | users_log = _filter(users_log, c.search_term) |
|
132 | users_log = _journal_filter(users_log, c.search_term) | |
126 | except: |
|
133 | except: | |
127 | # we want this to crash for now |
|
134 | # we want this to crash for now | |
128 | raise |
|
135 | raise |
@@ -42,6 +42,7 b' from rhodecode.model.meta import Session' | |||||
42 | from sqlalchemy.sql.expression import func |
|
42 | from sqlalchemy.sql.expression import func | |
43 | from rhodecode.model.scm import ScmModel |
|
43 | from rhodecode.model.scm import ScmModel | |
44 | from rhodecode.lib.utils2 import safe_int |
|
44 | from rhodecode.lib.utils2 import safe_int | |
|
45 | from rhodecode.controllers.admin.admin import _journal_filter | |||
45 |
|
46 | |||
46 | log = logging.getLogger(__name__) |
|
47 | log = logging.getLogger(__name__) | |
47 |
|
48 | |||
@@ -65,9 +66,14 b' class JournalController(BaseController):' | |||||
65 | .options(joinedload(UserFollowing.follows_repository))\ |
|
66 | .options(joinedload(UserFollowing.follows_repository))\ | |
66 | .all() |
|
67 | .all() | |
67 |
|
68 | |||
|
69 | #FILTERING | |||
|
70 | c.search_term = request.GET.get('filter') | |||
68 | journal = self._get_journal_data(c.following) |
|
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 | c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager) |
|
77 | c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager) | |
72 |
|
78 | |||
73 | c.journal_data = render('journal/journal_data.html') |
|
79 | c.journal_data = render('journal/journal_data.html') | |
@@ -141,9 +147,15 b' class JournalController(BaseController):' | |||||
141 | if filtering_criterion is not None: |
|
147 | if filtering_criterion is not None: | |
142 | journal = self.sa.query(UserLog)\ |
|
148 | journal = self.sa.query(UserLog)\ | |
143 | .options(joinedload(UserLog.user))\ |
|
149 | .options(joinedload(UserLog.user))\ | |
144 |
.options(joinedload(UserLog.repository)) |
|
150 | .options(joinedload(UserLog.repository)) | |
145 | .filter(filtering_criterion)\ |
|
151 | #filter | |
146 | .order_by(UserLog.action_date.desc()) |
|
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 | else: |
|
159 | else: | |
148 | journal = [] |
|
160 | journal = [] | |
149 |
|
161 |
@@ -11,6 +11,7 b' import math' | |||||
11 | import logging |
|
11 | import logging | |
12 | import re |
|
12 | import re | |
13 | import urlparse |
|
13 | import urlparse | |
|
14 | import textwrap | |||
14 |
|
15 | |||
15 | from datetime import datetime |
|
16 | from datetime import datetime | |
16 | from pygments.formatters.html import HtmlFormatter |
|
17 | from pygments.formatters.html import HtmlFormatter | |
@@ -1135,3 +1136,23 b' def changeset_status_lbl(changeset_statu' | |||||
1135 |
|
1136 | |||
1136 | def get_permission_name(key): |
|
1137 | def get_permission_name(key): | |
1137 | return dict(Permission.PERMS).get(key) |
|
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 | <%def name="breadcrumbs_links()"> |
|
8 | <%def name="breadcrumbs_links()"> | |
9 | <form id="filter_form"> |
|
9 | <form id="filter_form"> | |
10 |
<input class="q_filter_box ${'' if c.search_term else 'initial'}" id=" |
|
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( |
|
11 | <span class="tooltip" title="${h.tooltip(h.journal_filter_help())}">?</span> | |
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> |
|
|||
29 | <input type='submit' value="${_('filter')}" class="ui-btn" style="padding:0px 2px 0px 2px;margin:0px"/> |
|
12 | <input type='submit' value="${_('filter')}" class="ui-btn" style="padding:0px 2px 0px 2px;margin:0px"/> | |
30 | ${_('Admin journal')} - ${ungettext('%s entry', '%s entries', c.users_log.item_count) % (c.users_log.item_count)} |
|
13 | ${_('Admin journal')} - ${ungettext('%s entry', '%s entries', c.users_log.item_count) % (c.users_log.item_count)} | |
31 | </form> |
|
14 | </form> | |
@@ -50,24 +33,24 b'' | |||||
50 | </div> |
|
33 | </div> | |
51 |
|
34 | |||
52 | <script> |
|
35 | <script> | |
53 |
YUE.on(' |
|
36 | YUE.on('j_filter','click',function(){ | |
54 |
var |
|
37 | var jfilter = YUD.get('j_filter'); | |
55 |
if(YUD.hasClass( |
|
38 | if(YUD.hasClass(jfilter, 'initial')){ | |
56 |
|
|
39 | jfilter.value = ''; | |
57 | } |
|
40 | } | |
58 | }); |
|
41 | }); | |
59 |
var fix_ |
|
42 | var fix_j_filter_width = function(len){ | |
60 |
YUD.setStyle(YUD.get(' |
|
43 | YUD.setStyle(YUD.get('j_filter'),'width',Math.max(80, len*6.50)+'px'); | |
61 | } |
|
44 | } | |
62 |
YUE.on(' |
|
45 | YUE.on('j_filter','keyup',function(){ | |
63 |
fix_ |
|
46 | fix_j_filter_width(YUD.get('j_filter').value.length); | |
64 | }); |
|
47 | }); | |
65 | YUE.on('filter_form','submit',function(e){ |
|
48 | YUE.on('filter_form','submit',function(e){ | |
66 | YUE.preventDefault(e) |
|
49 | YUE.preventDefault(e) | |
67 |
var val = YUD.get(' |
|
50 | var val = YUD.get('j_filter').value; | |
68 | window.location = "${url.current(filter='__FILTER__')}".replace('__FILTER__',val); |
|
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 | </script> |
|
54 | </script> | |
72 | </%def> |
|
55 | </%def> | |
73 |
|
56 |
@@ -4,7 +4,15 b'' | |||||
4 | ${_('Journal')} - ${c.rhodecode_name} |
|
4 | ${_('Journal')} - ${c.rhodecode_name} | |
5 | </%def> |
|
5 | </%def> | |
6 | <%def name="breadcrumbs()"> |
|
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 | </%def> |
|
16 | </%def> | |
9 | <%def name="page_nav()"> |
|
17 | <%def name="page_nav()"> | |
10 | ${self.menu('home')} |
|
18 | ${self.menu('home')} | |
@@ -18,18 +26,18 b'' | |||||
18 | <div class="box box-left"> |
|
26 | <div class="box box-left"> | |
19 | <!-- box / title --> |
|
27 | <!-- box / title --> | |
20 | <div class="title"> |
|
28 | <div class="title"> | |
21 | <h5>${_('Journal')}</h5> |
|
29 | ${self.breadcrumbs()} | |
22 |
|
|
30 | <ul class="links"> | |
23 |
|
|
31 | <li> | |
24 |
|
|
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> | |
25 |
|
|
33 | </li> | |
26 |
|
|
34 | <li> | |
27 |
|
|
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> | |
28 |
|
|
36 | </li> | |
29 |
|
|
37 | <li> | |
30 |
|
|
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> | |
31 |
|
|
39 | </li> | |
32 |
|
|
40 | </ul> | |
33 | </div> |
|
41 | </div> | |
34 | <div id="journal">${c.journal_data}</div> |
|
42 | <div id="journal">${c.journal_data}</div> | |
35 | </div> |
|
43 | </div> | |
@@ -106,6 +114,26 b'' | |||||
106 | </div> |
|
114 | </div> | |
107 |
|
115 | |||
108 | <script type="text/javascript"> |
|
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 | var show_my = function(e){ |
|
137 | var show_my = function(e){ | |
110 | YUD.setStyle('watched','display','none'); |
|
138 | YUD.setStyle('watched','display','none'); | |
111 | YUD.setStyle('my','display',''); |
|
139 | YUD.setStyle('my','display',''); | |
@@ -153,7 +181,7 b'' | |||||
153 | } |
|
181 | } | |
154 |
|
182 | |||
155 | YUE.on('refresh','click',function(e){ |
|
183 | YUE.on('refresh','click',function(e){ | |
156 |
ypjax( |
|
184 | ypjax("${h.url.current(filter=c.search_term)}","journal",function(){ | |
157 | show_more_event(); |
|
185 | show_more_event(); | |
158 | tooltip_activate(); |
|
186 | tooltip_activate(); | |
159 | show_changeset_tooltip(); |
|
187 | show_changeset_tooltip(); |
@@ -100,4 +100,16 b' class TestAdminController(TestController' | |||||
100 | self.log_user() |
|
100 | self.log_user() | |
101 | response = self.app.get(url(controller='admin/admin', action='index', |
|
101 | response = self.app.get(url(controller='admin/admin', action='index', | |
102 | filter='action:*pull_request*')) |
|
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