File Coverage

lib/JIRA/REST/Class/Issue/Transitions.pm
Criterion Covered Total %
statement 14 59 23.7
branch 0 10 0.0
condition 0 6 0.0
subroutine 5 18 27.7
pod 11 11 100.0
total 30 104 28.8


line stmt bran cond sub pod time code
1             package JIRA::REST::Class::Issue::Transitions;
2 4     4   2293 use parent qw( JIRA::REST::Class::Abstract );
  4         8  
  4         20  
3 4     4   259 use strict;
  4         9  
  4         63  
4 4     4   22 use warnings;
  4         46  
  4         86  
5 4     4   52 use 5.010;
  4         13  
6              
7             our $VERSION = '0.11';
8             our $SOURCE = 'CPAN';
9             ## $SOURCE = 'GitHub'; # COMMENT
10             # the line above will be commented out by Dist::Zilla
11              
12             # ABSTRACT: A helper class for L<JIRA::REST::Class|JIRA::REST::Class> that represents the state transitions a JIRA issue can go through. Currently assumes a state diagram consisting of Open/In Progress/Resolved/Reopened/In QA/Verified/Closed.
13              
14 4     4   18 use Carp;
  4         11  
  4         2329  
15              
16             __PACKAGE__->mk_contextual_ro_accessors( qw/ transitions / );
17              
18             #pod =method B<transitions>
19             #pod
20             #pod Returns an array of
21             #pod L<JIRA::REST::Class::Issue::Transitions::Transition|JIRA::REST::Class::Issue::Transitions::Transition>
22             #pod objects representing the transitions the issue can currently go through.
23             #pod
24             #pod =accessor B<issue>
25             #pod
26             #pod The L<JIRA::REST::Class::Issue|JIRA::REST::Class::Issue> object this is a
27             #pod transition for.
28             #pod
29             #pod =cut
30              
31             sub init {
32 0     0 1   my $self = shift;
33 0           $self->SUPER::init( @_ );
34 0           $self->_refresh_transitions;
35 0           return;
36             }
37              
38             sub _refresh_transitions {
39 0     0     my $self = shift;
40              
41             $self->{data}
42 0           = $self->issue->get( '/transitions?expand=transitions.fields' );
43              
44             $self->{transitions} = [ #
45             map { #
46 0           $self->issue->make_object( 'transition', { data => $_ } )
47 0           } @{ $self->data->{transitions} }
  0            
48             ];
49              
50 0           return;
51             }
52              
53             #pod =method B<find_transition_named>
54             #pod
55             #pod Returns the transition object for the named transition provided.
56             #pod
57             #pod =cut
58              
59             sub find_transition_named {
60 0     0 1   my $self = shift;
61 0 0         my $name = shift or confess 'no name specified';
62              
63 0           $self->_refresh_transitions;
64              
65 0           foreach my $transition ( $self->transitions ) {
66 0 0         next unless $transition->name eq $name;
67 0           return $transition;
68             }
69              
70 0           croak sprintf "Unable to find transition '%s'\n"
71             . "issue status: %s\n"
72             . "transitions: %s\n",
73             $name,
74             $self->issue->status->name,
75             $self->dump( [ $self->transitions ] );
76             }
77              
78             #pod =method B<block>
79             #pod
80             #pod Blocks the issue.
81             #pod
82             #pod =cut
83              
84 0     0 1   sub block { return shift->find_transition_named( 'Block Issue' )->go( @_ ) }
85              
86             #pod =method B<close>
87             #pod
88             #pod Closes the issue.
89             #pod
90             #pod =cut
91              
92             ## no critic (ProhibitBuiltinHomonyms ProhibitAmbiguousNames)
93 0     0 1   sub close { return shift->find_transition_named( 'Close Issue' )->go( @_ ) }
94             ## use critic
95              
96             #pod =method B<verify>
97             #pod
98             #pod Verifies the issue.
99             #pod
100             #pod =cut
101              
102 0     0 1   sub verify { return shift->find_transition_named( 'Verify Issue' )->go( @_ ) }
103              
104             #pod =method B<resolve>
105             #pod
106             #pod Resolves the issue.
107             #pod
108             #pod =cut
109              
110 0     0 1   sub resolve { return shift->find_transition_named( 'Resolve Issue' )->go( @_ ) }
111              
112             #pod =method B<reopen>
113             #pod
114             #pod Reopens the issue.
115             #pod
116             #pod =cut
117              
118 0     0 1   sub reopen { return shift->find_transition_named( 'Reopen Issue' )->go( @_ ) }
119              
120             #pod =method B<start_progress>
121             #pod
122             #pod Starts progress on the issue.
123             #pod
124             #pod =cut
125              
126             sub start_progress {
127 0     0 1   return shift->find_transition_named( 'Start Progress' )->go( @_ );
128             }
129              
130             #pod =method B<stop_progress>
131             #pod
132             #pod Stops progress on the issue.
133             #pod
134             #pod =cut
135              
136             sub stop_progress {
137 0     0 1   return shift->find_transition_named( 'Stop Progress' )->go( @_ );
138             }
139              
140             #pod =method B<start_qa>
141             #pod
142             #pod Starts QA on the issue.
143             #pod
144             #pod =cut
145              
146 0     0 1   sub start_qa { return shift->find_transition_named( 'Start QA' )->go( @_ ) }
147              
148             #pod =method B<transition_walk>
149             #pod
150             #pod This method takes three unnamed parameters:
151             #pod + The name of the end target issue status
152             #pod + A hashref mapping possible current states to intermediate states
153             #pod that will progress the issue towards the end target issue status
154             #pod + A callback subroutine reference that will be called after each
155             #pod transition with the name of the current issue state and the name
156             #pod of the state it is transitioning to (defaults to an empty subroutine
157             #pod reference).
158             #pod
159             #pod =cut
160              
161             my %state_to_transition = (
162             'Open' => 'Stop Progress',
163             'In Progress' => 'Start Progress',
164             'Resolved' => 'Resolve Issue',
165             'In QA' => 'Start QA',
166             'Verified' => 'Verify Issue',
167             'Closed' => 'Close Issue',
168             'Reopened' => 'Reopen Issue',
169             'Blocked' => 'Block Issue',
170             );
171              
172             sub transition_walk {
173 0     0 1   my $self = shift;
174 0           my $target = shift;
175 0           my $map = shift;
176 0   0 0     my $callback = shift // sub { };
177 0           my $name = $self->issue->status->name;
178              
179 0   0       my $orig_assignee = $self->issue->assignee // q{};
180              
181 0           until ( $name eq $target ) {
182 0 0         if ( exists $map->{$name} ) {
183 0           my $to = $map->{$name};
184 0 0         unless ( exists $state_to_transition{$to} ) {
185 0           die "Unknown target state '$to'!\n";
186             }
187             my $trans
188 0           = $self->find_transition_named( $state_to_transition{$to} );
189 0           $callback->( $name, $to );
190 0           $trans->go;
191             }
192             else {
193 0           die "Don't know how to transition from '$name' to '$target'!\n";
194             }
195              
196             # get the new status name
197 0           $name = $self->issue->status->name;
198             }
199              
200             # put the owner back to who it's supposed
201             # to be if it changed during our walk
202 0   0       my $current_assignee = $self->issue->assignee // q{};
203 0 0         if ( $current_assignee ne $orig_assignee ) {
204 0           $self->issue->set_assignee( $orig_assignee );
205             }
206              
207 0           return;
208             }
209              
210             1;
211              
212             #pod =head1 SEE ALSO
213             #pod
214             #pod =head2 JIRA REST API Reference L<Do transition|https://docs.atlassian.com/jira/REST/latest/#api/2/issue-doTransition>
215             #pod
216             #pod The fields that can be set on transition, in either the fields parameter or the
217             #pod update parameter can be determined using the
218             #pod C</rest/api/2/issue/{issueIdOrKey}/transitions?expand=transitions.fields>
219             #pod resource. If a field is not configured to appear on the transition screen, then
220             #pod it will not be in the transition metadata, and a field validation error will
221             #pod occur if it is submitted.
222             #pod
223             #pod =cut
224              
225             __END__
226              
227             =pod
228              
229             =encoding UTF-8
230              
231             =for :stopwords Packy Anderson Alexandr Alexey Ciornii Melezhik
232              
233             =head1 NAME
234              
235             JIRA::REST::Class::Issue::Transitions - A helper class for L<JIRA::REST::Class|JIRA::REST::Class> that represents the state transitions a JIRA issue can go through. Currently assumes a state diagram consisting of Open/In Progress/Resolved/Reopened/In QA/Verified/Closed.
236              
237             =head1 VERSION
238              
239             version 0.11
240              
241             =head1 METHODS
242              
243             =head2 B<transitions>
244              
245             Returns an array of
246             L<JIRA::REST::Class::Issue::Transitions::Transition|JIRA::REST::Class::Issue::Transitions::Transition>
247             objects representing the transitions the issue can currently go through.
248              
249             =head2 B<find_transition_named>
250              
251             Returns the transition object for the named transition provided.
252              
253             =head2 B<block>
254              
255             Blocks the issue.
256              
257             =head2 B<close>
258              
259             Closes the issue.
260              
261             =head2 B<verify>
262              
263             Verifies the issue.
264              
265             =head2 B<resolve>
266              
267             Resolves the issue.
268              
269             =head2 B<reopen>
270              
271             Reopens the issue.
272              
273             =head2 B<start_progress>
274              
275             Starts progress on the issue.
276              
277             =head2 B<stop_progress>
278              
279             Stops progress on the issue.
280              
281             =head2 B<start_qa>
282              
283             Starts QA on the issue.
284              
285             =head2 B<transition_walk>
286              
287             This method takes three unnamed parameters:
288             + The name of the end target issue status
289             + A hashref mapping possible current states to intermediate states
290             that will progress the issue towards the end target issue status
291             + A callback subroutine reference that will be called after each
292             transition with the name of the current issue state and the name
293             of the state it is transitioning to (defaults to an empty subroutine
294             reference).
295              
296             =head1 READ-ONLY ACCESSORS
297              
298             =head2 B<issue>
299              
300             The L<JIRA::REST::Class::Issue|JIRA::REST::Class::Issue> object this is a
301             transition for.
302              
303             =head1 RELATED CLASSES
304              
305             =over 2
306              
307             =item * L<JIRA::REST::Class|JIRA::REST::Class>
308              
309             =item * L<JIRA::REST::Class::Abstract|JIRA::REST::Class::Abstract>
310              
311             =item * L<JIRA::REST::Class::Issue|JIRA::REST::Class::Issue>
312              
313             =item * L<JIRA::REST::Class::Issue::Transitions::Transition|JIRA::REST::Class::Issue::Transitions::Transition>
314              
315             =back
316              
317             =head1 SEE ALSO
318              
319             =head2 JIRA REST API Reference L<Do transition|https://docs.atlassian.com/jira/REST/latest/#api/2/issue-doTransition>
320              
321             The fields that can be set on transition, in either the fields parameter or the
322             update parameter can be determined using the
323             C</rest/api/2/issue/{issueIdOrKey}/transitions?expand=transitions.fields>
324             resource. If a field is not configured to appear on the transition screen, then
325             it will not be in the transition metadata, and a field validation error will
326             occur if it is submitted.
327              
328             =head1 AUTHOR
329              
330             Packy Anderson <packy@cpan.org>
331              
332             =head1 COPYRIGHT AND LICENSE
333              
334             This software is Copyright (c) 2017 by Packy Anderson.
335              
336             This is free software, licensed under:
337              
338             The Artistic License 2.0 (GPL Compatible)
339              
340             =cut