예제 #1
0
    def filter_stream(self, req, method, filename, stream, data):
        chrome = Chrome(self.env)

        if req.path_info.startswith('/milestone') \
            and req.args.get('action') in ['edit', 'new'] \
            and 'max_level' not in data:
            milestone = data.get('milestone')
            levels = IttecoMilestoneAdminPanel(self.env).milestone_levels
            mydata ={'structured_milestones':StructuredMilestone.select(self.env),
                  'max_level':  levels and len(levels)-1 or 0,
                  'milestone_name' : milestone and milestone.parent or None,
                  'field_name' : 'parent'}
            stream |=Transformer('//*[@id="edit"]/fieldset').append(
                chrome.render_template(req, 'itteco_milestones_dd.html', mydata, fragment=True))
            
        if 'ticket' in data:
            tkt = data['ticket']
            mydata ={
                'structured_milestones':StructuredMilestone.select(self.env),
                'milestone_name': data['ticket']['milestone'],
                'field_name' : 'field_milestone',
                'hide_completed' : not ( tkt.exists and 'TICKET_ADMIN' in req.perm(tkt.resource))
            }
            req.chrome.setdefault('ctxtnav',[]).insert(
                -1, 
                tag.a(
                    _('Go To Whiteboard'), 
                    href=req.href.whiteboard('team_tasks', data['ticket']['milestone'] or 'none')
                )
            )
            stream |=Transformer('//*[@id="field-milestone"]').replace(
                chrome.render_template(req, 'itteco_milestones_dd.html', mydata, fragment=True))
        return stream
예제 #2
0
    def create(self, req, attributes):
        """ Create a structure milestone object."""
        name = attributes.get('summary')
        try:
            if StructuredMilestone(self.env, name).exists:
                raise TracError('Milestone with name %s already exists' % name)
        except ResourceNotFound:
            pass
            
        milestone = StructuredMilestone(self.env)
        milestone.name = name
        req.perm.require('MILESTONE_CREATE', Resource(milestone.resource.realm))
        
        milestone.description = attributes.get('description')
        
        milestone.ticket.populate(attributes)
        def set_date(name, attr_name = None):
            val = attributes.get(name)
            if val is None:
                return
            val = val and parse_date(val, tzinfo=req.tz) or None
            if attr_name is not None:
                setattr(milestone, attr_name, val)
            milestone.ticket[name] = val and str(to_timestamp(val))
            
        set_date('duedate', 'due')
        set_date('completedate', 'completed')
        set_date('started')

        milestone.ticket.values['reporter'] = attributes.get('author') or get_reporter_id(req)
        milestone.insert()
        return self._milestone_as_dict(milestone)
예제 #3
0
 def _resolve_milestone(self, name, include_kids, show_completed):
     def _flatten_and_get_names(mil, include_kids, show_completed):
         names= []
         if mil:
             mil = isinstance(mil, StructuredMilestone) and [mil,] or mil
             for m in mil:
                 if show_completed or not m.completed:
                     names.append(m.name)
                     if include_kids:
                         names.extend(_flatten_and_get_names(m.kids, include_kids, show_completed))
         return names
     if name=='nearest':
         db = self.env.get_db_cnx()
         cursor = db.cursor()
         cursor.execute(
             'SELECT name FROM milestone WHERE due>%s ORDER BY due LIMIT 1', \
             (to_timestamp(datetime.now(utc)),))
         row = cursor.fetchone()
         name=row and row[0] or 'none'
     elif name=='not_completed_milestones':
         return _flatten_and_get_names(StructuredMilestone.select(self.env, False), \
             include_kids, show_completed)
     if name=='none':
         return ''
     try:    
         mil = StructuredMilestone(self.env, name)            
         names = _flatten_and_get_names(mil, include_kids, show_completed)
         if not names:
             names = mil.name
         return names
     except ResourceNotFound:
         return ''
예제 #4
0
 def update(self, req, name, comment, attributes=None):
     """ Updates a structure milestone object."""
     milestone = StructuredMilestone(self.env, name)
     if not milestone.exists:
         raise TracError('Milestone with name %s does not exist' % name)
     req.perm.require('MILESTONE_MODIFY', Resource(milestone.resource.realm))
     if attributes is not None:
         milestone.name = attributes.get('summary')
         milestone.description = attributes.get('description')
         milestone.ticket.populate(attributes)
         def set_date(name, attr_name = None):
             val = attributes.get(name)
             if val is None:
                 return
             val = val and parse_date(val, tzinfo=req.tz) or None
             if attr_name is not None:
                 setattr(milestone, attr_name, val)
             milestone.ticket[name] = val and str(to_timestamp(val))
             
         set_date('duedate', 'due')
         set_date('completedate', 'completed')
         set_date('started')
         
     milestone.save_changes(get_reporter_id(req, 'author'), comment)
     return self._milestone_as_dict(milestone)
예제 #5
0
 def delete(self, req, name):
     """ Deletes structure milestone object."""
     milestone = StructuredMilestone(self.env, name)
     if not milestone.exists:
         raise TracError('Milestone with name %s does not exist' % name)
     req.perm.require('MILESTONE_DELETE', Resource(milestone.resource.realm))
     milestone.delete()
     return {'name': milestone.name, 'description': milestone.description}
예제 #6
0
 def delete(self, req, name):
     """ Deletes structure milestone object."""
     milestone = StructuredMilestone(self.env, name)
     if not milestone.exists:
         raise TracError('Milestone with name %s does not exist' % name)
     req.perm.require('MILESTONE_DELETE',
                      Resource(milestone.resource.realm))
     milestone.delete()
     return {'name': milestone.name, 'description': milestone.description}
