def _get_name_action(self, req): """Return the name and action parameters from the req""" name = req.args.get('name') action = req.args.get('action') log.debug(self, "Processing request: action:%s name:%s" % \ (action, name)) return name, action
def create_types(env, type_names, db=None): db, handle_ta = get_db_for_write(env, db) # Query definitions sql_select_type = "SELECT count(*) FROM enum WHERE type = 'ticket_type' AND name = '%s'" sql_select_max_value = "SELECT max(value) FROM enum WHERE type = 'ticket_type'" sql_insert_type = "INSERT INTO enum (type,name,value) VALUES('ticket_type','%s',%d)" # For every default type check, if already in db, otherwise insert it. # To consistently insert, we need to know the value of the highest ticket_type entry first. cursor = db.cursor() cursor.execute(sql_select_max_value) ticket_type_max_value = int(cursor.fetchone()[0]) for t in type_names: try: cursor.execute(sql_select_type % t) if cursor.fetchone()[0] == 0: ticket_type_max_value += 1 debug(env, "ticket_type '%s' not found within table 'enum' - will " \ "insert it with value %d..." % (t,ticket_type_max_value)) # If the type is not present already insert with the next value available cursor.execute(sql_insert_type % (t,ticket_type_max_value)) if handle_ta: db.commit() else: debug(env, "Found ticket_type '%s' within table 'enum'" % (t)) except Exception, e: if handle_ta: db.rollback() exception_to_unicode(e)
def create_table(env, table, conn=None): """ Creates a the given table in the given environment. The Table has to be of type trac.db.Table, and the Environment a trac.env.Environment. """ assert isinstance(env, Environment), \ "[DB]: env should be an instance of trac.env.Environment, got %s" % type(env) assert isinstance(table, Table), \ "[DB]: table should be an instance of trac.sb.Table, got %s" % type(table) # Get The Databse Manager dbm = DatabaseManager(env) # Get the Connector Object for the current DB schema connector, args = dbm._get_connector() # Ask the connector to generate the proper DDL for the table ddl_gen = connector.to_sql(table) # Get a DB Connection from the pool, create a cursor and the table conn, handle_ta = get_db_for_write(env, conn) try: cursor = conn.cursor() for statement in ddl_gen: debug(env, "[DB]: Table: %s\n%s" % (table.name, statement)) cursor.execute(statement) if handle_ta: conn.commit() debug(env, "[DB]: Successfully Created Table %s" % table.name) except Exception, e: if handle_ta: conn.rollback() error(env, "[DB]: Unable to Create Table %s, an error occurred: %s" % \ (table.name, exception_to_unicode(e))) raise
def expand_macro(self, formatter, name, content): req = formatter.req # Analyze the arguments list, that should be in content args = content.split(',') if len(args) == 0: raise TracError("No argument provided for %s." % self.__class__.__name__) # The first must be the type of the chart chart_type = self._get_chart_type(args[0]) # The second the Sprint sprint_name = self._get_sprint(args[1]) width, height = self._get_dimensions(args[2:]) filter_by = None if len(args) >= 4: filter_by = args[3] chart_params = dict(sprint_name=sprint_name, width=width, height=height, filter_by=filter_by) debug(self.env, "Params: %s" % chart_params) chart = ChartGenerator(self.env).get_chartwidget( chart_type, **chart_params) chart.prepare_rendering(req) return html.DIV(chart.display())
def _fetch_tickets(self, db=None): """ Fetch from the DB the ids, type, status of the tickets planned for this sprint, and returns a list of id, type, status. """ params = {"table": BACKLOG_TICKET_TABLE, "scope": self.name} t_sql = ( "SELECT DISTINCT id, type, status FROM ticket INNER JOIN ticket_custom ON " "ticket.id=ticket_custom.ticket LEFT OUTER JOIN %(table)s ON " "ticket.id=%(table)s.ticket_id WHERE (%(table)s.name='Sprint Backlog' " "AND scope='%(scope)s') or (ticket_custom.name='sprint' AND " "ticket_custom.value='%(scope)s')" % params ) tickets = [(0, "", "")] db, handle_ta = get_db_for_write(self.env, db) try: cursor = db.cursor() debug(self, "SQL (_fetch_tickets_stats): %s" % t_sql) cursor.execute(t_sql) tickets = cursor.fetchall() except: if handle_ta: db.rollback() return tickets
def parse_calculated_field(configstring, component=None): result = None if configstring != None and configstring.strip() != "": match = CALCULATE.match(configstring) if match: field_name = match.group('name') operator_name = match.group('operator') values = match.group('values') conditions = match.group('conditions') if operator_name in operators: operator = operators[operator_name] try: result = (field_name, operator(values, condition_string=conditions)) if component: base_msg = u"Setting calculated property: %s => %s:%s|%s" msg = base_msg % (field_name, operator_name, values, conditions) debug(component, msg) except AgiloConfigSyntaxError, e: if component: msg = u"Error while parsing calculated property '%s': %s" error(component, msg % (field_name, unicode(e))) else: if component: error(component, u"Unkown operator name '%s'" % field_name)
def _rename(self, name, new_name, db=None): """Renames this sprint and reassigns all tickets to the new name.""" # avoid circular imports from agilo.scrum.metrics.model import TeamMetrics params = { 'name' : name, 'new_name' : new_name, } team_metrics_entry_table = TeamMetrics.TeamMetricsEntry._table.name queries = [ "UPDATE ticket_custom SET value=%(new_name)s WHERE name='sprint' AND value=%(name)s", "UPDATE " + team_metrics_entry_table + " SET sprint=%(new_name)s WHERE sprint=%(name)s", ] db, handle_ta = get_db_for_write(self.env, db) try: cursor = db.cursor() for q in queries: debug(self, "RENAME => Executing query: %s (%s)" % (q, params)) safe_execute(cursor, q, params) if handle_ta: db.commit() except Exception, e: if handle_ta: db.rollback() raise TracError("An error occurred while renaming sprint: %s" % to_unicode(e))
def _save(self, timestamp, value, update=False, db=None): """Saves a remaining time value to the database. The update parameter decides if the value should be updated (True) or inserted (False)""" params = { Key.TABLE : BURNDOWN_TABLE, Key.TASK_ID : self.task.id, Key.DATE : timestamp, Key.REMAINING_TIME : value, } if update: sql_query = "UPDATE %(table)s SET remaining_time=%(remaining_time)d " \ "WHERE task_id=%(task_id)d AND date=%(date)f" % params else: sql_query = "INSERT INTO %(table)s (task_id, date, remaining_time) " \ "VALUES (%(task_id)s, %(date)s, %(remaining_time)s)" % params db, handle_ta = get_db_for_write(self.env, db) try: cursor = db.cursor() cursor.execute(sql_query) if handle_ta: db.commit() debug(self, "DB Committed, saved remaining time (%s) for task %d" % \ (params[Key.REMAINING_TIME], self.task.id)) except Exception, e: error(self, to_unicode(e)) if handle_ta: db.rollback() raise TracError("Error while saving remaining time: %s" % \ to_unicode(e))
def _add_linking_source_to_template_data(self, req, data): if 'src' in req.args: debug(self, "Got SRC: %s" % req.args['src']) try: data['src'] = int(req.args['src']) except (ValueError, TypeError): raise TracError("src (%s) must be a valid ticket id!" % req.args['src'])
def create_types(env, type_names, db=None): db, handle_ta = get_db_for_write(env, db) # Query definitions sql_select_type = "SELECT count(*) FROM enum WHERE type = 'ticket_type' AND name = '%s'" sql_select_max_value = "SELECT max(value) FROM enum WHERE type = 'ticket_type'" sql_insert_type = "INSERT INTO enum (type,name,value) VALUES('ticket_type','%s',%d)" # For every default type check, if already in db, otherwise insert it. # To consistently insert, we need to know the value of the highest ticket_type entry first. cursor = db.cursor() cursor.execute(sql_select_max_value) ticket_type_max_value = int(cursor.fetchone()[0]) for t in type_names: try: cursor.execute(sql_select_type % t) if cursor.fetchone()[0] == 0: ticket_type_max_value += 1 debug(env, "ticket_type '%s' not found within table 'enum' - will " \ "insert it with value %d..." % (t,ticket_type_max_value)) # If the type is not present already insert with the next value available cursor.execute(sql_insert_type % (t, ticket_type_max_value)) if handle_ta: db.commit() else: debug(env, "Found ticket_type '%s' within table 'enum'" % (t)) except Exception, e: if handle_ta: db.rollback() exception_to_unicode(e)
def _set_duration(self, value): """Sets the duration of the Sprint and recalculates the end Date""" if value is not None: week_days_off = None if AgiloConfig(self.env).sprints_can_start_or_end_on_weekends: week_days_off = [] self.end = add_to_date(self.start, value, week_days_off=week_days_off) debug(self, "Setting Sprint end date to: %s" % self.end)
def validate_rules(self, ticket): """ Validates the give ticket against the registered rules. Every rule will be validated and has to take care of all the checks, return True or False """ debug(self, "Called validate_rules(%s)" % ticket) for r in self.rules: r.validate(ticket)
def validate(self, ticket): """Accept only tickets with remaining time field""" debug(self, "Called validate(%s)..." % ticket) if ticket is not None and isinstance(ticket, AgiloTicket) and \ ticket.is_writeable_field(Key.REMAINING_TIME): remaining_time = ticket[Key.REMAINING_TIME] or '' match = self.extract_numbers_regex.match(remaining_time) if match != None: time_as_number = match.group(1) ticket[Key.REMAINING_TIME] = time_as_number else: ticket[Key.REMAINING_TIME] = None
def validate(self, ticket): """Accept tickets which have story as parents and have remaninig time""" debug(self, "Called validate(%s)..." % ticket) if ticket is not None and isinstance(ticket, AgiloTicket) and \ ticket.is_writeable_field(Key.REMAINING_TIME) and \ ticket[Key.STATUS] == Status.ACCEPTED and \ len(ticket.get_incoming()) > 0: # The ticket is a task or similar and has parents for p in ticket.get_incoming(): if p.get_type() == Type.USER_STORY and \ p[Key.STATUS] != Status.ACCEPTED: p[Key.STATUS] = Status.ACCEPTED p.save_changes('agilo', 'Updated status, related task in progress')
def validate(self, ticket): """Validate the ticket against the defined rules""" debug(self, "Called validate(%s)..." % ticket) if ticket is not None and isinstance(ticket, AgiloTicket) and \ Key.REMAINING_TIME in ticket.fields_for_type: sprint_name = ticket[Key.SPRINT] if sprint_name not in (None, ''): sprint = SprintModelManager(self.env).get(name=sprint_name) if sprint and sprint.team is not None: owner = ticket[Key.OWNER] self.check_team_membership(ticket, sprint, owner, is_owner=True) for r in ticket.get_resource_list(): self.check_team_membership(ticket, sprint, r)
def validate(self, ticket): """Accept only tickets with owner and resources fields""" debug(self, "Called validate(%s)..." % ticket) if ticket is not None and isinstance(ticket, AgiloTicket) and \ Key.OWNER in ticket.fields_for_type and \ Key.RESOURCES in ticket.fields_for_type: owner = ticket[Key.OWNER] resources = ticket.get_resource_list() if (owner is None or owner.strip() == '') and \ len(resources) > 0: ticket[Key.OWNER] = resources[0] ticket[Key.RESOURCES] = ', '.join([r.strip() for r in resources[1:] if r.strip() != '']) elif owner is not None and owner.strip() in resources: resources.remove(owner.strip()) ticket[Key.RESOURCES] = ', '.join([r.strip() for r in resources if r.strip() != ''])
def create_permissions(self, req): """ Returns a list of the permissions to create new ticket types for the given request object """ create_perms = list() # see what ticket types the user has create permissions for for t_type, alias in AgiloConfig(self.env).ALIASES.items(): permission_name = self.get_permission_name_to_create(t_type) debug(self, "Type: %s, Alias: %s Permission: %s" % \ (t_type, alias, permission_name)) if permission_name in req.perm: create_perms.append(t_type) debug(self, "%s has create permission for types: %s" % \ (req.authname, create_perms)) return create_perms
def _display_back_to_url(self, req): """ Check in the session for a back_to_url link to visualize in the Jump Back """ back_to_key = 'back_to_url' back_to_url = None if req.args.has_key(back_to_key): back_to_url = req.args.get(back_to_key) elif req.session.has_key(back_to_key): back_to_url = req.session.get(back_to_key) debug(self, "$$$ VALUE OF back_to_url: %s $$$" % back_to_url) if back_to_url is not None: # Add it directly to the context navigation bar add_ctxtnav(req, _('Jump Back'), back_to_url)
def _extract_params(self, req): # This will give priority to a rename in case of change name = req.args.get('name') if name is None: name = req.args.get('sprint_name') milestone = req.args.get('milestone') or req.args.get('url_milestone') action = req.args.get('edit') or req.args.get('add') log.debug(self, "Processing request: action:%s name:%s milestone:%s" % \ (action, name, milestone)) if action == 'edit': if not name: raise TracError(_("Please provide a Sprint Name")) elif 'save' in req.args and not milestone: raise TracError(_("Please provide a Milestone Name")) elif action == 'add' and not milestone: raise TracError(_("Please provide a Milestone Name")) return name, milestone, action
def validate(self, ticket): """Accept only tickets with owner and resources fields""" debug(self, "Called validate(%s)..." % ticket) if ticket is not None and isinstance(ticket, AgiloTicket) and \ Key.OWNER in ticket.fields_for_type and \ Key.RESOURCES in ticket.fields_for_type: owner = ticket[Key.OWNER] resources = ticket.get_resource_list() if (owner is None or owner.strip() == '') and \ len(resources) > 0: ticket[Key.OWNER] = resources[0] ticket[Key.RESOURCES] = ', '.join( [r.strip() for r in resources[1:] if r.strip() != '']) elif owner is not None and owner.strip() in resources: resources.remove(owner.strip()) ticket[Key.RESOURCES] = ', '.join( [r.strip() for r in resources if r.strip() != ''])
def ticket_changed(self, ticket, comment, author, old_values): """Called when a ticket is modified. `old_values` is a dictionary containing the previous values of the fields that have changed. """ debug(self, "Invoked for ticket #%s of type %s" % \ (ticket.id, ticket[Key.TYPE])) if Key.REMAINING_TIME in old_values: # remaining time has been changed previous_remaining_time = old_values.get(Key.REMAINING_TIME) ticket_remaining = float(ticket[Key.REMAINING_TIME] or '0') if previous_remaining_time != ticket_remaining: rt = RemainingTime(self.env, ticket) rt.set_remaining_time(ticket_remaining) debug(self, "Updated remaining_time for ticket #%s from: %s to %s" % \ (ticket, previous_remaining_time, ticket_remaining))
def validate(self, ticket): """Accepts only tickets with remaining_time field""" debug(self, "Called validate(%s)..." % ticket) if ticket is not None and isinstance(ticket, AgiloTicket) and \ ticket.is_writeable_field(Key.REMAINING_TIME): remaining_time = self.parse_remaining_time(ticket) if (not self.status_did_change(ticket)) and (not self.remaining_time_did_change(ticket)): return elif remaining_time is None: return ticket_was_closed = (self.old_status(ticket) == Status.CLOSED) ticket_is_now_closed = (ticket[Key.STATUS] == Status.CLOSED) if self.status_did_change(ticket) and ticket_is_now_closed and remaining_time != 0: ticket[Key.REMAINING_TIME] = '0' elif self.remaining_time_did_change(ticket) and (not ticket_was_closed) and remaining_time == 0: ticket[Key.STATUS] = Status.CLOSED ticket[Key.RESOLUTION] = Status.RES_FIXED
def _load(self, db=None): """Try to load the Sprint from the database""" db, handle_ta = get_db_for_write(self.env, db) sql_query = "SELECT date, remaining_time FROM %s" \ " WHERE task_id=%d ORDER BY date DESC" % (BURNDOWN_TABLE, self.task.id) debug(self, "Burndown-SQL Query: %s" % sql_query) try: history = dict() cursor = db.cursor() cursor.execute(sql_query) for row in cursor.fetchall(): timestamp, remaining_time = row history[timestamp] = remaining_time self.loaded = True except Exception, e: error(self, to_unicode(e)) if handle_ta: db.rollback() raise TracError("An error occurred while loading Burndown data: %s" % to_unicode(e))
def expand_macro(self, formatter, name, content): req = formatter.req # Analyze the arguments list, that should be in content args = content.split(',') if len(args) == 0: raise TracError("No argument provided for %s." % self.__class__.__name__) # The first must be the type of the chart chart_type = self._get_chart_type(args[0]) # The second the Sprint sprint_name = self._get_sprint(args[1]) width, height = self._get_dimensions(args[2:]) filter_by = None if len(args) >= 4: filter_by = args[3] chart_params = dict(sprint_name=sprint_name, width=width, height=height, filter_by=filter_by) debug(self.env, "Params: %s" % chart_params) chart = ChartGenerator(self.env).get_chartwidget(chart_type, **chart_params) chart.prepare_rendering(req) return html.DIV(chart.display())
def validate(self, ticket): """Accepts only tickets with remaining_time field""" debug(self, "Called validate(%s)..." % ticket) if ticket is not None and isinstance(ticket, AgiloTicket) and \ ticket.is_writeable_field(Key.REMAINING_TIME): remaining_time = self.parse_remaining_time(ticket) if (not self.status_did_change(ticket)) and ( not self.remaining_time_did_change(ticket)): return elif remaining_time is None: return ticket_was_closed = (self.old_status(ticket) == Status.CLOSED) ticket_is_now_closed = (ticket[Key.STATUS] == Status.CLOSED) if self.status_did_change( ticket) and ticket_is_now_closed and remaining_time != 0: ticket[Key.REMAINING_TIME] = '0' elif self.remaining_time_did_change(ticket) and ( not ticket_was_closed) and remaining_time == 0: ticket[Key.STATUS] = Status.CLOSED ticket[Key.RESOLUTION] = Status.RES_FIXED
def _fetch_tickets(self, db=None): """ Fetch from the DB the ids, type, status of the tickets planned for this sprint, and returns a list of id, type, status. """ params = {'table': BACKLOG_TICKET_TABLE, 'scope': self.name} t_sql = "SELECT DISTINCT id, type, status FROM ticket INNER JOIN ticket_custom ON "\ "ticket.id=ticket_custom.ticket LEFT OUTER JOIN %(table)s ON " \ "ticket.id=%(table)s.ticket_id WHERE (%(table)s.name='Sprint Backlog' " \ "AND scope='%(scope)s') or (ticket_custom.name='sprint' AND " \ "ticket_custom.value='%(scope)s')" % params tickets = [(0, '', '')] db, handle_ta = get_db_for_write(self.env, db) try: cursor = db.cursor() debug(self, "SQL (_fetch_tickets_stats): %s" % t_sql) cursor.execute(t_sql) tickets = cursor.fetchall() except: if handle_ta: db.rollback() return tickets
def _rename(self, name, new_name, db=None): """Renames this sprint and reassigns all tickets to the new name.""" # avoid circular imports from agilo.scrum.metrics.model import TeamMetrics params = {"name": name, "new_name": new_name} team_metrics_entry_table = TeamMetrics.TeamMetricsEntry._table.name queries = [ "UPDATE ticket_custom SET value=%(new_name)s WHERE name='sprint' AND value=%(name)s", "UPDATE " + team_metrics_entry_table + " SET sprint=%(new_name)s WHERE sprint=%(name)s", ] db, handle_ta = get_db_for_write(self.env, db) try: cursor = db.cursor() for q in queries: debug(self, "RENAME => Executing query: %s (%s)" % (q, params)) safe_execute(cursor, q, params) if handle_ta: db.commit() except Exception, e: if handle_ta: db.rollback() raise TracError("An error occurred while renaming sprint: %s" % to_unicode(e))
def get_tickets_matching(self, t_id, summary): """ Returns a list of dictionaries (id: value, summary: value) matching the summary request and excluding the requesting ticket having id = id. """ try: t_id = int(t_id) # Make sure it is an int :-) keyword = re.compile(summary, re.IGNORECASE) db = self.env.get_db_cnx() from agilo.ticket.model import AgiloTicketModelManager sql = """SELECT id, type, summary FROM ticket WHERE id != $id $allowed AND id NOT IN (SELECT dest FROM %s WHERE src = $id UNION SELECT src FROM %s WHERE dest = $id) ORDER BY summary""" \ % (LINKS_TABLE, LINKS_TABLE) sql_query = string.Template(sql) sql_allowed = "AND ticket.type IN ('%s')" t_type = AgiloTicketModelManager( self.env).get(tkt_id=t_id).get_type() linkconfig = LinksConfiguration(self.env) if linkconfig.is_allowed_source_type(t_type): allowed_types = linkconfig.get_allowed_destination_types( t_type) allowed = sql_allowed % '\', \''.join(allowed_types) else: debug(self, "No Key found for #%s#" % repr(t_type)) allowed = '' sql_query = sql_query.substitute({'id': t_id, 'allowed': allowed}) debug(self, "SQL: %s" % sql_query) cursor = db.cursor() cursor.execute(sql_query) results = [] for row in cursor: if keyword.search(row[2] or ''): results.append({ 'id': row[0], 'type': row[1], 'summary': row[2] }) debug(self, "Search Results: %s" % str(results)) return results except Exception, e: warning(self, e) msg = "[%s]: ERROR: Search module unable to complete query!" % \ self.__class__.__name__ raise TracError(msg)
def get_tickets_matching(self, t_id, summary): """ Returns a list of dictionaries (id: value, summary: value) matching the summary request and excluding the requesting ticket having id = id. """ try: t_id = int(t_id) # Make sure it is an int :-) keyword = re.compile(summary, re.IGNORECASE) db = self.env.get_db_cnx() from agilo.ticket.model import AgiloTicketModelManager sql = """SELECT id, type, summary FROM ticket WHERE id != $id $allowed AND id NOT IN (SELECT dest FROM %s WHERE src = $id UNION SELECT src FROM %s WHERE dest = $id) ORDER BY summary""" \ % (LINKS_TABLE, LINKS_TABLE) sql_query = string.Template(sql) sql_allowed = "AND ticket.type IN ('%s')" t_type = AgiloTicketModelManager(self.env).get(tkt_id=t_id).get_type() linkconfig = LinksConfiguration(self.env) if linkconfig.is_allowed_source_type(t_type): allowed_types = linkconfig.get_allowed_destination_types(t_type) allowed = sql_allowed % '\', \''.join(allowed_types) else: debug(self, "No Key found for #%s#" % repr(t_type)) allowed = '' sql_query = sql_query.substitute({'id' : t_id, 'allowed' : allowed}) debug(self, "SQL: %s" % sql_query) cursor = db.cursor() cursor.execute(sql_query) results = [] for row in cursor: if keyword.search(row[2] or ''): results.append({'id': row[0], 'type': row[1], 'summary': row[2]}) debug(self, "Search Results: %s" % str(results)) return results except Exception, e: warning(self, e) msg = "[%s]: ERROR: Search module unable to complete query!" % \ self.__class__.__name__ raise TracError(msg)
def _get_duration(self): """Gets the duration of the Sprint""" duration = count_working_days(self.start, self.end) debug(self, "Returning duration %d start: %s, end: %s" % (duration, self.start, self.end)) return duration
def _get_duration(self): """Gets the duration of the Sprint""" duration = count_working_days(self.start, self.end) debug(self, "Returning duration %d start: %s, end: %s" % \ (duration, self.start, self.end)) return duration