1# -*- coding: utf-8 -*- 2# 3# Copyright (C) 2012-2021 Edgewall Software 4# Copyright (C) 2012 Franz Mayer <franz.mayer@gefasoft.de> 5# All rights reserved. 6# 7# This software is licensed as described in the file COPYING, which 8# you should have received as part of this distribution. The terms 9# are also available at https://trac.edgewall.org/wiki/TracLicense. 10# 11# This software consists of voluntary contributions made by many 12# individuals. For the exact contribution history, see the revision 13# history and logs, available at https://trac.edgewall.org/. 14 15from trac.core import Component, implements 16from trac.resource import ResourceNotFound 17from trac.ticket.api import ITicketActionController 18from trac.ticket.default_workflow import ConfigurableTicketWorkflow 19from trac.ticket.model import Milestone 20from trac.util import to_list 21from trac.util.translation import _ 22from trac.web.chrome import add_warning 23 24revision = "$Rev$" 25url = "$URL$" 26 27 28class MilestoneOperation(Component): 29 """Sets milestone for specified resolutions. 30 31 Example: 32 {{{#!ini 33 [ticket-workflow] 34 resolve.operations = set_resolution,set_milestone 35 resolve.milestone = invalid,wontfix,duplicate,worksforme -> rejected 36 }}} 37 38 When setting resolution to `duplicate` the milestone will 39 automatically change to `rejected`. If user changes milestone 40 manually when resolving the ticket, this workflow operation has 41 ''no effect''. 42 43 Don't forget to add `MilestoneOperation` to the `workflow` option 44 in the `[ticket]` section of TracIni. When added to the default 45 value of `workflow`, the line will look like this: 46 {{{#!ini 47 [ticket] 48 workflow = ConfigurableTicketWorkflow,MilestoneOperation 49 }}} 50 """ 51 52 implements(ITicketActionController) 53 54 def get_ticket_actions(self, req, ticket): 55 controller = ConfigurableTicketWorkflow(self.env) 56 return controller.get_actions_by_operation_for_req(req, ticket, 57 'set_milestone') 58 59 def get_all_status(self): 60 return [] 61 62 def render_ticket_action_control(self, req, ticket, action): 63 actions = ConfigurableTicketWorkflow(self.env).actions 64 label = actions[action]['label'] 65 hint = None 66 old_milestone = ticket._old.get('milestone') 67 if old_milestone is None: 68 resolutions, milestone = \ 69 self._get_resolutions_and_milestone(action) 70 if resolutions: 71 try: 72 Milestone(self.env, milestone) 73 except ResourceNotFound: 74 pass 75 else: 76 res_hint = ', '.join("'%s'" % r for r in resolutions) 77 hint = _("For resolution %(resolutions)s the milestone " 78 "will be set to '%(milestone)s'.", 79 resolutions=res_hint, milestone=milestone) 80 return label, None, hint 81 82 def get_ticket_changes(self, req, ticket, action): 83 old_milestone = ticket._old.get('milestone') 84 if old_milestone is None: 85 new_milestone = self._get_resolutions_and_milestone(action)[1] 86 try: 87 Milestone(self.env, new_milestone) 88 except ResourceNotFound: 89 add_warning(req, _("Milestone %(name)s does not exist.", 90 name=new_milestone)) 91 else: 92 self.log.info("Changed milestone from %s to %s", 93 old_milestone, new_milestone) 94 return {'milestone': new_milestone} 95 return {} 96 97 def apply_action_side_effects(self, req, ticket, action): 98 pass 99 100 def _get_resolutions_and_milestone(self, action): 101 transitions = self.config.get('ticket-workflow', action + '.milestone') 102 milestone = None 103 resolutions = [] 104 try: 105 transition = to_list(transitions, sep='->') 106 except ValueError: 107 pass 108 else: 109 if len(transition) == 2: 110 resolutions = to_list(transition[0]) 111 milestone = transition[1] 112 return resolutions, milestone 113