예제 #7
0
    def _render_editor(self, req, db, milestone):
        data = {
            'milestone': milestone,
            'ticket': milestone.ticket,
            'datefields': self.date_fields,
            'date_hint': get_date_format_hint(),
            'datetime_hint': get_datetime_format_hint(),
            'milestone_groups': [],
            'jump_to': req.args.get('jump_to') or referer_module(req)
        }

        if milestone.exists:
            req.perm(milestone.resource).require('MILESTONE_VIEW')
            milestones = [
                m for m in StructuredMilestone.select(self.env, db=db)
                if m.name != milestone.name
                and 'MILESTONE_VIEW' in req.perm(m.resource)
            ]
            data['milestone_groups'] = group_milestones(
                milestones, 'TICKET_ADMIN' in req.perm)
        else:
            req.perm(milestone.resource).require('MILESTONE_CREATE')

        TicketModule(self.env)._insert_ticket_data(
            req, milestone.ticket, data, get_reporter_id(req, 'author'), {})
        self._add_tickets_report_data(milestone, req, data)
        context = Context.from_request(req, milestone.resource)

        data['attachments'] = AttachmentModule(
            self.env).attachment_data(context)

        return 'itteco_milestone_edit.html', data, None
예제 #8
0
 def get(self, req, name):
     """ Get a structure milestone object."""
     milestone = StructuredMilestone(self.env, name)
     req.perm.require('MILESTONE_VIEW', Resource(milestone.resource.realm))
     if not milestone.exists:
         raise TracError('Milestone with name %s does not exist' % name)
     return self._milestone_as_dict(milestone)
예제 #9
0
    def _render_editor(self, req, db, milestone):
        data = {
            'milestone': milestone,
            'ticket': milestone.ticket,
            'datefields' : self.date_fields,
            'date_hint': get_date_format_hint(),
            'datetime_hint': get_datetime_format_hint(),
            'milestone_groups': [],
            'jump_to' : req.args.get('jump_to') or referer_module(req)
        }

        if milestone.exists:
            req.perm(milestone.resource).require('MILESTONE_VIEW')
            milestones = [m for m in StructuredMilestone.select(self.env, db=db)
                          if m.name != milestone.name
                          and 'MILESTONE_VIEW' in req.perm(m.resource)]
            data['milestone_groups'] = group_milestones(milestones,
                'TICKET_ADMIN' in req.perm)
        else:
            req.perm(milestone.resource).require('MILESTONE_CREATE')

        TicketModule(self.env)._insert_ticket_data(req, milestone.ticket, data, 
                                         get_reporter_id(req, 'author'), {})
        self._add_tickets_report_data(milestone, req, data)
        context = Context.from_request(req, milestone.resource)
        
        data['attachments']=AttachmentModule(self.env).attachment_data(context)

        return 'itteco_milestone_edit.html', data, None
예제 #10
0
    def create(self, req, attributes):
        """ Create a structure milestone object."""
        name = attributes.get('summary')
        try:
            if StructuredMilestone(self.env, name).exists:
                raise TracError('Milestone with name %s already exists' % name)
        except ResourceNotFound:
            pass

        milestone = StructuredMilestone(self.env)
        milestone.name = name
        req.perm.require('MILESTONE_CREATE',
                         Resource(milestone.resource.realm))

        milestone.description = attributes.get('description')

        milestone.ticket.populate(attributes)

        def set_date(name, attr_name=None):
            val = attributes.get(name)
            if val is None:
                return
            val = val and parse_date(val, tzinfo=req.tz) or None
            if attr_name is not None:
                setattr(milestone, attr_name, val)
            milestone.ticket[name] = val and str(to_timestamp(val))

        set_date('duedate', 'due')
        set_date('completedate', 'completed')
        set_date('started')

        milestone.ticket.values['reporter'] = attributes.get(
            'author') or get_reporter_id(req)
        milestone.insert()
        return self._milestone_as_dict(milestone)
예제 #11
0
    def filter_stream(self, req, method, filename, stream, data):
        chrome = Chrome(self.env)

        if req.path_info.startswith('/milestone') \
            and req.args.get('action') in ['edit', 'new'] \
            and 'max_level' not in data:
            milestone = data.get('milestone')
            levels = IttecoMilestoneAdminPanel(self.env).milestone_levels
            mydata = {
                'structured_milestones': StructuredMilestone.select(self.env),
                'max_level': levels and len(levels) - 1 or 0,
                'milestone_name': milestone and milestone.parent or None,
                'field_name': 'parent'
            }
            stream |= Transformer('//*[@id="edit"]/fieldset').append(
                chrome.render_template(req,
                                       'itteco_milestones_dd.html',
                                       mydata,
                                       fragment=True))

        if 'ticket' in data:
            tkt = data['ticket']
            mydata = {
                'structured_milestones':
                StructuredMilestone.select(self.env),
                'milestone_name':
                data['ticket']['milestone'],
                'field_name':
                'field_milestone',
                'hide_completed':
                not (tkt.exists and 'TICKET_ADMIN' in req.perm(tkt.resource))
            }
            req.chrome.setdefault('ctxtnav', []).insert(
                -1,
                tag.a(_('Go To Whiteboard'),
                      href=req.href.whiteboard(
                          'team_tasks', data['ticket']['milestone']
                          or 'none')))
            stream |= Transformer('//*[@id="field-milestone"]').replace(
                chrome.render_template(req,
                                       'itteco_milestones_dd.html',
                                       mydata,
                                       fragment=True))
        return stream
