Roundup Tracker

You can turn the default search box in the classic (or other) tracker into a jump/search box.

All these changes are done on a per tracker basis and requires no changes to the core roundup code. So all these files live under your tracker directory.

Html page change

Change the existing search form in html/page.html. It should look something like:

    <form method="GET" action="issue">
       <input type="hidden" name="@columns"
              tal:attributes="value columns_showall"
              value="id,activity,title,creator,assignedto,status"/>
       <input type="hidden" name="@sort" value="activity"/>
       <input type="hidden" name="@group" value="priority"/>
       <input id="search-text" name="@search_text" size="10"
              tal:attributes="value request/search_text | default" />
       <input type="submit" id="submit" name="submit" value="Search"
              i18n:attributes="value" />
     </form>

Add a hidden @action field with the value JumpSearch to what exists so it looks like:

     <form method="GET" action="issue">
       <input type="hidden" name="@columns"
              tal:attributes="value columns_showall"
              value="id,activity,title,creator,assignedto,status"/>
       <input type="hidden" name="@sort" value="activity"/>
       <input type="hidden" name="@group" value="priority"/>
       <input type="hidden" name="@action" value="JumpSearch"/>
       <input id="search-text" name="@search_text" size="10"
              tal:attributes="value request/search_text | default" />
       <input type="submit" id="submit" name="submit" value="Search"
              i18n:attributes="value" />
     </form>

Consider replacing the submit button label text with something more descriptive. Perhaps "Goto/Search".

Add the extension

Now we create the action JumpSearch. Create an extensions/jumpsearch.py file in your tracker. Fill it with the following:

   1 import roundup.cgi.actions
   2 import cgi
   3 
   4 import re
   5 
   6 class JumpSearch(roundup.cgi.actions.SearchAction, roundup.cgi.actions.ShowAction):
   7     ''' Used to create a combo Jump to issue and Search issues box.
   8 
   9         If a single unquoted number is entered jump to that issue.
  10         If the search item looks like an item designator (user2,
  11         issue23) jump to that item.
  12         If a list of numbers is entered (e.g. by copying a dependson
  13         entry box), transform the search to an id search for those
  14         issue items.
  15         Otherwise call the standard Search function.
  16     '''
  17 
  18     def handle(self):
  19         if '@search_text' in self.form:
  20             # get the value of the @search_text field
  21             # only the first is used if there are multiples.
  22             search_text = self.form.getfirst('@search_text')
  23         else:
  24             # if there is no @search_text pass it off to the default
  25             # search action to have it deal with it
  26             roundup.cgi.actions.SearchAction.handle(self)
  27             return
  28 
  29         if search_text.isdigit():
  30             # we have a single number (unquoted) with possible whitespace
  31             # so jump to that issue.
  32 
  33             # add the @number field used to do the redirect
  34             self.form.list.append(cgi.MiniFieldStorage("@number", search_text))
  35             # add the @type field used to do the redirect to an issue with
  36             # number @number.
  37             self.form.list.append(cgi.MiniFieldStorage("@type", "issue"))
  38             # call the existing ShowAction for display
  39             return roundup.cgi.actions.ShowAction.handle(self)
  40 
  41         # really wish I could do an elif match=....
  42         # match msg22 or user13 or issue10
  43         designator=re.compile(r"^(\w+([0-9]+?)$") # split class and node id
  44         match=re.match(designator, search_text)
  45         if match:
  46             # we have something that could be user2 or msg2389
  47             # try jumping to it
  48             klass = match.group(1)
  49             nodeid = match.group(2)
  50 
  51             #verify that class exists
  52             try:
  53                 self.db.getclass(klass)
  54             except KeyError:
  55                 # the class doesn't exist
  56                 # call the existing SearchAction for display
  57                 return roundup.cgi.actions.SearchAction.handle(self)
  58 
  59             # class exists show the designator
  60             self.form.list.append(cgi.MiniFieldStorage("@number", nodeid))
  61             # add the @type field used to do the redirect to an issue with
  62             # number @number.
  63             self.form.list.append(cgi.MiniFieldStorage("@type", klass))
  64             # note that self.form.list still has @search_text in it,
  65             # but it's ignored by ShowAction so don't waste cycles
  66             # removing it.
  67             return roundup.cgi.actions.ShowAction.handle(self)
  68 
  69         # match 2,3,4 5 as list of id's.
  70         # NOTE: this has to be done in two steps as my prefered
  71         # regexp:
  72         #    r"^\s*(\d+)(?:[,\s]+(\d+))+\s*$"
  73         # only returns the first and last match (e.g. in 1,2,3,4, I
  74         # get only 1 and 4). I want this to return 1 and 2 and 3 and 4
  75         # but..... Why, who knows. This first match makes sure the
  76         # list is numbers with spaces/commas only.
  77         idlist=re.compile(r"^\s*\d+(?:[,\s]+\d+)+\s*$") # match id list
  78         match=re.match(idlist, search_text)
  79         if match:
  80             # overall pattern matches.
  81             idelement=re.compile(r"(\d+)[,\s]*")
  82             nums=re.findall(idelement, search_text)
  83             ids=','.join(nums)
  84             # remove @search_text entry and add search for id
  85             # have to remove @search_text since SearchAction
  86             # uses it.
  87             self.form.list[:] = [fs for fs in self.form.list
  88                                  if fs.name != '@search_text']
  89             self.form.list.append(cgi.MiniFieldStorage("id", ids))
  90             return roundup.cgi.actions.SearchAction.handle(self)
  91 
  92         else:
  93             # call the existing SearchAction for display
  94             return roundup.cgi.actions.SearchAction.handle(self)
  95 
  96 
  97 def init(instance):
  98     # register the new jumpsearch action. 
  99     instance.registerAction('jumpsearch', JumpSearch)


CategoryActions