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, '&quot;').replace(/'/g, '&apos;');
+	}
+});
+

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,
> '&quot;').replace(/'/g, '&apos;');
> +       }
> +});
> +
>
> 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.*