예제 #12
0
    def process_request(self, req):
        req.perm('ticket').require('TICKET_VIEW')
        
        board_type = req.args.get('board_type', 'team_tasks')
        milestone = req.args.get('milestone')
        if board_type == 'chart_settings':
            return self._chart_settings(milestone)
        else:
            board_type = _get_req_param(req, 'board_type', 'team_tasks')
            
            if board_type != req.args.get('board_type'):
                #boardtype was not implicitly  selected, let's restore previos state
                req.redirect(req.href.whiteboard(board_type, milestone))

            add_stylesheet(req, 'common/css/roadmap.css')
            add_stylesheet(req, 'itteco/css/common.css')            
            add_jscript(
                req, 
                [
                    'stuff/ui/ui.core.js',
                    'stuff/ui/ui.draggable.js',
                    'stuff/ui/ui.droppable.js',
                    'stuff/ui/ui.resizable.js',
                    'stuff/ui/plugins/jquery.colorbox.js',
                    'stuff/plugins/jquery.rpc.js',
                    'custom_select.js',
                    'whiteboard2.js'
                ],
                IttecoEvnSetup(self.env).debug
            )
            show_closed_milestones = req.args.get('show_closed_milestones', False)
            
            scope_item, work_item = self._get_wbitems_config(board_type)
            structured_milestones = StructuredMilestone.select(self.env, show_closed_milestones)
            if board_type == 'burndown':
                structured_milestones, _ignore = self._get_milestones_by_level(structured_milestones, 'Sprint', True)
            data ={
                'structured_milestones' : structured_milestones,
                'current_board_type' : board_type,
                'milestone' : milestone,
                'milestone_levels': IttecoEvnSetup(self.env).milestone_levels,
                'stats_config': self._get_stats_config(),
                'show_closed_milestones': show_closed_milestones,
                'wbconfig' : {
                    'rpcurl' : req.href.login("xmlrpc"),
                    'baseurl' : req.href(),
                    'workitem' : work_item,
                    'scopeitem': scope_item,
                    'groups': self.ticket_groups,
                    'transitions': self.transitions
                },
                'team' : self.team_members_provider and self.team_members_provider.get_team_members() or [],
                'ticket_types' : work_item['types'] or []
            }
                
            return 'itteco_whiteboard2.html', data, 'text/html'
예제 #13
0
    def _get_job_done(self, mil_names, tkt_type=None, db=None):
        started_at = to_timestamp(datetime(tzinfo=localtz, \
            *(StructuredMilestone(self.env, mil_names[0]).started.timetuple()[:3])))
        db = db or self.env.get_db_cnx()
        cursor = db.cursor()

        base_sql = None
        params = list(mil_names)
        group_by = " GROUP BY t.id, t.type"
        final_statuses = IttecoEvnSetup(self.env).final_statuses
        status_params = ("%s," * len(final_statuses))[:-1]
        params = final_statuses + final_statuses + params
        if self.count_burndown_on == 'quantity':
            base_sql = """SELECT MAX(c.time), t.id, t.type, 1
                FROM ticket t 
                    LEFT JOIN milestone m ON m.name=t.milestone 
                    LEFT OUTER JOIN ticket_change c ON t.id=c.ticket 
                        AND c.field='status' AND c.newvalue IN (%s) 
                WHERE IN (%s) AND m.name IN (%s)""" % \
                    (
                        status_params,
                        status_params,
                        ("%s,"*len(mil_names))[:-1]
                    )
        else:
            base_sql = "SELECT MAX(c.time), t.id, t.type, "+db.cast(db.concat('0','tc.value'),'int')+ \
                """FROM ticket t 
                    LEFT JOIN milestone m ON m.name=t.milestone 
                    LEFT JOIN ticket_custom tc ON t.id=tc.ticket AND tc.name=%%s
                    LEFT OUTER JOIN ticket_change c ON t.id=c.ticket AND c.field='status' 
                        AND c.newvalue IN (%s) 
                WHERE t.status IN (%s) AND m.name IN (%s)""" % \
                    (
                        status_params,
                        status_params,
                        ("%s,"*len(mil_names))[:-1]
                    )
            params = [self.count_burndown_on] + params
            group_by += ", tc.value"
        if tkt_type:
            if isinstance(tkt_type, basestring):
                base_sql += " AND t.type=%s"
                params += [tkt_type]
            else:
                base_sql += " AND t.type IN (%s)" % ("%s," *
                                                     len(tkt_type))[:-1]
                params += list(tkt_type)

        cursor.execute(base_sql + group_by + " ORDER BY 1", params)
        data = [(to_datetime((dt < started_at) and started_at
                             or dt), ttype, sum or 0)
                for dt, tkt_id, ttype, sum in cursor]
        return data
예제 #14
0
    def update(self, req, name, comment, attributes=None):
        """ Updates a structure milestone object."""
        milestone = StructuredMilestone(self.env, name)
        if not milestone.exists:
            raise TracError('Milestone with name %s does not exist' % name)
        req.perm.require('MILESTONE_MODIFY',
                         Resource(milestone.resource.realm))
        if attributes is not None:
            milestone.name = attributes.get('summary')
            milestone.description = attributes.get('description')
            milestone.ticket.populate(attributes)

            def set_date(name, attr_name=None):
                val = attributes.get(name)
                if val is None:
                    return
                val = val and parse_date(val, tzinfo=req.tz) or None
                if attr_name is not None:
                    setattr(milestone, attr_name, val)
                milestone.ticket[name] = val and str(to_timestamp(val))

            set_date('duedate', 'due')
            set_date('completedate', 'completed')
            set_date('started')

        milestone.save_changes(get_reporter_id(req, 'author'), comment)
        return self._milestone_as_dict(milestone)
