def test_get_ticket_fields_milestone_update_completed(self): """Cached ticket fields are updated when milestone is completed date is changed. """ fields = self.ticket_system.get_ticket_fields() milestone_field = self._get_ticket_field('milestone') m2 = Milestone(self.env, 'milestone2') m2.completed = datetime_now(utc) m2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_milestone_field = self._get_ticket_field('milestone') self.assertNotEqual(fields, updated_fields) self.assertEqual( ['milestone1', 'milestone2', 'milestone3', 'milestone4'], milestone_field['options']) self.assertEqual( ['milestone2', 'milestone1', 'milestone3', 'milestone4'], updated_milestone_field['options'])
def test_change_milestone_requires_milestone_view(self): """Changing ticket milestone requires MILESTONE_VIEW.""" perm_sys = PermissionSystem(self.env) self._insert_ticket(summary='the summary') for name in ('milestone1', 'milestone2'): m = Milestone(self.env) m.name = name m.insert() def make_req(authname): return MockRequest(self.env, authname=authname, method='GET', path_info='/ticket/1') def get_milestone_field(fields): for field in fields: if 'milestone' == field['name']: return field perm_sys.grant_permission('user', 'TICKET_VIEW') req = make_req('user') self.assertTrue(self.ticket_module.match_request(req)) data = self.ticket_module.process_request(req)[1] milestone_field = get_milestone_field(data['fields']) self.assertFalse(milestone_field['editable']) self.assertEqual([], milestone_field['optgroups'][0]['options']) self.assertEqual([], milestone_field['optgroups'][1]['options']) perm_sys.grant_permission('user_w_mv', 'TICKET_VIEW') perm_sys.grant_permission('user_w_mv', 'MILESTONE_VIEW') req = make_req('user_w_mv') self.assertTrue(self.ticket_module.match_request(req)) data = self.ticket_module.process_request(req)[1] milestone_field = get_milestone_field(data['fields']) self.assertTrue(milestone_field['editable']) self.assertEqual([], milestone_field['optgroups'][0]['options']) self.assertEqual(['milestone1', 'milestone2'], milestone_field['optgroups'][1]['options'])
def _render_link(self, context, name, label, extra=''): if not (name or extra): return tag() try: milestone = Milestone(self.env, name) except ResourceNotFound: 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) exists = milestone and milestone.exists if exists: if 'MILESTONE_VIEW' in context.perm(milestone.resource): title = None if hasattr(context, 'req'): if milestone.is_completed: title = _( "Completed %(duration)s ago (%(date)s)", duration=pretty_timedelta(milestone.completed), date=user_time(context.req, format_datetime, milestone.completed)) elif milestone.is_late: title = _("%(duration)s late (%(date)s)", duration=pretty_timedelta(milestone.due), date=user_time(context.req, format_datetime, milestone.due)) elif milestone.due: title = _("Due in %(duration)s (%(date)s)", duration=pretty_timedelta(milestone.due), date=user_time(context.req, format_datetime, milestone.due)) else: title = _("No date set") closed = 'closed ' if milestone.is_completed else '' return tag.a(label, class_='%smilestone' % closed, href=href + extra, title=title) elif 'MILESTONE_CREATE' in context.perm(self.realm, name): return tag.a(label, class_='missing milestone', href=href + extra, rel='nofollow') return tag.a(label, class_=classes('milestone', missing=not exists))
def render_ticket_action_control(self, req, ticket, action): actions = ConfigurableTicketWorkflow(self.env).actions label = actions[action]['label'] hint = None old_milestone = ticket._old.get('milestone') if old_milestone is None: resolutions, milestone = \ self._get_resolutions_and_milestone(action) if resolutions: try: Milestone(self.env, milestone) except ResourceNotFound: pass else: res_hint = ', '.join("'%s'" % r for r in resolutions) hint = _( "For resolution %(resolutions)s the milestone " "will be set to '%(milestone)s'.", resolutions=res_hint, milestone=milestone) return label, None, hint
def task(add): """the thread task - either we are discovering or adding events""" with lock: env = ProductEnvironment(self.global_env, self.default_product) if add: name = 'milestone_from_' + threading.current_thread().name milestone = Milestone(env) milestone.name = name milestone.insert() else: # collect the names of milestones reported by Milestone and # directly from the db - as sets to ease comparison later results.append({ 'from_t': set([m.name for m in Milestone.select(env)]), 'from_db': set([ v[0] for v in self.env.db_query( "SELECT name FROM milestone") ]) })
def process_request(self, req): req.perm.require('MILESTONE_VIEW') milestone_id = req.args.get('id') self.env.log.info("mdashboard process request %s, %s" % (req.path_info, req.args.get('id'))) add_link(req, 'up', req.href.pdashboard(), 'Dashboard') db = self.env.get_db_cnx() milestone = Milestone(self.env, milestone_id, db) if not milestone_id: req.redirect(req.href.pdashboard()) self.env.log.info("request mdashboard") add_stylesheet(req, 'pd/css/dashboard.css') return self._render_view(req, db, milestone)
def _render_link(self, context, name, label, extra=''): try: milestone = Milestone(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 = 'closed ' if milestone.is_completed else '' 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')
def copy_milestone(source_env, dest_env, name, dest_db=None): # In case a string gets passed in if not isinstance(source_env, Environment): source_env = _open_environment(source_env) if not isinstance(dest_env, Environment): dest_env = _open_environment(dest_env) # Log message source_env.log.info( 'DatamoverPlugin: Moving milestone %s to the environment at %s', name, dest_env.path) dest_env.log.info( 'DatamoverPlugin: Moving milestone %s from the environment at %s', name, source_env.path) # Open databases source_db = source_env.get_db_cnx() source_cursor = source_db.cursor() handle_commit = True if not dest_db: dest_db, handle_commit = dest_env.get_db_cnx(), False dest_cursor = dest_db.cursor() # Remove the milestone from the destination try: dest_milestone = Milestone(dest_env, name, db=dest_db) dest_milestone.delete(retarget_to=name, db=dest_db) except TracError: pass # Copy each entry in the milestone table source_cursor.execute('SELECT * FROM milestone WHERE name=%s', (name, )) for row in source_cursor: milestone_data = dict( zip([d[0] for d in source_cursor.description], row)) q = make_query(milestone_data, 'milestone') dest_cursor.execute(*q) if handle_commit: dest_db.commit()
def test_add_milestone_with_spaces(self): name = 'mile stone 5' map = MilestoneAdminPanel(self.env) req = MockRequest(self.env, method='POST', args={ 'name': ' mile \t stone \t 5 ', 'add': True }) self.assertRaises(ResourceNotFound, Milestone, self.env, name) self.assertRaises(RequestDone, map.render_admin_panel, req, 'ticket', 'milestone', None) self.assertIn('The milestone "mile stone 5" has been added.', req.chrome['notices']) milestone = Milestone(self.env, name) self.assertEqual(name, milestone.name) with self.assertRaises(ResourceExistsError) as cm: map.render_admin_panel(req, 'ticket', 'milestone', None) self.assertIn('Milestone "mile stone 5" already exists', unicode(cm.exception))
def get_ticket_changes(self, req, ticket, action): if action == 'resolve' and \ req.args and 'action_resolve_resolve_resolution' in req.args: old_milestone = ticket._old.get('milestone') or None user_milestone = ticket['milestone'] or None # If there's no user defined milestone, we try to set it # using the defined resolution -> milestone mapping. if old_milestone is None: new_status = req.args['action_resolve_resolve_resolution'] new_milestone = self.__get_new_milestone(ticket, action, new_status) # ... but we don't reset it to None unless it was None if new_milestone is not None or user_milestone is None: try: milestone = Milestone(self.env, new_milestone) self.log.info('changed milestone from %s to %s', old_milestone, new_milestone) return {'milestone': new_milestone} except ResourceNotFound: add_warning(req, _("Milestone %(name)s does not exist.", name=new_milestone)) return {}
def render_ticket_action_control(self, req, ticket, action): actions = ConfigurableTicketWorkflow(self.env).actions label = actions[action]['label'] res_ms = self.__get_resolution_milestone_dict(ticket, action) resolutions = '' milestone = None for i, resolution in enumerate(res_ms): if i > 0: resolutions = "%s, '%s'" % (resolutions, resolution) else: resolutions = "'%s'" % resolution milestone = res_ms[resolution] hint = None if res_ms: try: Milestone(self.env, milestone) except ResourceNotFound: pass else: hint = _("For resolution %(resolutions)s the milestone will " "be set to '%(milestone)s'.", resolutions=resolutions, milestone=milestone) return label, None, hint
def test_reset_milestone(self): self.env.config.set( 'ticket', 'workflow', 'ConfigurableTicketWorkflow,TicketWorkflowOpResetMilestone') self._config_set('ticket-workflow', [ ('reset-milestone', '* -> *'), ('reset-milestone.operations', 'reset_milestone'), ]) tktid = self._insert_ticket(when=datetime(2017, 3, 9, tzinfo=utc), summary='reset milestone', milestone='milestone1', reporter='anonymous', owner='joe') ticket = Ticket(self.env, tktid) req = self._post_req('reset-milestone', ticket) self.assertTrue(self.tktmod.match_request(req)) self.assertRaises(RequestDone, self.tktmod.process_request, req) ticket = Ticket(self.env, tktid) self.assertEqual('milestone1', ticket['milestone']) milestone = Milestone(self.env, ticket['milestone']) milestone.completed = datetime(2017, 3, 8, tzinfo=utc) milestone.update() req = self._post_req('reset-milestone', ticket) self.assertTrue(self.tktmod.match_request(req)) self.assertRaises(RequestDone, self.tktmod.process_request, req) ticket = Ticket(self.env, tktid) self.assertEqual('', ticket['milestone']) ticket['milestone'] = 'unknown-milestone' ticket.save_changes(when=datetime(2017, 3, 8, 1, tzinfo=utc)) req = self._post_req('reset-milestone', ticket) self.assertTrue(self.tktmod.match_request(req)) self.assertRaises(RequestDone, self.tktmod.process_request, req) ticket = Ticket(self.env, tktid) self.assertEqual('unknown-milestone', ticket['milestone'])
def test_create_milestone_without_name(self): milestone = Milestone(self.env) self.assertRaises(AssertionError, milestone.insert)
def _create_milestone(self, **values): milestone = Milestone(self.env) for k, v in values.iteritems(): setattr(milestone, k, v) return milestone
def test_update_milestone_without_name(self): self.env.db_transaction("INSERT INTO milestone (name) VALUES ('Test')") milestone = Milestone(self.env, 'Test') milestone.name = None self.assertRaises(TracError, milestone.update)
def _test_change_milestone(self, editor): milestone = Milestone(self.env) milestone.name = 'milestone1' milestone.insert() perm_cache = PermissionCache(self.env, editor, milestone.resource) return perm_cache, milestone.resource
def testBacklogForMultipleSprint(self): """Tests a Backlog associated to a Sprint with multiple sprints""" # Creates a Milestone m = Milestone(self.env) m.name = "Milestone 1" m.insert() # Create 2 Sprints sprint1 = self.teh.create_sprint(name="Sprint 1", start=to_datetime(t=None), duration=20, milestone=m.name) sprint2 = self.teh.create_sprint(name="Sprint 2", start=to_datetime(t=None), duration=20, milestone=m.name) # Create some tickets s1 = self.teh.create_ticket(Type.USER_STORY, props={ Key.STORY_POINTS: '3', Key.SPRINT: sprint1.name }) self.assert_true( s1.link_to( self.teh.create_ticket(Type.TASK, props={ Key.REMAINING_TIME: '4', Key.SPRINT: sprint1.name }))) self.assert_true( s1.link_to( self.teh.create_ticket(Type.TASK, props={ Key.REMAINING_TIME: '8', Key.SPRINT: sprint1.name }))) self.assert_true( s1.link_to( self.teh.create_ticket(Type.TASK, props={Key.REMAINING_TIME: '4'}))) s2 = self.teh.create_ticket(Type.USER_STORY, props={ Key.STORY_POINTS: '5', Key.SPRINT: sprint2.name }) self.assert_true( s2.link_to( self.teh.create_ticket(Type.TASK, props={ Key.REMAINING_TIME: '2', Key.SPRINT: sprint2.name }))) self.assert_true( s2.link_to( self.teh.create_ticket(Type.TASK, props={Key.REMAINING_TIME: '3'}))) # Creates the Backlog bound to the Sprint backlog = BacklogConfiguration(self.env, name="Sprint-Backlog", type=BacklogType.SPRINT) backlog.ticket_types = [Type.USER_STORY, Type.TASK] backlog.save() # The Backlog should contains only the items planned for the Sprint, and with parents # planned for the Sprint too backlog1 = self.bmm.get(name="Sprint-Backlog", scope=sprint1.name) self.assert_length(3, backlog1) backlog2 = self.bmm.get(name="Sprint-Backlog", scope=sprint2.name) self.assert_length(2, backlog2)
def _do_save(self, req, milestone): if milestone.exists: req.perm(milestone.resource).require('MILESTONE_MODIFY') else: req.perm(milestone.resource).require('MILESTONE_CREATE') old_name = milestone.name new_name = req.args.get('name') milestone.description = req.args.get('description', '') if 'due' in req.args: due = req.args.get('duedate', '') milestone.due = user_time(req, parse_date, due, hint='datetime') \ if due else None else: milestone.due = None completed = req.args.get('completeddate', '') 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 the name has changed, check that the milestone doesn't already # exist # FIXME: the whole .exists business needs to be clarified # (#4130) and should behave like a WikiPage does in # this respect. try: new_milestone = Milestone(self.env, new_name) if new_milestone.name == old_name: pass # Creation or no name change elif new_milestone.name: warn( _( 'Milestone "%(name)s" already exists, please ' 'choose another name.', name=new_milestone.name)) else: warn(_('You must provide a name for the milestone.')) except ResourceNotFound: milestone.name = new_name # -- check completed date if 'completed' in req.args: completed = user_time(req, parse_date, completed, hint='datetime') if completed else None if completed and completed > datetime.now(utc): warn(_('Completion date may not be in the future')) else: completed = None milestone.completed = completed if warnings: return self._render_editor(req, milestone) # -- actually save changes if milestone.exists: milestone.update() # eventually retarget opened tickets associated with the milestone if 'retarget' in req.args and completed: self.env.db_transaction( """ UPDATE ticket SET milestone=%s WHERE milestone=%s and status != 'closed' """, (retarget_to, old_name)) self.log.info("Tickets associated with milestone %s " "retargeted to %s" % (old_name, retarget_to)) else: milestone.insert() add_notice(req, _("Your changes have been saved.")) req.redirect(req.href.milestone(milestone.name))
def save_milestone(self, req, milestone): # Instead of raising one single error, check all the constraints # and let the user fix them by going back to edit mode and showing # the warnings warnings = [] def warn(msg): add_warning(req, msg) warnings.append(msg) milestone.description = req.args.get('description', '') if 'due' in req.args: duedate = req.args.get('duedate') milestone.due = user_time(req, parse_date, duedate, hint='datetime') \ if duedate else None else: milestone.due = None # -- check completed date if 'completed' in req.args: completed = req.args.get('completeddate', '') completed = user_time(req, parse_date, completed, hint='datetime') if completed else None if completed and completed > datetime_now(utc): warn(_("Completion date may not be in the future")) else: completed = None milestone.completed = completed # -- check the name # If the name has changed, check that the milestone doesn't already # exist # FIXME: the whole .exists business needs to be clarified # (#4130) and should behave like a WikiPage does in # this respect. new_name = req.args.get('name') try: new_milestone = Milestone(self.env, new_name) except ResourceNotFound: milestone.name = new_name else: if new_milestone.name != milestone.name: if new_milestone.name: warn( _( 'Milestone "%(name)s" already exists, please ' 'choose another name.', name=new_milestone.name)) else: warn(_("You must provide a name for the milestone.")) if warnings: return False # -- actually save changes if milestone.exists: milestone.update(author=req.authname) if completed and 'retarget' in req.args: comment = req.args.get('comment', '') retarget_to = req.args.get('target') or None retargeted_tickets = \ milestone.move_tickets(retarget_to, req.authname, comment, exclude_closed=True) add_notice( req, _( 'The open tickets associated with ' 'milestone "%(name)s" have been retargeted ' 'to milestone "%(retarget)s".', name=milestone.name, retarget=retarget_to)) new_values = {'milestone': retarget_to} comment = comment or \ _("Open tickets retargeted after milestone closed") event = BatchTicketChangeEvent(retargeted_tickets, None, req.authname, comment, new_values, None) try: NotificationSystem(self.env).notify(event) except Exception as e: self.log.error( "Failure sending notification on ticket " "batch change: %s", exception_to_unicode(e)) add_warning( req, tag_( "The changes have been saved, but " "an error occurred while sending " "notifications: %(message)s", message=to_unicode(e))) add_notice(req, _("Your changes have been saved.")) else: milestone.insert() add_notice( req, _('The milestone "%(name)s" has been added.', name=milestone.name)) return True
def _get_milestone(self, name): try: return Milestone(self.env, name) except ResourceNotFound: return None
def get_ticket_changes(self, req, ticket, action): """Returns the change of milestone, if needed.""" milestone = Milestone(self.env,ticket['milestone']) if milestone.is_completed: return {'milestone': ''} return {}