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