예제 #15
0
    def milestone(self, req):
        mil_id = req.args.get('obj_id')
        if mil_id:
            req.perm.require('MILESTONE_MODIFY', Resource('milestone', mil_id))
        else:
            req.perm.require('MILESTONE_CREATE')

        milestone = StructuredMilestone(self.env, mil_id)
        descriptor = WhiteboardModule(self.env).get_new_ticket_descriptor(
            [type.name for type in Type.select(self.env)], milestone.ticket.id)

        data = {
            'structured_milestones':
            StructuredMilestone.select(self.env),
            'new_ticket_descriptor':
            descriptor,
            'milestone':
            milestone,
            'action_controls':
            self._get_action_controls(req, descriptor['ticket']),
        }
        return 'itteco_milestone_quick_edit.html', data, 'text/html'
예제 #16
0
    def _comment_milestone(self, req, mil_id):
        milestone = StructuredMilestone(self.env, mil_id)
        if not milestone.exists:
            raise ResourceNotFound('Milestone %s does not exist.' % mil_id,
                                   'Invalid Milestone Name')

        req.perm.require('MILESTONE_MODIFY', milestone.resource)
        changes = TicketModule(self.env).rendered_changelog_entries(
            req, milestone.ticket)
        return 'itteco_milestone_comment.html', {
            'milestone': milestone,
            'changes': changes
        }, 'text/html'
예제 #17
0
    def ticket(self, req):
        tkt_id = req.args.get("obj_id")
        if tkt_id:
            req.perm.require("TICKET_MODIFY", Resource("ticket", tkt_id))
        else:
            req.perm.require("TICKET_CREATE")

        descriptor = WhiteboardModule(self.env).get_new_ticket_descriptor(
            [type.name for type in Type.select(self.env)], tkt_id
        )

        data = {
            "structured_milestones": StructuredMilestone.select(self.env),
            "resolutions": [],  # val.name for val in Resolution.select(self.env)],
            "new_ticket_descriptor": descriptor,
            "action_controls": self._get_action_controls(req, descriptor["ticket"]),
        }
        return "itteco_ticket_edit.html", data, "text/html"
예제 #18
0
    def milestone(self, req):
        mil_id = req.args.get("obj_id")
        if mil_id:
            req.perm.require("MILESTONE_MODIFY", Resource("milestone", mil_id))
        else:
            req.perm.require("MILESTONE_CREATE")

        milestone = StructuredMilestone(self.env, mil_id)
        descriptor = WhiteboardModule(self.env).get_new_ticket_descriptor(
            [type.name for type in Type.select(self.env)], milestone.ticket.id
        )

        data = {
            "structured_milestones": StructuredMilestone.select(self.env),
            "new_ticket_descriptor": descriptor,
            "milestone": milestone,
            "action_controls": self._get_action_controls(req, descriptor["ticket"]),
        }
        return "itteco_milestone_quick_edit.html", data, "text/html"
예제 #19
0
 def _render_link(self, context, name, label, extra=''):
     try:
         milestone = StructuredMilestone(self.env, name)
     except TracError:
         milestone = None
     # Note: the above should really not be needed, `Milestone.exists`
     # should simply be false if the milestone doesn't exist in the db
     # (related to #4130)
     href = context.href.milestone(name)
     if milestone and milestone.exists:
         if 'MILESTONE_VIEW' in context.perm(milestone.resource):
             closed = milestone.is_completed and 'closed ' or ''
             return tag.a(label,
                          class_='%smilestone' % closed,
                          href=href + extra)
     elif 'MILESTONE_CREATE' in context.perm('milestone', name):
         return tag.a(label,
                      class_='missing milestone',
                      href=href + extra,
                      rel='nofollow')
     return tag.a(label, class_='missing milestone')
예제 #20
0
    def ticket(self, req):
        tkt_id = req.args.get('obj_id')
        if tkt_id:
            req.perm.require('TICKET_MODIFY', Resource('ticket', tkt_id))
        else:
            req.perm.require('TICKET_CREATE')

        descriptor = WhiteboardModule(self.env).get_new_ticket_descriptor(
            [type.name for type in Type.select(self.env)], tkt_id)

        data = {
            'structured_milestones':
            StructuredMilestone.select(self.env),
            'resolutions':
            [],  #val.name for val in Resolution.select(self.env)],
            'new_ticket_descriptor':
            descriptor,
            'action_controls':
            self._get_action_controls(req, descriptor['ticket']),
        }
        return 'itteco_ticket_edit.html', data, 'text/html'
예제 #21
0
    def process_request(self, req):
        milestone_id = req.args.get('id')
        req.perm('milestone', milestone_id).require('MILESTONE_VIEW')

        add_link(req, 'up', req.href.roadmap(), _('Roadmap'))

        db = self.env.get_db_cnx()  # TODO: db can be removed
        milestone = StructuredMilestone(self.env, milestone_id, db)
        action = req.args.get('action', 'view')

        if req.method == 'POST':
            if req.args.has_key('cancel'):
                req.redirect(req.href.roadmap())
            elif action == 'delete':
                self._do_delete(req, db, milestone)
            return self._do_save(req, db, milestone)

        elif action in ('new', 'edit', 'view'):
            return self._render_editor(req, db, milestone)
        elif action == 'delete':
            return self._render_confirm(req, db, milestone)

        req.redirect(req.href.roadmap())
