You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bloodhound.apache.org by th...@apache.org on 2014/07/17 21:25:50 UTC
svn commit: r1611444 - in
/bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme:
htdocs/js/DupeSearch.js htdocs/js/popoverDupSearch.js theme.py
Author: thimal
Date: Thu Jul 17 19:25:49 2014
New Revision: 1611444
URL: http://svn.apache.org/r1611444
Log:
add the duplicate feature to popover ticket create and did some style change according to the bloodhound style
Added:
bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/htdocs/js/popoverDupSearch.js (with props)
Modified:
bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/htdocs/js/DupeSearch.js
bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/theme.py
Modified: bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/htdocs/js/DupeSearch.js
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/htdocs/js/DupeSearch.js?rev=1611444&r1=1611443&r2=1611444&view=diff
==============================================================================
--- bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/htdocs/js/DupeSearch.js (original)
+++ bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/htdocs/js/DupeSearch.js Thu Jul 17 19:25:49 2014
@@ -1,17 +1,17 @@
-$(document).ready(function() {
+jQuery(document).ready(function() {
$('div#content.ticket h2#vc-summary').blur(function() {
var text = $('div#content.ticket h2#vc-summary').text();
if (text.length > 0) {
var html = '<h5 class="loading">Loading related tickets..</h5>';
- var dupeticketlistDiv = $('div#content.ticket h2#vc-summary + div#dupeticketlist');
- if (dupeticketlistDiv.length == 0) {
+ var duplicate_eticket_list_div = $('div#content.ticket h2#vc-summary + div#dupeticketlist');
+ if (duplicate_eticket_list_div.length == 0) {
$('div#content.ticket h2#vc-summary').after('<div id="dupeticketlist" style="display:none;"></div>');
- dupeticketlistDiv = $('div#content.ticket h2#vc-summary + div#dupeticketlist');
+ duplicate_eticket_list_div = $('div#content.ticket h2#vc-summary + div#dupeticketlist');
}
- $('ul',dupeticketlistDiv).slideUp('fast');
- dupeticketlistDiv.html(html).slideDown();
+ $('ul',duplicate_eticket_list_div).slideUp('fast');
+ duplicate_eticket_list_div.html(html).slideDown();
$.ajax({
url:'duplicate_ticket_search',
@@ -20,48 +20,51 @@ $(document).ready(function() {
success: function(data, status) {
var tickets =data;
- var ticketBaseHref = 'ticket/';
- var searchBaseHref = 'bhsearch?type=ticket&q=';
- var maxTickets = 15;
+ var ticket_base_Href = 'ticket/';
+ var search_base_Href = 'bhsearch?type=ticket&q=';
+ var max_tickets = 15;
var html = '';
if (tickets === null) {
// error
- dupeticketlistDiv.html('<h5 class="error">Error loading tickets.</h5>');
+ duplicate_eticket_list_div.html('<h5 class="error">Error loading tickets.</h5>');
} else if (tickets.length <= 0) {
// no dupe tickets
- dupeticketlistDiv.slideUp();
+ duplicate_eticket_list_div.slideUp();
} else {
- html = '<h5>Possible related tickets:</h5><ul style="display:none;">'
+ html = '<h5>Possible related tickets:</h5><ul id="results">'
tickets = tickets.reverse();
- for (var i = 0; i < tickets.length && i < maxTickets; i++) {
+ for (var i = 0; i < tickets.length && i < max_tickets; i++) {
var ticket = tickets[i];
html += '<li class="highlight_matches" title="' + ticket.description +
- '"><a href="' + ticketBaseHref + ticket.url +
+ '"><a href="' + ticket_base_Href + ticket.url +
'"><span class="' + htmlencode(ticket.status) + '">#' +
- ticket.url + '</span></a>: ' + htmlencode(ticket.type) + ': ' +
- ticket.summary + '(' + htmlencode(ticket.status) +
- (ticket.url ? ': ' + htmlencode(ticket.url) : '') +
- ')' + '</li>'
+ ticket.url + '</span></a>: ' +
+ ticket.summary + ' (' + htmlencode(ticket.status)
+ +': '+ htmlencode(ticket.type) +
+ ') ' +'<span class="author">created by '+ticket.owner +'</span> <span class="date">at ' +ticket.date+ '</span></li>'
}
html += '</ul>';
- if (tickets.length > maxTickets) {
+ if (tickets.length > max_tickets) {
var text = $('div#content.ticket input#field-summary').val();
- html += '<a href="' + searchBaseHref + escape(text) + '">More..</a>';
+ html += '<a href="' + search_base_Href + escape(text) + '">More..</a>';
}
- dupeticketlistDiv.html(html);
- $('> ul', dupeticketlistDiv).slideDown();
+ duplicate_eticket_list_div.html(html);
+ $('> ul', duplicate_eticket_list_div).slideDown();
}
},
error: function(xhr, textStatus, exception) {
- dupeticketlistDiv.html('<h5 class="error">Error loading tickets: ' + textStatus + '</h5>');
+ duplicate_eticket_list_div.html('<h5 class="error">Error loading tickets: ' + textStatus + '</h5>');
}
});
- }
+ }else{
+ var duplicate_eticket_list_div = $('div#content.ticket div#dupeticketlist');
+ duplicate_eticket_list_div.slideUp('fast');
+ }
});
function htmlencode(text) {
Added: bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/htdocs/js/popoverDupSearch.js
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/htdocs/js/popoverDupSearch.js?rev=1611444&view=auto
==============================================================================
--- bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/htdocs/js/popoverDupSearch.js (added)
+++ bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/htdocs/js/popoverDupSearch.js Thu Jul 17 19:25:49 2014
@@ -0,0 +1,73 @@
+jQuery(document).ready(function() {
+
+ $('input#field-summary.input-block-level').blur(function() {
+ var text = $('input#field-summary.input-block-level').val();
+ if (text.length > 0) {
+
+ var html = '<h5 class="loading">Loading related tickets..</h5>';
+ var dupelicate_ticket_list_div = $('div.popover-content input#field-summary + div#dupeticketlist');
+ if (dupelicate_ticket_list_div.length == 0) {
+ $('div.popover-content input#field-summary').after('<div id="dupeticketlist" style="display:none;"></div>');
+ dupelicate_ticket_list_div = $('div.popover-content input#field-summary + div#dupeticketlist');
+ }
+ $(dupelicate_ticket_list_div).slideUp('fast');
+ dupelicate_ticket_list_div.html(html).slideDown();
+
+ $.ajax({
+ url:'duplicate_ticket_search',
+ data:{q:text},
+ type:'GET',
+ success: function(data, status) {
+ var tickets =data;
+ var ticket_base_href = 'ticket/';
+ var search_base_Href = 'bhsearch?type=ticket&q=';
+ var max_tickets = 5;
+
+ var html = '';
+ if (tickets === null) {
+ // error
+ dupelicate_ticket_list_div.html('<h5 class="error">Error loading tickets.</h5>');
+ } else if (tickets.length <= 0) {
+ // no dupe tickets
+ dupelicate_ticket_list_div.slideUp();
+ } else {
+ html = '<h5>Possible related tickets:</h5><ul style="display:none;">';
+ //tickets = tickets.reverse();
+
+ for (var i = 0; i < tickets.length && i < max_tickets; i++) {
+ var ticket = tickets[i];
+ html += '<li class="highlight_matches" title="' + ticket.description +
+ '"><a href="' + ticket_base_href + ticket.url +
+ '"><span class="' + htmlencode(ticket.status) + '">#' +
+ ticket.url + '</span></a>: ' + htmlencode(ticket.type) + ': ' +
+ ticket.summary + '(' + htmlencode(ticket.status) +
+ (ticket.url ? ': ' + htmlencode(ticket.url) : '') +
+ ')' + '</li>'
+ }
+ html += '</ul>';
+ if (tickets.length > max_tickets) {
+ var text = $('div.popover-content input#field-summary').val();
+ html += '<a href="' + search_base_Href + escape(text) + '">More..</a>';
+ }
+
+ dupelicate_ticket_list_div.html(html);
+ $('> ul', dupelicate_ticket_list_div).slideDown();
+
+ }
+
+ },
+ error: function(xhr, textStatus, exception) {
+ dupelicate_ticket_list_div.html('<h5 class="error">Error loading tickets: ' + textStatus + '</h5>');
+ }
+ });
+ }else{
+ var dupelicate_ticket_list_div = $('div.popover-content input#field-summary + div#dupeticketlist');
+ dupelicate_ticket_list_div.slideUp();
+ }
+ });
+
+ function htmlencode(text) {
+ return $('<div/>').text(text).html().replace(/"/g, '"').replace(/'/g, ''');
+ }
+});
+
Propchange: bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/htdocs/js/popoverDupSearch.js
------------------------------------------------------------------------------
svn:eol-style = native
Modified: bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/theme.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/theme.py?rev=1611444&r1=1611443&r2=1611444&view=diff
==============================================================================
--- bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/theme.py (original)
+++ bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/theme.py Thu Jul 17 19:25:49 2014
@@ -19,6 +19,7 @@
import sys
+from trac.util.datefmt import format_datetime, user_time
from collections import Counter
import fnmatch
@@ -42,7 +43,7 @@ from trac.util.presentation import to_js
from trac.versioncontrol.web_ui.browser import BrowserModule
from trac.web.api import IRequestFilter, IRequestHandler, ITemplateStreamFilter
from trac.web.chrome import (add_stylesheet, add_warning, INavigationContributor,
- ITemplateProvider, prevnext_nav, Chrome, add_script)
+ ITemplateProvider, prevnext_nav, Chrome, add_script, add_script_data)
from trac.wiki.admin import WikiAdmin
from trac.wiki.formatter import format_to_html
@@ -694,8 +695,8 @@ class AutocompleteUsers(Component):
implements(IRequestFilter, IRequestHandler,
ITemplateProvider, ITemplateStreamFilter)
- selectfields = ListOption('autocomplete', 'fields', default='',
- doc='select fields to transform to autocomplete text boxes')
+ select_fields = ListOption('autocomplete', 'fields', default='',
+ doc='select fields to transform to autocomplete text boxes')
# IRequestHandler methods
@@ -711,16 +712,15 @@ class AutocompleteUsers(Component):
if req.args.get('users', '1') == '1':
users = self._get_users(req)
if req.perm.has_permission('EMAIL_VIEW'):
- subjects = ['{"label":"%s %s %s","value":"%s"}' % (user[USER] and '%s' % user[USER] or '',
- user[EMAIL] and '<%s>' % user[EMAIL] or '',
- user[NAME] and '%s' % user[NAME] or '',
- user[USER])
- for value, user in users] # value unused (placeholder needed for sorting)
+ subjects = ['{"label":"%s %s %s","value":"%s"}' % (user[USER] and '%s' % user[USER] or
+ '', user[EMAIL] and '<%s>' % user[EMAIL] or
+ '', user[NAME] and '%s' % user[NAME] or
+ '', user[USER])
+ for value, user in users]
else:
- subjects = ['{"label":"%s %s","value":"%s"}' % (user[USER] and '%s' % user[USER] or '',
- user[NAME] and '%s' % user[NAME] or '',
- user[USER])
- for value, user in users] # value unused (placeholder needed for sorting)
+ subjects = ['{"label":"%s %s","value":"%s"}' % (user[USER] and '%s' % user[USER] or '', user[NAME] and
+ '%s' % user[NAME] or'', user[USER])
+ for value, user in users]
respond_str = ','.join(subjects).encode('utf-8')
respond_str = '[' + respond_str + ']'
@@ -730,7 +730,6 @@ class AutocompleteUsers(Component):
def get_htdocs_dirs(self):
from pkg_resources import resource_filename
-
return [('autocompleteusers', resource_filename(__name__, 'htdocs'))]
def get_templates_dirs(self):
@@ -750,7 +749,6 @@ class AutocompleteUsers(Component):
add_script(req, 'autocompleteusers/js/format_item.js')
if template == 'query.html':
add_script(req, 'autocompleteusers/js/autocomplete_query.js')
-
return template, data, content_type
# ITemplateStreamFilter methods
@@ -762,7 +760,7 @@ class AutocompleteUsers(Component):
fields = [field['name'] for field in data['ticket'].fields
if field['type'] == 'select']
fields = set(sum([fnmatch.filter(fields, pattern)
- for pattern in self.selectfields], []))
+ for pattern in self.select_fields], []))
js = ""
@@ -770,7 +768,7 @@ class AutocompleteUsers(Component):
restrict_owner = self.env.config.getbool('ticket', 'restrict_owner')
if req.path_info.startswith('/ticket/'):
- js = """$(document).bind('DOMSubtreeModified', function (){
+ js = """jQuery(document).bind('DOMSubtreeModified', function (){
$( "#field-cc" ).autocomplete({
source: "user_list"
multiple: true,
@@ -779,7 +777,7 @@ class AutocompleteUsers(Component):
});
});"""
if not restrict_owner:
- js = """$(document).bind('DOMSubtreeModified', function (){
+ js = """jQuery(document).bind('DOMSubtreeModified', function (){
$( "#field-cc" ).autocomplete({
source: "user_list",
@@ -817,16 +815,14 @@ class AutocompleteUsers(Component):
formatItem: formatItem
});
});"""
- stream = stream | Transformer('.//head').append(tag.script(Markup(js),
- type='text/javascript'))
+ stream = stream | Transformer('.//head').append(tag.script(Markup(js), type='text/javascript'))
elif filename == 'bh_admin_perms.html':
users = self._get_users(req)
- subjects = ['{"label":"%s %s %s","value":"%s"}' % (user[USER] and '%s' % user[USER] or '',
- user[EMAIL] and '<%s>' % user[EMAIL] or '',
- user[NAME] and '%s' % user[NAME] or '',
- user[USER])
- for value, user in users] # value unused (placeholder needed for sorting)
+ subjects = ['{"label":"%s %s %s","value":"%s"}' % (user[USER] and '%s' % user[USER] or '', user[EMAIL] and
+ '<%s>' % user[EMAIL] or '', user[NAME] and
+ '%s' % user[NAME] or '', user[USER])
+ for value, user in users]
groups = self._get_groups(req)
if groups:
@@ -839,7 +835,7 @@ class AutocompleteUsers(Component):
respond_str_groups = ','.join(subjects_groups).encode('utf-8')
respond_str_groups = '[' + respond_str_groups + ']'
- js = """$(document).ready(function () {
+ js = """jQuery(document).ready(function () {
var subjects = %(subject)s
var groups = %(group)s
$("#gp_subject").autocomplete( {
@@ -855,15 +851,12 @@ class AutocompleteUsers(Component):
formatItem: formatItem
});
});"""
- js_ticket = js % {'subject': respond_str_subjects,
- 'group': respond_str_groups
+ js_ticket = js % {'subject': respond_str_subjects, 'group': respond_str_groups
}
- stream = stream | Transformer('.//head').append(tag.script(Markup(js_ticket),
- type='text/javascript'))
+ stream = stream | Transformer('.//head').append(tag.script(Markup(js_ticket), type='text/javascript'))
return stream
-
# Private methods
def _get_groups(self, req):
@@ -877,9 +870,10 @@ class AutocompleteUsers(Component):
db = self.env.get_db_cnx()
cursor = db.cursor()
cursor.execute("""SELECT DISTINCT username FROM permission""")
- usernames = [user[0] for user in self.env.get_known_users()]
- return sorted([row[0] for row in cursor if not row[0] in usernames
- and row[0].lower().startswith(query)])
+ user_names = [user[0] for user in self.env.get_known_users()]
+ return sorted([row[0]
+ for row in cursor
+ if not row[0] in user_names and row[0].lower().startswith(query)])
def _get_users(self, req):
# instead of known_users, could be
@@ -929,8 +923,7 @@ class KeywordSuggestModule(Component):
def post_process_request(self, req, template, data, content_type):
"""add the necessary javascript and css files
"""
- if req.path_info.startswith('/ticket/') or \
- req.path_info.startswith('/newticket') or \
+ if req.path_info.startswith('/ticket/') or req.path_info.startswith('/newticket') or \
(req.path_info.startswith('/query')):
add_script(req, 'keywordssuggest/js/bootstrap-tagsinput.js')
add_stylesheet(req, 'keywordssuggest/css/bootstrap-tagsinput.css')
@@ -942,8 +935,7 @@ class KeywordSuggestModule(Component):
"""add the jQuery tagsinput function to ticket and query pages
"""
- if not (filename == 'bh_ticket.html' or
- (filename == 'bh_query.html')):
+ if not (filename == 'bh_ticket.html' or (filename == 'bh_query.html')):
return stream
keywords = self._get_keywords_string(req)
@@ -970,7 +962,6 @@ class KeywordSuggestModule(Component):
js = """jQuery(document).ready(function($) {
var keywords = %(keywords)s
-
$('%(field)s').tagsinput({
typeahead: {
source: keywords
@@ -979,7 +970,7 @@ class KeywordSuggestModule(Component):
});"""
if filename == 'bh_query.html':
- js = """$(document).ready(function ($) {
+ js = """jQuery(document).ready(function ($) {
function addAutocompleteBehavior() {
var filters = $('#filters');
var contains = $.contains // jQuery 1.4+
@@ -1040,14 +1031,10 @@ class KeywordSuggestModule(Component):
js_ticket = js % {'field': '#field-' + self.field_opt,
'keywords': keywords
}
- stream = stream | Transformer('.//head').append \
- (tag.script(Markup(js_ticket),
- type='text/javascript'))
+ stream = stream | Transformer('.//head').append(tag.script(Markup(js_ticket), type='text/javascript'))
if req.path_info.startswith('/query'):
js_ticket = js % {'keywords': keywords}
- stream = stream | Transformer('.//head').append \
- (tag.script(Markup(js_ticket),
- type='text/javascript'))
+ stream = stream | Transformer('.//head').append(tag.script(Markup(js_ticket), type='text/javascript'))
return stream
@@ -1070,16 +1057,18 @@ class KeywordSuggestModule(Component):
# get keywords from db
db = self.env.get_db_cnx()
cursor = db.cursor()
- product = self.env.product._data['prefix']
- sql = """SELECT t.keywords FROM ticket AS t WHERE t.keywords IS NOT null AND t.product ='%s'""" % product
-
- cursor.execute(sql)
keywords = []
- for row in cursor:
- if not row[0] == '':
- row_val = str(row[0]).split(',')
- for val in row_val:
- keywords.append(val.strip())
+ if self.env.product is not None:
+ product = self.env.product._data['prefix']
+ sql = """SELECT t.keywords FROM ticket AS t WHERE t.keywords IS NOT null AND t.product ='%s'""" % product
+
+ cursor.execute(sql)
+
+ for row in cursor:
+ if not row[0] == '':
+ row_val = str(row[0]).split(',')
+ for val in row_val:
+ keywords.append(val.strip())
# sort keywords according to frequency of occurrence
if keywords:
keyword_dic = Counter(keywords)
@@ -1092,8 +1081,10 @@ class KeywordSuggestModule(Component):
# component to find duplicate tickets
#DuplicateTicketSearch component basic structure is taken from trac DuplicateTicketSearch plugin
#https://trac-hacks.org/wiki/DuplicateTicketSearchPlugin
+
+
class DuplicateTicketSearch(Component):
- implements(ITemplateProvider, ITemplateStreamFilter,IRequestHandler)
+ implements(ITemplateProvider, ITemplateStreamFilter, IRequestHandler)
# ITemplateProvider methods
@@ -1104,10 +1095,10 @@ class DuplicateTicketSearch(Component):
def get_templates_dirs(self):
return []
-
# ITemplateStreamFilter methods
def filter_stream(self, req, method, filename, stream, data):
+ add_script(req, 'duplicateticketsearch/js/popoverDupSearch.js')
if filename == 'bh_ticket.html':
ticket = data.get('ticket')
@@ -1116,7 +1107,7 @@ class DuplicateTicketSearch(Component):
return stream
- # IRequestHandler methods
+ # IRequestHandler methods
def match_request(self, req):
"""Handle requests sent to /user_list and /ticket/user_list
@@ -1124,23 +1115,78 @@ class DuplicateTicketSearch(Component):
return req.path_info.rstrip('/') == '/duplicate_ticket_search'
def process_request(self, req):
- product = self.env.product._data['prefix']
- query_result = BloodhoundSearchApi(self.env).query(
- req.args.get('q'),
- pagenum=1,
- pagelen=10,
- filter=['type:"ticket"', 'product:"'+product+'"'],
- highlight=True,
- )
- ticket_list = []
- cnt = 0
- for ticket in query_result.docs:
- ticket_list.append(to_json({'summary': query_result.highlighting[cnt]['summary'], 'description': query_result.highlighting[cnt]['content'],'type':ticket['type'] ,'status':ticket['status'] , 'owner':ticket['author'] ,'date':ticket['time'].strftime('%m/%d/%Y') ,'url': ticket['id']}))
- cnt+1
+ terms = req.args.get('q').split(' ')
+
+ with self.env.db_direct_query as db:
+ sql, args = self._search_to_sql(db, ['summary', 'keywords', 'description'], terms)
+ sql2, args2 = self._search_to_sql(db, ['newvalue'], terms)
+ sql3, args3 = self._search_to_sql(db, ['value'], terms)
+ if self.env.product is not None:
+ product_sql = "product='%s' AND" % self.env.product._data['prefix']
+ else:
+ product_sql = ""
+ ticket_list = []
+ ticket_list_value = []
+ for summary, desc, author, type, tid, ts, status, resolution in \
+ db("""SELECT summary, description, reporter, type, id,
+ time, status, resolution
+ FROM ticket
+ WHERE (%s id IN (
+ SELECT id FROM ticket WHERE %s
+ UNION
+ SELECT ticket FROM ticket_change
+ WHERE field='comment' AND %s
+ UNION
+ SELECT ticket FROM ticket_custom WHERE %s
+ ))
+ """ % (product_sql, sql, sql2, sql3),
+ args + args2 + args3):
+
+ summary_term_count = 0
+ summary_list = summary.split(' ')
+ for s in summary_list:
+ for t in terms:
+ if s.lower() == t.lower():
+ summary = summary.replace(s,'<em>'+t+'</em>')
+ summary_term_count += 1
+ break
+
+ ticket_list.append(to_json({'summary': summary, 'description': desc, 'type': type, 'status': status,
+ 'owner': author, 'date': user_time(req, format_datetime, ts), 'url': tid}))
+ ticket_list_value.append(summary_term_count)
+ ticket_list = [x for (y, x) in sorted(zip(ticket_list_value, ticket_list))]
str_list = '['+','.join(ticket_list)+']'
req.send(str_list, 'application/json')
+ # Private methods
+
+ def _search_to_sql(self, db, columns, terms):
+ """Convert a search query into an SQL WHERE clause and corresponding
+ parameters.
+
+ The result is returned as an `(sql, params)` tuple.
+ """
+ assert columns and terms
+
+ likes = ['%s %s' % (i, db.like()) for i in columns]
+ c = ' OR '.join(likes)
+ sql = '(' + ') OR ('.join([c] * len(terms)) + ')'
+ args = []
+ for t in terms:
+ args.extend(['%' + db.like_escape(t) + '%'] * len(columns))
+ return sql, tuple(args)
+
+
+
+
+
+
+
+
+
+
+
Re: svn commit: r1611444 - in /bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme:
htdocs/js/DupeSearch.js htdocs/js/popoverDupSearch.js theme.py
Posted by Thimal Kempitiya <tk...@gmail.com>.
change according to the PEP 8 and trac style
add the duplicate ticket feature to the popover ticket create
On Fri, Jul 18, 2014 at 12:55 AM, <th...@apache.org> wrote:
> Author: thimal
> Date: Thu Jul 17 19:25:49 2014
> New Revision: 1611444
>
> URL: http://svn.apache.org/r1611444
> Log:
> add the duplicate feature to popover ticket create and did some style
> change according to the bloodhound style
>
> Added:
>
> bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/htdocs/js/popoverDupSearch.js
> (with props)
> Modified:
>
> bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/htdocs/js/DupeSearch.js
>
> bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/theme.py
>
> Modified:
> bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/htdocs/js/DupeSearch.js
> URL:
> http://svn.apache.org/viewvc/bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/htdocs/js/DupeSearch.js?rev=1611444&r1=1611443&r2=1611444&view=diff
>
> ==============================================================================
> ---
> bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/htdocs/js/DupeSearch.js
> (original)
> +++
> bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/htdocs/js/DupeSearch.js
> Thu Jul 17 19:25:49 2014
> @@ -1,17 +1,17 @@
> -$(document).ready(function() {
> +jQuery(document).ready(function() {
>
> $('div#content.ticket h2#vc-summary').blur(function() {
> var text = $('div#content.ticket h2#vc-summary').text();
> if (text.length > 0) {
>
> var html = '<h5 class="loading">Loading related
> tickets..</h5>';
> - var dupeticketlistDiv = $('div#content.ticket
> h2#vc-summary + div#dupeticketlist');
> - if (dupeticketlistDiv.length == 0) {
> + var duplicate_eticket_list_div =
> $('div#content.ticket h2#vc-summary + div#dupeticketlist');
> + if (duplicate_eticket_list_div.length == 0) {
> $('div#content.ticket
> h2#vc-summary').after('<div id="dupeticketlist"
> style="display:none;"></div>');
> - dupeticketlistDiv = $('div#content.ticket
> h2#vc-summary + div#dupeticketlist');
> + duplicate_eticket_list_div =
> $('div#content.ticket h2#vc-summary + div#dupeticketlist');
> }
> - $('ul',dupeticketlistDiv).slideUp('fast');
> - dupeticketlistDiv.html(html).slideDown();
> + $('ul',duplicate_eticket_list_div).slideUp('fast');
> + duplicate_eticket_list_div.html(html).slideDown();
>
> $.ajax({
> url:'duplicate_ticket_search',
> @@ -20,48 +20,51 @@ $(document).ready(function() {
>
> success: function(data, status) {
> var tickets =data;
> - var ticketBaseHref = 'ticket/';
> - var searchBaseHref =
> 'bhsearch?type=ticket&q=';
> - var maxTickets = 15;
> + var ticket_base_Href = 'ticket/';
> + var search_base_Href =
> 'bhsearch?type=ticket&q=';
> + var max_tickets = 15;
>
> var html = '';
> if (tickets === null) {
> // error
> -
> dupeticketlistDiv.html('<h5 class="error">Error loading tickets.</h5>');
> +
> duplicate_eticket_list_div.html('<h5 class="error">Error loading
> tickets.</h5>');
> } else if (tickets.length <= 0) {
> // no dupe tickets
> -
> dupeticketlistDiv.slideUp();
> +
> duplicate_eticket_list_div.slideUp();
> } else {
> - html = '<h5>Possible
> related tickets:</h5><ul style="display:none;">'
> + html = '<h5>Possible
> related tickets:</h5><ul id="results">'
> tickets =
> tickets.reverse();
>
> - for (var i = 0; i <
> tickets.length && i < maxTickets; i++) {
> + for (var i = 0; i <
> tickets.length && i < max_tickets; i++) {
> var ticket =
> tickets[i];
> html += '<li
> class="highlight_matches" title="' + ticket.description +
> - '"><a
> href="' + ticketBaseHref + ticket.url +
> + '"><a
> href="' + ticket_base_Href + ticket.url +
>
> '"><span class="' + htmlencode(ticket.status) + '">#' +
> -
> ticket.url + '</span></a>: ' + htmlencode(ticket.type) + ': ' +
> -
> ticket.summary + '(' + htmlencode(ticket.status) +
> -
> (ticket.url ? ': ' + htmlencode(ticket.url) : '') +
> - ')' +
> '</li>'
> +
> ticket.url + '</span></a>: ' +
> +
> ticket.summary + ' (' + htmlencode(ticket.status)
> + +': '+
> htmlencode(ticket.type) +
> + ') '
> +'<span class="author">created by '+ticket.owner +'</span> <span
> class="date">at ' +ticket.date+ '</span></li>'
> }
> html += '</ul>';
> - if (tickets.length >
> maxTickets) {
> + if (tickets.length >
> max_tickets) {
> var text =
> $('div#content.ticket input#field-summary').val();
> - html += '<a
> href="' + searchBaseHref + escape(text) + '">More..</a>';
> + html += '<a
> href="' + search_base_Href + escape(text) + '">More..</a>';
> }
>
> -
> dupeticketlistDiv.html(html);
> - $('> ul',
> dupeticketlistDiv).slideDown();
> +
> duplicate_eticket_list_div.html(html);
> + $('> ul',
> duplicate_eticket_list_div).slideDown();
>
> }
>
> },
> error: function(xhr, textStatus,
> exception) {
> - dupeticketlistDiv.html('<h5
> class="error">Error loading tickets: ' + textStatus + '</h5>');
> +
> duplicate_eticket_list_div.html('<h5 class="error">Error loading tickets: '
> + textStatus + '</h5>');
> }
> });
> - }
> + }else{
> + var duplicate_eticket_list_div = $('div#content.ticket
> div#dupeticketlist');
> + duplicate_eticket_list_div.slideUp('fast');
> + }
> });
>
> function htmlencode(text) {
>
> Added:
> bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/htdocs/js/popoverDupSearch.js
> URL:
> http://svn.apache.org/viewvc/bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/htdocs/js/popoverDupSearch.js?rev=1611444&view=auto
>
> ==============================================================================
> ---
> bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/htdocs/js/popoverDupSearch.js
> (added)
> +++
> bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/htdocs/js/popoverDupSearch.js
> Thu Jul 17 19:25:49 2014
> @@ -0,0 +1,73 @@
> +jQuery(document).ready(function() {
> +
> + $('input#field-summary.input-block-level').blur(function() {
> + var text =
> $('input#field-summary.input-block-level').val();
> + if (text.length > 0) {
> +
> + var html = '<h5 class="loading">Loading related
> tickets..</h5>';
> + var dupelicate_ticket_list_div =
> $('div.popover-content input#field-summary + div#dupeticketlist');
> + if (dupelicate_ticket_list_div.length == 0) {
> + $('div.popover-content
> input#field-summary').after('<div id="dupeticketlist"
> style="display:none;"></div>');
> + dupelicate_ticket_list_div =
> $('div.popover-content input#field-summary + div#dupeticketlist');
> + }
> + $(dupelicate_ticket_list_div).slideUp('fast');
> + dupelicate_ticket_list_div.html(html).slideDown();
> +
> + $.ajax({
> + url:'duplicate_ticket_search',
> + data:{q:text},
> + type:'GET',
> + success: function(data, status) {
> + var tickets =data;
> + var ticket_base_href = 'ticket/';
> + var search_base_Href =
> 'bhsearch?type=ticket&q=';
> + var max_tickets = 5;
> +
> + var html = '';
> + if (tickets === null) {
> + // error
> +
> dupelicate_ticket_list_div.html('<h5 class="error">Error loading
> tickets.</h5>');
> + } else if (tickets.length <= 0) {
> + // no dupe tickets
> +
> dupelicate_ticket_list_div.slideUp();
> + } else {
> + html = '<h5>Possible
> related tickets:</h5><ul style="display:none;">';
> + //tickets =
> tickets.reverse();
> +
> + for (var i = 0; i <
> tickets.length && i < max_tickets; i++) {
> + var ticket =
> tickets[i];
> + html += '<li
> class="highlight_matches" title="' + ticket.description +
> + '"><a
> href="' + ticket_base_href + ticket.url +
> +
> '"><span class="' + htmlencode(ticket.status) + '">#' +
> +
> ticket.url + '</span></a>: ' + htmlencode(ticket.type) + ': ' +
> +
> ticket.summary + '(' + htmlencode(ticket.status) +
> +
> (ticket.url ? ': ' + htmlencode(ticket.url) : '') +
> + ')' +
> '</li>'
> + }
> + html += '</ul>';
> + if (tickets.length >
> max_tickets) {
> + var text =
> $('div.popover-content input#field-summary').val();
> + html += '<a
> href="' + search_base_Href + escape(text) + '">More..</a>';
> + }
> +
> +
> dupelicate_ticket_list_div.html(html);
> + $('> ul',
> dupelicate_ticket_list_div).slideDown();
> +
> + }
> +
> + },
> + error: function(xhr, textStatus,
> exception) {
> +
> dupelicate_ticket_list_div.html('<h5 class="error">Error loading tickets: '
> + textStatus + '</h5>');
> + }
> + });
> + }else{
> + var dupelicate_ticket_list_div = $('div.popover-content
> input#field-summary + div#dupeticketlist');
> + dupelicate_ticket_list_div.slideUp();
> + }
> + });
> +
> + function htmlencode(text) {
> + return $('<div/>').text(text).html().replace(/"/g,
> '"').replace(/'/g, ''');
> + }
> +});
> +
>
> Propchange:
> bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/htdocs/js/popoverDupSearch.js
>
> ------------------------------------------------------------------------------
> svn:eol-style = native
>
> Modified:
> bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/theme.py
> URL:
> http://svn.apache.org/viewvc/bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/theme.py?rev=1611444&r1=1611443&r2=1611444&view=diff
>
> ==============================================================================
> ---
> bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/theme.py
> (original)
> +++
> bloodhound/branches/bep_0013_dynamic_clientside_features/bloodhound_theme/bhtheme/theme.py
> Thu Jul 17 19:25:49 2014
> @@ -19,6 +19,7 @@
>
> import sys
>
> +from trac.util.datefmt import format_datetime, user_time
> from collections import Counter
>
> import fnmatch
> @@ -42,7 +43,7 @@ from trac.util.presentation import to_js
> from trac.versioncontrol.web_ui.browser import BrowserModule
> from trac.web.api import IRequestFilter, IRequestHandler,
> ITemplateStreamFilter
> from trac.web.chrome import (add_stylesheet, add_warning,
> INavigationContributor,
> - ITemplateProvider, prevnext_nav, Chrome,
> add_script)
> + ITemplateProvider, prevnext_nav, Chrome,
> add_script, add_script_data)
> from trac.wiki.admin import WikiAdmin
> from trac.wiki.formatter import format_to_html
>
> @@ -694,8 +695,8 @@ class AutocompleteUsers(Component):
> implements(IRequestFilter, IRequestHandler,
> ITemplateProvider, ITemplateStreamFilter)
>
> - selectfields = ListOption('autocomplete', 'fields', default='',
> - doc='select fields to transform to
> autocomplete text boxes')
> + select_fields = ListOption('autocomplete', 'fields', default='',
> + doc='select fields to transform to
> autocomplete text boxes')
>
> # IRequestHandler methods
>
> @@ -711,16 +712,15 @@ class AutocompleteUsers(Component):
> if req.args.get('users', '1') == '1':
> users = self._get_users(req)
> if req.perm.has_permission('EMAIL_VIEW'):
> - subjects = ['{"label":"%s %s %s","value":"%s"}' %
> (user[USER] and '%s' % user[USER] or '',
> - user[EMAIL] and '<%s>' %
> user[EMAIL] or '',
> - user[NAME] and '%s' %
> user[NAME] or '',
> - user[USER])
> - for value, user in users] # value unused
> (placeholder needed for sorting)
> + subjects = ['{"label":"%s %s %s","value":"%s"}' %
> (user[USER] and '%s' % user[USER] or
> + '',
> user[EMAIL] and '<%s>' % user[EMAIL] or
> + '',
> user[NAME] and '%s' % user[NAME] or
> + '',
> user[USER])
> + for value, user in users]
> else:
> - subjects = ['{"label":"%s %s","value":"%s"}' %
> (user[USER] and '%s' % user[USER] or '',
> - user[NAME] and '%s' %
> user[NAME] or '',
> - user[USER])
> - for value, user in users] # value unused
> (placeholder needed for sorting)
> + subjects = ['{"label":"%s %s","value":"%s"}' %
> (user[USER] and '%s' % user[USER] or '', user[NAME] and
> + '%s' %
> user[NAME] or'', user[USER])
> + for value, user in users]
>
> respond_str = ','.join(subjects).encode('utf-8')
> respond_str = '[' + respond_str + ']'
> @@ -730,7 +730,6 @@ class AutocompleteUsers(Component):
>
> def get_htdocs_dirs(self):
> from pkg_resources import resource_filename
> -
> return [('autocompleteusers', resource_filename(__name__,
> 'htdocs'))]
>
> def get_templates_dirs(self):
> @@ -750,7 +749,6 @@ class AutocompleteUsers(Component):
> add_script(req, 'autocompleteusers/js/format_item.js')
> if template == 'query.html':
> add_script(req,
> 'autocompleteusers/js/autocomplete_query.js')
> -
> return template, data, content_type
>
> # ITemplateStreamFilter methods
> @@ -762,7 +760,7 @@ class AutocompleteUsers(Component):
> fields = [field['name'] for field in data['ticket'].fields
> if field['type'] == 'select']
> fields = set(sum([fnmatch.filter(fields, pattern)
> - for pattern in self.selectfields], []))
> + for pattern in self.select_fields], []))
>
> js = ""
>
> @@ -770,7 +768,7 @@ class AutocompleteUsers(Component):
>
> restrict_owner = self.env.config.getbool('ticket',
> 'restrict_owner')
> if req.path_info.startswith('/ticket/'):
> - js = """$(document).bind('DOMSubtreeModified', function
> (){
> + js = """jQuery(document).bind('DOMSubtreeModified',
> function (){
> $( "#field-cc" ).autocomplete({
> source: "user_list"
> multiple: true,
> @@ -779,7 +777,7 @@ class AutocompleteUsers(Component):
> });
> });"""
> if not restrict_owner:
> - js = """$(document).bind('DOMSubtreeModified',
> function (){
> + js = """jQuery(document).bind('DOMSubtreeModified',
> function (){
>
> $( "#field-cc" ).autocomplete({
> source: "user_list",
> @@ -817,16 +815,14 @@ class AutocompleteUsers(Component):
> formatItem: formatItem
> });
> });"""
> - stream = stream |
> Transformer('.//head').append(tag.script(Markup(js),
> -
> type='text/javascript'))
> + stream = stream |
> Transformer('.//head').append(tag.script(Markup(js),
> type='text/javascript'))
>
> elif filename == 'bh_admin_perms.html':
> users = self._get_users(req)
> - subjects = ['{"label":"%s %s %s","value":"%s"}' % (user[USER]
> and '%s' % user[USER] or '',
> - user[EMAIL] and '<%s>' %
> user[EMAIL] or '',
> - user[NAME] and '%s' % user[NAME] or
> '',
> - user[USER])
> - for value, user in users] # value unused
> (placeholder needed for sorting)
> + subjects = ['{"label":"%s %s %s","value":"%s"}' % (user[USER]
> and '%s' % user[USER] or '', user[EMAIL] and
> + '<%s>' %
> user[EMAIL] or '', user[NAME] and
> + '%s' %
> user[NAME] or '', user[USER])
> + for value, user in users]
>
> groups = self._get_groups(req)
> if groups:
> @@ -839,7 +835,7 @@ class AutocompleteUsers(Component):
> respond_str_groups =
> ','.join(subjects_groups).encode('utf-8')
> respond_str_groups = '[' + respond_str_groups + ']'
>
> - js = """$(document).ready(function () {
> + js = """jQuery(document).ready(function () {
> var subjects = %(subject)s
> var groups = %(group)s
> $("#gp_subject").autocomplete( {
> @@ -855,15 +851,12 @@ class AutocompleteUsers(Component):
> formatItem: formatItem
> });
> });"""
> - js_ticket = js % {'subject': respond_str_subjects,
> - 'group': respond_str_groups
> + js_ticket = js % {'subject': respond_str_subjects, 'group':
> respond_str_groups
> }
> - stream = stream |
> Transformer('.//head').append(tag.script(Markup(js_ticket),
> -
> type='text/javascript'))
>
> + stream = stream |
> Transformer('.//head').append(tag.script(Markup(js_ticket),
> type='text/javascript'))
> return stream
>
> -
> # Private methods
>
> def _get_groups(self, req):
> @@ -877,9 +870,10 @@ class AutocompleteUsers(Component):
> db = self.env.get_db_cnx()
> cursor = db.cursor()
> cursor.execute("""SELECT DISTINCT username FROM permission""")
> - usernames = [user[0] for user in self.env.get_known_users()]
> - return sorted([row[0] for row in cursor if not row[0] in usernames
> - and row[0].lower().startswith(query)])
> + user_names = [user[0] for user in self.env.get_known_users()]
> + return sorted([row[0]
> + for row in cursor
> + if not row[0] in user_names and
> row[0].lower().startswith(query)])
>
> def _get_users(self, req):
> # instead of known_users, could be
> @@ -929,8 +923,7 @@ class KeywordSuggestModule(Component):
> def post_process_request(self, req, template, data, content_type):
> """add the necessary javascript and css files
> """
> - if req.path_info.startswith('/ticket/') or \
> - req.path_info.startswith('/newticket') or \
> + if req.path_info.startswith('/ticket/') or
> req.path_info.startswith('/newticket') or \
> (req.path_info.startswith('/query')):
> add_script(req,
> 'keywordssuggest/js/bootstrap-tagsinput.js')
> add_stylesheet(req,
> 'keywordssuggest/css/bootstrap-tagsinput.css')
> @@ -942,8 +935,7 @@ class KeywordSuggestModule(Component):
> """add the jQuery tagsinput function to ticket and query pages
> """
>
> - if not (filename == 'bh_ticket.html' or
> - (filename == 'bh_query.html')):
> + if not (filename == 'bh_ticket.html' or (filename ==
> 'bh_query.html')):
> return stream
>
> keywords = self._get_keywords_string(req)
> @@ -970,7 +962,6 @@ class KeywordSuggestModule(Component):
> js = """jQuery(document).ready(function($) {
> var keywords = %(keywords)s
>
> -
> $('%(field)s').tagsinput({
> typeahead: {
> source: keywords
> @@ -979,7 +970,7 @@ class KeywordSuggestModule(Component):
> });"""
>
> if filename == 'bh_query.html':
> - js = """$(document).ready(function ($) {
> + js = """jQuery(document).ready(function ($) {
> function addAutocompleteBehavior() {
> var filters = $('#filters');
> var contains = $.contains // jQuery 1.4+
> @@ -1040,14 +1031,10 @@ class KeywordSuggestModule(Component):
> js_ticket = js % {'field': '#field-' + self.field_opt,
> 'keywords': keywords
> }
> - stream = stream | Transformer('.//head').append \
> - (tag.script(Markup(js_ticket),
> - type='text/javascript'))
> + stream = stream |
> Transformer('.//head').append(tag.script(Markup(js_ticket),
> type='text/javascript'))
> if req.path_info.startswith('/query'):
> js_ticket = js % {'keywords': keywords}
> - stream = stream | Transformer('.//head').append \
> - (tag.script(Markup(js_ticket),
> - type='text/javascript'))
> + stream = stream |
> Transformer('.//head').append(tag.script(Markup(js_ticket),
> type='text/javascript'))
>
> return stream
>
> @@ -1070,16 +1057,18 @@ class KeywordSuggestModule(Component):
> # get keywords from db
> db = self.env.get_db_cnx()
> cursor = db.cursor()
> - product = self.env.product._data['prefix']
> - sql = """SELECT t.keywords FROM ticket AS t WHERE t.keywords IS
> NOT null AND t.product ='%s'""" % product
> -
> - cursor.execute(sql)
> keywords = []
> - for row in cursor:
> - if not row[0] == '':
> - row_val = str(row[0]).split(',')
> - for val in row_val:
> - keywords.append(val.strip())
> + if self.env.product is not None:
> + product = self.env.product._data['prefix']
> + sql = """SELECT t.keywords FROM ticket AS t WHERE t.keywords
> IS NOT null AND t.product ='%s'""" % product
> +
> + cursor.execute(sql)
> +
> + for row in cursor:
> + if not row[0] == '':
> + row_val = str(row[0]).split(',')
> + for val in row_val:
> + keywords.append(val.strip())
> # sort keywords according to frequency of occurrence
> if keywords:
> keyword_dic = Counter(keywords)
> @@ -1092,8 +1081,10 @@ class KeywordSuggestModule(Component):
> # component to find duplicate tickets
> #DuplicateTicketSearch component basic structure is taken from trac
> DuplicateTicketSearch plugin
> #https://trac-hacks.org/wiki/DuplicateTicketSearchPlugin
> +
> +
> class DuplicateTicketSearch(Component):
> - implements(ITemplateProvider, ITemplateStreamFilter,IRequestHandler)
> + implements(ITemplateProvider, ITemplateStreamFilter, IRequestHandler)
>
> # ITemplateProvider methods
>
> @@ -1104,10 +1095,10 @@ class DuplicateTicketSearch(Component):
> def get_templates_dirs(self):
> return []
>
> -
> # ITemplateStreamFilter methods
>
> def filter_stream(self, req, method, filename, stream, data):
> + add_script(req, 'duplicateticketsearch/js/popoverDupSearch.js')
>
> if filename == 'bh_ticket.html':
> ticket = data.get('ticket')
> @@ -1116,7 +1107,7 @@ class DuplicateTicketSearch(Component):
>
> return stream
>
> - # IRequestHandler methods
> + # IRequestHandler methods
>
> def match_request(self, req):
> """Handle requests sent to /user_list and /ticket/user_list
> @@ -1124,23 +1115,78 @@ class DuplicateTicketSearch(Component):
> return req.path_info.rstrip('/') == '/duplicate_ticket_search'
>
> def process_request(self, req):
> - product = self.env.product._data['prefix']
> - query_result = BloodhoundSearchApi(self.env).query(
> - req.args.get('q'),
> - pagenum=1,
> - pagelen=10,
> - filter=['type:"ticket"', 'product:"'+product+'"'],
> - highlight=True,
> - )
> - ticket_list = []
> - cnt = 0
> - for ticket in query_result.docs:
> - ticket_list.append(to_json({'summary':
> query_result.highlighting[cnt]['summary'], 'description':
> query_result.highlighting[cnt]['content'],'type':ticket['type']
> ,'status':ticket['status'] , 'owner':ticket['author']
> ,'date':ticket['time'].strftime('%m/%d/%Y') ,'url': ticket['id']}))
> - cnt+1
> + terms = req.args.get('q').split(' ')
> +
> + with self.env.db_direct_query as db:
> + sql, args = self._search_to_sql(db, ['summary', 'keywords',
> 'description'], terms)
> + sql2, args2 = self._search_to_sql(db, ['newvalue'], terms)
> + sql3, args3 = self._search_to_sql(db, ['value'], terms)
> + if self.env.product is not None:
> + product_sql = "product='%s' AND" %
> self.env.product._data['prefix']
> + else:
> + product_sql = ""
> + ticket_list = []
> + ticket_list_value = []
> + for summary, desc, author, type, tid, ts, status, resolution
> in \
> + db("""SELECT summary, description, reporter, type, id,
> + time, status, resolution
> + FROM ticket
> + WHERE (%s id IN (
> + SELECT id FROM ticket WHERE %s
> + UNION
> + SELECT ticket FROM ticket_change
> + WHERE field='comment' AND %s
> + UNION
> + SELECT ticket FROM ticket_custom WHERE %s
> + ))
> + """ % (product_sql, sql, sql2, sql3),
> + args + args2 + args3):
> +
> + summary_term_count = 0
> + summary_list = summary.split(' ')
> + for s in summary_list:
> + for t in terms:
> + if s.lower() == t.lower():
> + summary = summary.replace(s,'<em>'+t+'</em>')
> + summary_term_count += 1
> + break
> +
> + ticket_list.append(to_json({'summary': summary,
> 'description': desc, 'type': type, 'status': status,
> + 'owner': author, 'date':
> user_time(req, format_datetime, ts), 'url': tid}))
> + ticket_list_value.append(summary_term_count)
> + ticket_list = [x for (y, x) in sorted(zip(ticket_list_value,
> ticket_list))]
> str_list = '['+','.join(ticket_list)+']'
>
> req.send(str_list, 'application/json')
>
> + # Private methods
> +
> + def _search_to_sql(self, db, columns, terms):
> + """Convert a search query into an SQL WHERE clause and
> corresponding
> + parameters.
> +
> + The result is returned as an `(sql, params)` tuple.
> + """
> + assert columns and terms
> +
> + likes = ['%s %s' % (i, db.like()) for i in columns]
> + c = ' OR '.join(likes)
> + sql = '(' + ') OR ('.join([c] * len(terms)) + ')'
> + args = []
> + for t in terms:
> + args.extend(['%' + db.like_escape(t) + '%'] * len(columns))
> + return sql, tuple(args)
> +
> +
> +
> +
> +
> +
> +
> +
> +
> +
> +
>
>
>
>
>
>
--
*Thimal Kempitiya <http://www.facebook.com/thimalk> UndergraduateDepartment
of Computer Science and Engineering University of Moratuwa.*