Ticket #70: agiletrac-70-iteration-tickets-trac.patch

File agiletrac-70-iteration-tickets-trac.patch, 11.7 KB (added by dfraser, 3 years ago)

Patch to trac to put iteration's tickets in separate table and allow querying through that table

  • trac/trac/ticket/api.py

    diff -ur -ubB trac/trac/ticket/api.py trac/trac/ticket/api.py
     
    694695    def get_tickets_for_iteration(self, db, iteration_tickets): 
    695696        from trac.ticket import Ticket 
    696697        tickets = [] 
    697         for ticket_id in iteration_tickets.split(): 
     698        for ticket_id in iteration_tickets: 
    698699            ticket = Ticket(self.env, ticket_id, db) 
    699700            if ticket.exists: 
    700701               tickets.append(ticket) 
     
    710711         
    711712        cursor = db.cursor() 
    712713        cursor.execute("SELECT ticket,time,author,field,oldvalue,newvalue " 
    713                        "FROM ticket_change WHERE time > %s AND time < %s " 
    714                        "ORDER BY ticket", (start_date, end_date)) 
     714                       "FROM ticket_change, iteration_ticket " 
     715                       "WHERE time > %s AND time < %s " 
     716                       "AND ticket_change.ticket = iteration_ticket.ticket_id " 
     717                       "AND iteration_ticket.iteration_id = %s " 
     718                       "ORDER BY ticket", (start_date, end_date, iteration.id)) 
    715719         
    716720        for ticket, time, author, field, oldvalue, newvalue in cursor: 
    717721            if not changed_tickets.has_key( ticket ): 
  • trac/trac/ticket/model.py

    diff -ur -ubB trac/trac/ticket/model.py trac/trac/ticket/model.py
     
    981981            self._old_id = iteration_id 
    982982        else: 
    983983            self.id = self._old_id = self.start_date = self.end_date = None 
    984             self.summary = self.tickets = '' 
     984            self.summary = '' 
     985            self.tickets = [] 
    985986 
    986987    def _get_resource(self): 
    987988        return Resource('iteration', self.id) ### .version !!! 
     
    991992        if not db: 
    992993            db = self.env.get_db_cnx() 
    993994        cursor = db.cursor() 
    994         cursor.execute("SELECT id,summary,start_date,end_date,tickets " 
     995        cursor.execute("SELECT id,summary,start_date,end_date " 
    995996                       "FROM iteration WHERE id=%s", (iteration_id,)) 
    996997        row = cursor.fetchone() 
    997998        if not row: 
    998999            raise ResourceNotFound('Iteration %s does not exist.' % iteration_id, 
    9991000                                   'Invalid Iteration Id') 
    1000         id, summary, start_date, end_date, tickets = row 
     1001        id, summary, start_date, end_date = row 
    10011002        self.id = id 
    10021003        self.summary = summary or '' 
    10031004        self.start_date = start_date and datetime.fromtimestamp(int(start_date), utc) or None 
    10041005        self.end_date = end_date and datetime.fromtimestamp(int(end_date), utc) or None 
    1005         self.tickets = tickets or '' 
     1006        cursor.execute("SELECT ticket_id FROM iteration_ticket " 
     1007                       "WHERE iteration_id=%s ORDER BY ticket_id", (iteration_id,)) 
     1008        self.tickets = [str(row[0]) for row in cursor.fetchall()] 
    10061009 
    10071010    is_started = property(fget=lambda self: self.start_date and \ 
    10081011                                            self.start_date.date() < date.today()) 
     
    10241027        cursor = db.cursor() 
    10251028        self.env.log.info('Deleting iteration %s' % self.id) 
    10261029        cursor.execute("DELETE FROM iteration WHERE id=%s", (self.id,)) 
     1030        cursor.execute("DELETE FROM iteration_ticket WHERE iteration_id=%s" % (self.id,)) 
    10271031 
    10281032        if handle_ta: 
    10291033            db.commit() 
     
    10371041            handle_ta = False 
    10381042 
    10391043        cursor = db.cursor() 
    1040         cursor.execute("INSERT INTO iteration (summary,start_date,end_date,tickets) " 
    1041                        "VALUES (%s,%s,%s,%s)", 
    1042                        (self.summary, to_timestamp(self.start_date), to_timestamp(self.end_date), 
    1043                         self.tickets)) 
     1044        cursor.execute("INSERT INTO iteration (summary,start_date,end_date) " 
     1045                       "VALUES (%s,%s,%s)", 
     1046                       (self.summary, to_timestamp(self.start_date), to_timestamp(self.end_date))) 
    10441047        iteration_id = db.get_last_id(cursor, 'iteration') 
     1048        for ticket_id in self.tickets: 
     1049            cursor.execute("INSERT INTO iteration_ticket (iteration_id, ticket_id) " 
     1050                           "VALUES (%s,%s)", 
     1051                           (iteration_id, ticket_id)) 
    10451052 
    10461053        if handle_ta: 
    10471054            db.commit() 
     
    10601067        cursor = db.cursor() 
    10611068        self.env.log.info('Updating iteration "%s"' % self.id) 
    10621069        cursor.execute("UPDATE iteration SET summary=%s,start_date=%s," 
    1063                        "end_date=%s,tickets=%s WHERE id=%s", 
     1070                       "end_date=%s WHERE id=%s", 
    10641071                       (self.summary, to_timestamp(self.start_date), to_timestamp(self.end_date), 
    1065                         self.tickets, 
    10661072                        self.id)) 
    10671073 
     1074        cursor.execute("DELETE FROM iteration_ticket WHERE iteration_id=%s" % (self.id,)) 
     1075        for ticket_id in self.tickets: 
     1076            cursor.execute("INSERT INTO iteration_ticket (iteration_id, ticket_id) " 
     1077                           "VALUES (%s,%s)", 
     1078                           (self.id, ticket_id)) 
     1079 
    10681080        if handle_ta: 
    10691081            db.commit() 
    10701082 
     
    10721084        if not db: 
    10731085            db = env.get_db_cnx() 
    10741086        now = to_timestamp( datetime.now(utc) - timedelta(days=1) ) 
    1075         sql = "SELECT id,summary,start_date,end_date,tickets FROM iteration " 
     1087        sql = "SELECT id,summary,start_date,end_date FROM iteration " 
    10761088        if not include_finished: 
    10771089            sql += "WHERE end_date >= %s " % now 
    10781090        cursor = db.cursor() 
    10791091        cursor.execute(sql) 
     1092        iteration_rows = cursor.fetchall() 
    10801093        iterations = [] 
    1081         for iteration_id,summary,start_date,end_date,tickets in cursor: 
     1094        for iteration_id,summary,start_date,end_date in iteration_rows: 
    10821095            iteration = Iteration(env) 
    10831096            iteration.id = iteration._old_id = iteration_id 
    10841097            iteration.summary = summary or '' 
    10851098            iteration.start_date = start_date and datetime.fromtimestamp(int(start_date), utc) or None 
    10861099            iteration.end_date = end_date and datetime.fromtimestamp(int(end_date), utc) or None 
    1087             iteration.tickets = tickets or '' 
     1100            cursor.execute("SELECT ticket_id FROM iteration_ticket " 
     1101                                  "WHERE iteration_id = %d ORDER BY ticket_id" % (iteration_id,)) 
     1102            iteration.tickets = [str(row[0]) for row in cursor.fetchall()] 
    10881103            iterations.append(iteration) 
    10891104        def iteration_order(m): 
    10901105            return (m.start_date or utcmax, 
  • trac/trac/ticket/roadmap.py

     
    525525                    ticket_tokens.append(id_token) 
    526526            except ValueError: 
    527527                invalid_ticket_tokens.append(id_token) 
    528         iteration_tickets = " ".join(ticket_tokens) 
    529         iteration.tickets = iteration_tickets 
     528        iteration.tickets = ticket_tokens 
    530529        # Instead of raising one single error, check all the constraints and 
    531530        # let the user fix them by going back to edit mode showing the warnings 
    532531        warnings = [] 
  • trac/trac/ticket/templates/iteration_edit.html

    diff -ur -ubB trac/trac/ticket/templates/iteration_edit.html trac/trac/ticket/templates/iteration_edit.html
     
    212212          <div class="field"> 
    213213            <label>List the tickets, by id, associated with this iteration, separated by a space:<br /> 
    214214            <input type="text" id="iteration_tickets" name="iteration_tickets" size="60" 
    215                    value="${iteration.tickets}" /> 
     215                   value="${' '.join(iteration.tickets)}" /> 
    216216            <em>For example: 56 3 4 12</em> 
    217217            </label> 
    218218          </div> 
  • trac/trac/ticket/query.py

    diff -ur -ubB trac/trac/ticket/query.py trac/trac/ticket/query.py
     
    404404 
    405405        sql = [] 
    406406        sql.append("SELECT " + ",".join(['t.%s AS %s' % (c, c) for c in cols 
    407                                          if c not in custom_fields])) 
     407                                         if c not in custom_fields and c != 'iteration'])) 
    408408        sql.append(",priority.value AS priority_value") 
    409409        for k in [k for k in cols if k in custom_fields]: 
    410410            sql.append(",%s.value AS %s" % (k, k)) 
     
    429429                       % (col, col, col)) 
    430430 
    431431        def get_constraint_sql(name, value, mode, neg): 
     432            if name == "iteration": 
     433                name = 'iteration_ticket.iteration_id' 
     434                if mode != '': 
     435                    raise ValueError("Iteration searching only supports is or is not") 
     436                clause = "(SELECT DISTINCT ticket_id FROM iteration_ticket WHERE %s = %%s)" % (name,) 
     437                if neg: 
     438                    clause = "t.id NOT IN %s" % (clause,) 
     439                else: 
     440                    clause = "t.id IN %s" % (clause,) 
     441                value = int(value) 
     442                return (clause, value) 
    432443            if name not in custom_fields: 
    433444                name = 't.' + name 
    434445            else: 
     
    486497                                               ' OR '.join(id_clauses))) 
    487498            # Special case for exact matches on multiple values 
    488499            elif not mode and len(v) > 1: 
    489                 if k not in custom_fields: 
     500                if k == 'iteration': 
     501                    col = 'iteration_ticket.iteration_id' 
     502                elif k not in custom_fields: 
    490503                    col = 't.' + k 
    491504                else: 
    492505                    col = k + '.value' 
    493                 clauses.append("COALESCE(%s,'') %sIN (%s)" 
    494                                % (col, neg and 'NOT ' or '', 
    495                                   ','.join(['%s' for val in v]))) 
    496                 args += [val[neg:] for val in v] 
     506                if k == 'iteration': 
     507                    clause = "%s IN (%s)" % (col, ','.join(['%s' for val in v])) 
     508                    clause = "(SELECT DISTINCT ticket_id FROM iteration_ticket WHERE %s)" % (clause,) 
     509                    clause = "t.id %sIN %s" % (neg and 'NOT ' or '', clause) 
     510                    clauses.append(clause) 
     511                    args += [int(val[neg:]) for val in v] 
     512                else: 
     513                    clauses.append("COALESCE(%s,'') %sIN (%s)" 
     514                                  % (col, neg and 'NOT ' or '', 
     515                                     ','.join(['%s' for val in v]))) 
     516                    args += [val[neg:] for val in v] 
    497517            elif len(v) > 1: 
    498518                constraint_sql = filter(None, 
    499519                                        [get_constraint_sql(k, val, mode, neg) 
     
    585605        # TODO: remove after adding time/changetime to the api.py 
    586606        labels['changetime'] = _('Modified') 
    587607        labels['time'] = _('Created') 
     608        labels['iteration'] = _('Iteration') 
    588609 
    589610        headers = [{ 
    590611            'name': col, 'label': labels.get(col, _('Ticket')), 
     
    602623            field_data.update(field) 
    603624            del field_data['name'] 
    604625            fields[field['name']] = field_data 
    605  
     626        fields['iteration'] = {'label': _('Iteration'), 'type': 'text', 'name': 'iteration'} 
    606627        modes = {} 
    607628        modes['text'] = [ 
    608629            {'name': _("contains"), 'value': "~"}, 
     
    840861        ticket_fields = [f['name'] for f in 
    841862                         TicketSystem(self.env).get_ticket_fields()] 
    842863        ticket_fields.append('id') 
     864        ticket_fields.append('iteration') 
    843865 
    844866        # For clients without JavaScript, we remove constraints here if 
    845867        # requested