예제 #22
0
    def query_stories(self, req, context):
        level = context.get('level')
        
        all_milestones = StructuredMilestone.select(self.env, True)
        mils, mils_dict = self._get_milestones_by_level(all_milestones, level, context.get('show_completed'))
        milestones = [mil.name for mil in mils] +['']
        fields = [
            'summary', 
            'description',
            'owner',
            self.scope_element_weight_field, 
            self.work_element_weight_field
        ]

        def milestone_as_dict(milestone):
            res = dict([(f, milestone.ticket[f]) for f in fields])
            res.update(
                {
                    'id': milestone.name,
                    'references': []
                }
            )
            return res
        empty_scope_element  = {'id': '', 'summary': 'Backlog (no milestone)','references': []}
        
        roots = [empty_scope_element] + [milestone_as_dict(m) for m in mils]
        milestone_by_name = dict([(m['id'], m) for m in roots])
       
        scope_tkt_types = set([t for t in IttecoEvnSetup(self.env).scope_element])       
        tickets, ticket_ids = self._get_tickets_graph(req, milestones, (scope_tkt_types,))
        
        self.env.log.debug('roots ="%s"' % (roots,))
        for ticket in tickets:
            root = milestone_by_name.get(ticket['milestone'],empty_scope_element)
            root['references'].append(ticket)
            
        return roots
예제 #23
0
    def process_request(self, req):
        milestone_realm = Resource('milestone')
        req.perm.require('MILESTONE_VIEW')

        showall = req.args.get('show') == 'all'
        db = self.env.get_db_cnx()
        milestones = [m for m in StructuredMilestone.select(self.env, True, db)
                        if 'MILESTONE_VIEW' in req.perm(m.resource)]
        requested_fmt = req.args.get('format')
        if requested_fmt == 'ics':
            self.render_ics(req, db, milestones)
            return
        max_level = len(IttecoEvnSetup(self.env).milestone_levels)
        max_level = max_level and max_level-1 or 0;
        current_level = int(req.args.get('mil_type', max_level))
        
        if current_level==-1:
            #show all milestones regardless to the level
            milestones = sum([_get_milestone_with_all_kids(mil) for mil in milestones], [])
        else:
            #filter by level
            i =0        
            while i<current_level:
                next_level_mils = []
                for m in milestones:
                    next_level_mils.extend(m.kids)
                milestones = next_level_mils
                i+=1

        calc_on = req.args.get('calc_on')
        ticket_group = req.args.get('ticket_group', 'all')
        selected_types = None
        if ticket_group=='scope_element':
            selected_types = IttecoEvnSetup(self.env).scope_element
        elif ticket_group=='work_element':
            selected_types = IttecoEvnSetup(self.env).work_element
        else:
            ticket_group = 'all'
            
        selected_type_names = [tkt_type.name for tkt_type in Type.select(self.env) 
            if selected_types is None or tkt_type.value in selected_types]

        stats = []
        milestones = [mil for mil in milestones if showall or not mil.is_completed]
        for milestone in milestones:
            tickets = get_tickets_for_structured_milestone(
                self.env, db, milestone.name, calc_on, selected_type_names)
            tickets = apply_ticket_permissions(self.env, req, tickets)
            stat = SelectionTicketGroupStatsProvider(self.env).get_ticket_group_stats(tickets, calc_on)
            stats.append(
                milestone_stats_data(
                    req, stat, [m.name for m in _get_milestone_with_all_kids(milestone)]))

        if requested_fmt=='json':
            self._render_milestones_stats_as_json(req, milestones, stats)
            return
        # FIXME should use the 'webcal:' scheme, probably
        username = None
        if req.authname and req.authname != 'anonymous':
            username = req.authname
        icshref = req.href.roadmap(show=req.args.get('show'), user=username,
                                   format='ics')
        add_link(req, 'alternate', icshref, _('iCalendar'), 'text/calendar',
                 'ics')
        visibility = [{'index':idx, 'label': label, 'active': idx==current_level} 
            for idx, label in enumerate(IttecoEvnSetup(self.env).milestone_levels)]
        ticket_groups = [{'index':value, 'label': name, 'active': value==ticket_group} 
                for value, name in self._ticket_groups]
        
        calculate_on = self.get_statistics_source(req.args.get('calc_on'))
        data = {
            'milestones': milestones,
            'milestone_stats': stats,
            'mil_types': visibility,
            'ticket_groups': ticket_groups,
            'calc_on': calculate_on,
            'queries': [],
            'showall': showall,
        }
        self.env.log.debug('data:%s' % data)
        return 'itteco_roadmap.html', data, None
