Ticket #4775: multiple_keywords_ticket_query.diff
| File multiple_keywords_ticket_query.diff, 7.1 KB (added by wijister@…, 5 years ago) |
|---|
-
htdocs/js/query.js
219 219 td.className = "filter"; 220 220 if (property.type == "select") { 221 221 var element = createSelect(propertyName, property.options, true); 222 } else if (property.type == "text" ) {222 } else if (property.type == "text" || property.type == "keywords") { 223 223 var element = document.createElement("input"); 224 224 element.type = "text"; 225 225 element.name = propertyName; -
trac/ticket/api.py
172 172 fields.append(field) 173 173 174 174 # Advanced text fields 175 for name in ('keywords', 'cc', ): 176 field = {'name': name, 'type': 'text', 'label': name.title()} 177 fields.append(field) 175 fields.append({'name': 'keywords', 'type': 'keywords', 'label': 'Keywords'}) 176 fields.append({'name': 'cc', 'type': 'text', 'label': 'Cc'}) 178 177 179 178 for field in self.get_custom_fields(): 180 179 if field['name'] in [f['name'] for f in fields]: -
trac/ticket/tests/query.py
312 312 self.assertEqual([], args) 313 313 tickets = query.execute(Mock(href=self.env.href)) 314 314 315 def test_constrained_allof_keywords(self): 316 query = Query.from_string(self.env, None, 317 'keywords@=foo bar', 318 order='id') 319 sql, args = query.get_sql() 320 self.assertEqual(sql, 321 """SELECT t.id AS id,t.summary AS summary,t.keywords AS keywords,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.time AS time,t.changetime AS changetime,priority.value AS priority_value 322 FROM ticket AS t 323 LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority) 324 WHERE (COALESCE(t.keywords,'') LIKE %s ESCAPE '/' OR COALESCE(t.keywords,'') LIKE %s ESCAPE '/') 325 ORDER BY COALESCE(t.id,0)=0,t.id""") 326 self.assertEqual(['%foo%', '%bar%'], args) 327 tickets = query.execute(Mock(href=self.env.href)) 328 315 329 def test_csv_escape(self): 316 330 query = Mock(get_columns=lambda: ['col1'], 317 331 execute=lambda r,c: [{'col1': 'value, needs escaped'}]) … … 336 350 '<em class="error">[Error: Query filter requires ' 337 351 'field and constraints separated by a "="]</em>') 338 352 339 340 353 def suite(): 341 354 suite = unittest.TestSuite() 342 355 suite.addTest(unittest.makeSuite(QueryTestCase, 'test')) -
trac/ticket/query.py
91 91 raise QuerySyntaxError('Query filter requires field name') 92 92 # from last char of `field`, get the mode of comparison 93 93 mode, neg = '', '' 94 if field[-1] in ('~', '^', '$' ):94 if field[-1] in ('~', '^', '$', '@'): 95 95 mode = field[-1] 96 96 field = field[:-1] 97 97 if field[-1] == '!': … … 138 138 if k != 'id' and k in cols]: 139 139 constraint = self.constraints[col] 140 140 if len(constraint) == 1 and constraint[0] \ 141 and not constraint[0][0] in ('!', '~', '^', '$' ):141 and not constraint[0][0] in ('!', '~', '^', '$', '@'): 142 142 if col in cols: 143 143 cols.remove(col) 144 144 if col == 'status' and not 'closed' in constraint \ … … 301 301 302 302 if mode == '': 303 303 return ("COALESCE(%s,'')%s=%%s" % (name, neg and '!' or ''), 304 value)304 [value]) 305 305 if not value: 306 306 return None 307 307 db = self.env.get_db_cnx() 308 308 value = db.like_escape(value) 309 310 # special case - search for keywords separated by space 311 if mode == '@': 312 words = value.split(' ') 313 con = "(" 314 count = 0 315 args = [] 316 317 # iterate the words 318 for w in words: 319 # word is empty, let's skip it 320 if w == '': 321 continue 322 if count > 0: 323 # need to append to the OR sequence 324 con = con + " OR "; 325 con = con + "COALESCE(%s,'') %s" % (name, db.like()) 326 args.append('%' + w + '%') 327 count = count + 1 328 329 if (count == 0): 330 # there are no words to filter by 331 return None 332 con = con + ")" 333 return (con, args) 309 334 if mode == '~': 310 335 value = '%' + value + '%' 311 336 elif mode == '^': … … 314 339 value = '%' + value 315 340 return ("COALESCE(%s,'') %s%s" % (name, neg and 'NOT ' or '', 316 341 db.like()), 317 value)342 [value]) 318 343 319 344 clauses = [] 320 345 args = [] … … 323 348 # starts-with, negation, etc.) 324 349 neg = v[0].startswith('!') 325 350 mode = '' 326 if len(v[0]) > neg and v[0][neg] in ('~', '^', '$' ):351 if len(v[0]) > neg and v[0][neg] in ('~', '^', '$', '@'): 327 352 mode = v[0][neg] 328 353 329 354 # Special case id ranges … … 368 393 else: 369 394 clauses.append("(" + " OR ".join( 370 395 [item[0] for item in constraint_sql]) + ")") 371 args += [item[1] for item in constraint_sql] 396 for item in constraint_sql: 397 args.extend(item[1]) 372 398 elif len(v) == 1: 373 399 constraint_sql = get_constraint_sql(k, v[0], mode, neg) 374 400 if constraint_sql: 375 401 clauses.append(constraint_sql[0]) 376 args. append(constraint_sql[1])402 args.extend(constraint_sql[1]) 377 403 378 404 clauses = filter(None, clauses) 379 405 if clauses: … … 438 464 if neg: 439 465 val = val[1:] 440 466 mode = '' 441 if val[:1] in ('~', '^', '$' ):467 if val[:1] in ('~', '^', '$', '@'): 442 468 mode, val = val[:1], val[1:] 443 469 constraint['mode'] = (neg and '!' or '') + mode 444 470 constraint['values'].append(val) … … 479 505 {'name': "is", 'value': ""}, 480 506 {'name': "is not", 'value': "!"} 481 507 ] 508 modes['keywords'] = [{'name': "contains all of", 'value': "@"}] 509 modes['keywords'].extend(modes['text']) 482 510 483 511 groups = {} 484 512 groupsequence = []