예제 #24
0
    def _do_save(self, req, db, milestone):
        if milestone.exists:
            req.perm(milestone.resource).require('MILESTONE_MODIFY')
        else:
            req.perm(milestone.resource).require('MILESTONE_CREATE')

        ticket_module = TicketModule(self.env)
        ticket_module._populate(req, milestone.ticket, False)
        if not milestone.exists:
            reporter_id = get_reporter_id(req, 'author')
            milestone.ticket.values['reporter'] = reporter_id

        action = req.args.get('action', 'leave')

        field_changes, problems = ticket_module.get_ticket_changes(
            req, milestone.ticket, action)
        if problems:
            for problem in problems:
                add_warning(req, problem)
                add_warning(
                    req,
                    tag(
                        tag.p('Please review your configuration, '
                              'probably starting with'),
                        tag.pre('[trac]\nworkflow = ...\n'),
                        tag.p('in your ', tag.tt('trac.ini'), '.')))

        ticket_module._apply_ticket_changes(milestone.ticket, field_changes)

        old_name = milestone.name
        new_name = milestone.ticket['summary']

        milestone.name = new_name
        milestone.description = milestone.ticket['description']

        due = req.args.get('duedate', '')
        milestone.due = due and parse_date(due, tzinfo=req.tz) or None
        milestone.ticket['duedate'] = milestone.due and str(
            to_timestamp(milestone.due)) or None

        completed = req.args.get('completedate', '')
        retarget_to = req.args.get('target')

        # Instead of raising one single error, check all the constraints and
        # let the user fix them by going back to edit mode showing the warnings
        warnings = []

        def warn(msg):
            add_warning(req, msg)
            warnings.append(msg)

        # -- check the name
        if new_name:
            if new_name != old_name:
                # check that the milestone doesn't already exists
                # FIXME: the whole .exists business needs to be clarified
                #        (#4130) and should behave like a WikiPage does in
                #        this respect.
                try:
                    other_milestone = StructuredMilestone(
                        self.env, new_name, db)
                    warn(
                        _(
                            'Milestone "%(name)s" already exists, please '
                            'choose another name',
                            name=new_name))
                except ResourceNotFound:
                    pass
        else:
            warn(_('You must provide a name for the milestone.'))

        # -- check completed date
        if action in MilestoneSystem(self.env).starting_action:
            milestone.ticket['started'] = str(to_timestamp(datetime.now(utc)))
        if action in MilestoneSystem(self.env).completing_action:
            milestone.completed = datetime.now(utc)

        if warnings:
            return self._render_editor(req, db, milestone)

        # -- actually save changes
        if milestone.exists:
            cnum = req.args.get('cnum')
            replyto = req.args.get('replyto')
            internal_cnum = cnum
            if cnum and replyto:  # record parent.child relationship
                internal_cnum = '%s.%s' % (replyto, cnum)

            now = datetime.now(utc)
            milestone.save_changes(get_reporter_id(req, 'author'),
                                   req.args.get('comment'),
                                   when=now,
                                   cnum=internal_cnum)
            # eventually retarget opened tickets associated with the milestone
            if 'retarget' in req.args and completed:
                cursor = db.cursor()
                cursor.execute(
                    "UPDATE ticket SET milestone=%s WHERE "
                    "milestone=%s and status != 'closed'",
                    (retarget_to, old_name))
                self.env.log.info('Tickets associated with milestone %s '
                                  'retargeted to %s' % (old_name, retarget_to))
        else:
            milestone.insert()
        db.commit()

        add_notice(req, _('Your changes have been saved.'))
        jump_to = req.args.get('jump_to', 'roadmap')
        if jump_to == 'roadmap':
            req.redirect(req.href.roadmap())
        elif jump_to == 'whiteboard':
            req.redirect(
                req.href.whiteboard('team_tasks') + '#' + milestone.name)
        else:
            req.redirect(req.href.milestone(milestone.name))
예제 #25
0
    def _chart_settings(self, milestone):
        burndown_info = self.burndown_info_provider.metrics(milestone)
        mils =[]
        def flatten(mil):
            mils.append(mil)
            for kid in mil.kids:
                flatten(kid)
        flatten(StructuredMilestone(self.env, milestone))
        fmt_date = lambda x: format_datetime(x, '%Y-%m-%dT%H:%M:%S')
        cvs_data = graphs = events = None
        if burndown_info:
            metrics, graphs = burndown_info
            def get_color(tkt_type):
                tkt_cfg = self.ticket_type_config
                if tkt_cfg:
                    cfg = tkt_cfg.get(tkt_type)
                    if cfg:
                        return cfg.get('max_color')

            graphs = [{'name': graph, 'color': get_color(graph)} for graph in graphs]

            burndown_cvs_data = burnup_cvs_data = []
            keys = ['burndown', 'approximation', 'ideal']
            milestone_dates= dict([(mil.completed or mil.due, mil) for mil in mils ])
            events =[]
            prev_burndown = metrics[0]['burndown']
            prev_burnup = 0
            
            def genitems(metric):
                yield fmt_date(metric['datetime'])
                for key in keys:
                    yield str(metric.get(key,''))
                    
            for metric in metrics:
                ts = metric['datetime']
                line = ",".join(genitems(metric))
                burnup_sum = 0
                burnup = metric.get('burnup',[])
                for item in burnup:
                    burnup_sum -= item
                    line +=','+str(-1*item)
                if burnup:
                    line +=','+str(burnup_sum)
                burndown_cvs_data.append(line)

                if ts in milestone_dates:
                    mil = milestone_dates[ts]
                    if mil.is_completed:
                        del milestone_dates[ts]
                        burndown = metric['burndown']
                        events.append({'datetime': fmt_date(mil.completed),
                            'extended': True, 
                            'text': '"%s" completed\nBurndown delta %d\nBurnup delta %d.' \
                                % (mil.name, prev_burndown-burndown, prev_burnup-burnup_sum) ,
                            'url': self.env.abs_href('milestone',mil.name)})
                        burndown_delta =0
                        prev_burnup = burnup_sum
                        prev_burndown = burndown
            events.extend([{'datetime': fmt_date(mil.due),
                            'text': '"%s" is planned to be completed.' % mil.name ,
                            'url': self.env.abs_href('milestone',mil.name)} for mil in milestone_dates.itervalues()])
            cvs_data = "<![CDATA["+"\n".join(burndown_cvs_data)+"]]>"

        data = {'data': cvs_data, 'graphs': graphs, 'events': events}
        return 'iiteco_chart_settings.xml', data, 'text/xml'
예제 #26
0
    def metrics(self, milestone):
        mil = StructuredMilestone(self.env, milestone)
        if not mil.is_started:
            return None

        scope = self._milestone_scope(mil)

        scope_types = IttecoEvnSetup(self.env).scope_element
        types = self._work_types()
        one_day_delta = timedelta(1)

        metrics = [{
            'datetime': mil.started,
            'burndown': scope,
            'burnup': [0] * len(types)
        }]

        def clone_item(item, new_time):
            item = deepcopy(item)
            item['datetime'] = new_time
            metrics.append(item)
            return item

        for ts, ttype, sum in self._burnup_info(mil):
            last = metrics[-1]
            if ts != last['datetime']:
                if self.fill_idle_days:
                    time_delta = ts - last['datetime']
                    if time_delta.days > 1:
                        last = clone_item(last, ts - one_day_delta)
                last = clone_item(last, ts)

            if ttype in self.initial_plan_element:
                #this ticket type should influence burndown
                last['burndown'] -= sum
            elif ttype not in scope_types:
                #burnup by ticket type turned around axis X, in order to look like burndown
                idx = types.index(ttype)
                last['burnup'][idx] += sum

        calculated_end_date = None
        if len(metrics) > 1:  # do we have any progress?
            start = metrics[0]
            end = metrics[-1]
            if start['burndown'] != end['burndown']:
                # do we have any progress to perform approximations?
                start_ts = to_timestamp(start['datetime'])
                end_ts = to_timestamp(end['datetime'])
                calc_ts = start_ts+ \
                    start['burndown']*(end_ts - start_ts)/ \
                        (start['burndown']-end['burndown'])
                end['approximation'] = end['burndown']
                calculated_end_date = to_datetime(calc_ts)

        if mil.due:  #if milestone is fully planned, add line of ideal progress
            if mil.due > metrics[-1]['datetime']:
                metrics.append({'datetime': mil.due})
            metrics[0]['ideal'] = scope
            metrics[-1]['ideal'] = 0

        if calculated_end_date:
            if calculated_end_date > metrics[-1]['datetime']:
                metrics.append({
                    'datetime': calculated_end_date,
                    'approximation': 0
                })
            else:
                #let's find a correct place on timeline for calculated end date
                for i in xrange(0, len(metrics)):
                    metric = metrics[i]
                    if metric['datetime'] >= calculated_end_date:
                        if metric['datetime'] > calculated_end_date:
                            metric = {'datetime': calculated_end_date}
                            metrics.insert(i, metric)
                        metric['approximation'] = 0
                        break

        return metrics, types
예제 #27
0
    def process_request(self, req):
        milestone_realm = Resource('milestone')
        req.perm.require('MILESTONE_VIEW')

        showall = req.args.get('show') == 'all'
        db = self.env.get_db_cnx()
        milestones = [
            m for m in StructuredMilestone.select(self.env, True, db)
            if 'MILESTONE_VIEW' in req.perm(m.resource)
        ]
        requested_fmt = req.args.get('format')
        if requested_fmt == 'ics':
            self.render_ics(req, db, milestones)
            return
        max_level = len(IttecoEvnSetup(self.env).milestone_levels)
        max_level = max_level and max_level - 1 or 0
        current_level = int(req.args.get('mil_type', max_level))

        if current_level == -1:
            #show all milestones regardless to the level
            milestones = sum(
                [_get_milestone_with_all_kids(mil) for mil in milestones], [])
        else:
            #filter by level
            i = 0
            while i < current_level:
                next_level_mils = []
                for m in milestones:
                    next_level_mils.extend(m.kids)
                milestones = next_level_mils
                i += 1

        calc_on = req.args.get('calc_on')
        ticket_group = req.args.get('ticket_group', 'all')
        selected_types = None
        if ticket_group == 'scope_element':
            selected_types = IttecoEvnSetup(self.env).scope_element
        elif ticket_group == 'work_element':
            selected_types = IttecoEvnSetup(self.env).work_element
        else:
            ticket_group = 'all'

        selected_type_names = [
            tkt_type.name for tkt_type in Type.select(self.env)
            if selected_types is None or tkt_type.value in selected_types
        ]

        stats = []
        milestones = [
            mil for mil in milestones if showall or not mil.is_completed
        ]
        for milestone in milestones:
            tickets = get_tickets_for_structured_milestone(
                self.env, db, milestone.name, calc_on, selected_type_names)
            tickets = apply_ticket_permissions(self.env, req, tickets)
            stat = SelectionTicketGroupStatsProvider(
                self.env).get_ticket_group_stats(tickets, calc_on)
            stats.append(
                milestone_stats_data(
                    req, stat,
                    [m.name for m in _get_milestone_with_all_kids(milestone)]))

        if requested_fmt == 'json':
            self._render_milestones_stats_as_json(req, milestones, stats)
            return
        # FIXME should use the 'webcal:' scheme, probably
        username = None
        if req.authname and req.authname != 'anonymous':
            username = req.authname
        icshref = req.href.roadmap(show=req.args.get('show'),
                                   user=username,
                                   format='ics')
        add_link(req, 'alternate', icshref, _('iCalendar'), 'text/calendar',
                 'ics')
        visibility = [{
            'index': idx,
            'label': label,
            'active': idx == current_level
        } for idx, label in enumerate(
            IttecoEvnSetup(self.env).milestone_levels)]
        ticket_groups = [{
            'index': value,
            'label': name,
            'active': value == ticket_group
        } for value, name in self._ticket_groups]

        calculate_on = self.get_statistics_source(req.args.get('calc_on'))
        data = {
            'milestones': milestones,
            'milestone_stats': stats,
            'mil_types': visibility,
            'ticket_groups': ticket_groups,
            'calc_on': calculate_on,
            'queries': [],
            'showall': showall,
        }
        self.env.log.debug('data:%s' % data)
        return 'itteco_roadmap.html', data, None
예제 #28
0
    def _render_admin_panel(self, req, cat, page, milestone):
        req.perm.require('TICKET_ADMIN')
        add_stylesheet(req, 'itteco/css/common.css')
        add_jscript(
            req, 
            [
                'stuff/ui/ui.core.js',
                'stuff/ui/ui.resizable.js',
                'custom_select.js'
            ],
            IttecoEvnSetup(self.env).debug
        )
        # Detail view?
        if milestone:
            mil = StructuredMilestone(self.env, milestone)
            if req.method == 'POST':
                if req.args.get('save'):
                    mil.name = req.args.get('name')
                    mil.due = mil.completed = None
                    due = req.args.get('duedate', '')
                    if due:
                        mil.due = parse_date(due, req.tz)
                    if req.args.get('completed', False):
                        completed = req.args.get('completeddate', '')
                        mil.completed = parse_date(completed, req.tz)
                        if mil.completed > datetime.now(utc):
                            raise TracError(_('Completion date may not be in '
                                              'the future'),
                                            _('Invalid Completion Date'))
                    mil.description = req.args.get('description', '')
                    mil.parent = req.args.get('parent', None)
                    if mil.parent and mil.parent==mil.name:
                        raise TracError(_('Milestone cannot be parent for itself,Please, give it another thought.'),
                                        _('Something is wrong with Parent Milestone. Will you check it please?'))

                    if mil.parent and not StructuredMilestone(self.env, mil.parent).exists:
                        raise TracError(_('Milestone should have a valid parent. It does not look like this is the case.'),
                                        _('Something is wrong with Parent Milestone. Will you check it please?'))
                    mil.update()
                    req.redirect(req.href.admin(cat, page))
                elif req.args.get('cancel'):
                    req.redirect(req.href.admin(cat, page))

            add_script(req, 'common/js/wikitoolbar.js')
            data = {'view': 'detail', 'milestone': mil}

        else:
            if req.method == 'POST':
                # Add Milestone
                if req.args.get('add') and req.args.get('name'):
                    name = req.args.get('name')
                    try:
                        StructuredMilestone(self.env, name)
                    except ResourceNotFound:
                        mil = StructuredMilestone(self.env)
                        mil.name = name
                        if req.args.get('duedate'):
                            mil.due = parse_date(req.args.get('duedate'),
                                                 req.tz)
                        mil.parent = req.args.get('parent', None)
                        if mil.parent and not StructuredMilestone(self.env, mil.parent).exists:
                            raise TracError(_('Milestone should have a valid parent. It does not look like this is the case'),
                                            _('Something is wrong with Parent Milestone. Will you check it please?'))

                        mil.insert()
                        req.redirect(req.href.admin(cat, page))
                    else:
                        raise TracError(_('Sorry, milestone %s already exists.') % name)

                # Remove milestone
                elif req.args.get('remove'):
                    sel = req.args.get('sel')
                    if not sel:
                        raise TracError(_('Please, select the milestone.'))
                    if not isinstance(sel, list):
                        sel = [sel]
                    db = self.env.get_db_cnx()
                    for name in sel:
                        mil = StructuredMilestone(self.env, name, db=db)
                        mil.delete(db=db)
                    db.commit()
                    req.redirect(req.href.admin(cat, page))

                # Set default milestone
                elif req.args.get('apply'):
                    if req.args.get('default'):
                        name = req.args.get('default')
                        self.config.set('ticket', 'default_milestone', name)
                        self.config.save()
                        req.redirect(req.href.admin(cat, page))

            data = {
                'view': 'list',
                'default': self.config.get('ticket', 'default_milestone'),
            }
            
        # Get ticket count
        db = self.env.get_db_cnx()
        cursor = db.cursor()
        milestones = []
        structured_milestones = StructuredMilestone.select(self.env)
        mil_names = self._get_mil_names(structured_milestones)
        
        cursor.execute("SELECT milestone, COUNT(*) FROM ticket "
                   "WHERE milestone IN (%s) GROUP BY milestone" % ("%s,"*len(mil_names))[:-1], mil_names)
        mil_tkt_quantity = {}
        for mil, cnt in cursor:
            mil_tkt_quantity[mil]=cnt

        data.update({
            'date_hint': get_date_format_hint(),
            'milestones': [(mil, 0) for mil in structured_milestones],# we recover this anyway
            'structured_milestones': structured_milestones,
            'milestone_tickets_quantity': mil_tkt_quantity,
            'max_milestone_level': self.milestone_levels and len(self.milestone_levels)-1 or 0,
            'datetime_hint': get_datetime_format_hint()
        })
        return 'itteco_admin_milestones.html', data