File Coverage

lib/JIRA/REST/Class/Issue.pm
Criterion Covered Total %
statement 23 219 10.5
branch 0 56 0.0
condition 0 50 0.0
subroutine 8 51 15.6
pod 30 30 100.0
total 61 406 15.0


line stmt bran cond sub pod time code
1             package JIRA::REST::Class::Issue;
2 4     4   1804 use parent qw( JIRA::REST::Class::Abstract );
  4         8  
  4         40  
3 4     4   189 use strict;
  4         8  
  4         61  
4 4     4   15 use warnings;
  4         4  
  4         77  
5 4     4   50 use 5.010;
  4         8  
6              
7             our $VERSION = '0.12';
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 an
13             # individual JIRA issue as an object.
14              
15 4     4   17 use Carp;
  4         4  
  4         208  
16 4     4   19 use Readonly 2.04;
  4         45  
  4         157  
17 4     4   22 use Scalar::Util qw( weaken );
  4         8  
  4         1025  
18              
19             # creating a bunch of read-only accessors
20              
21             # contextual returns lists or arrayrefs, depending on context
22             Readonly my @CONTEXTUAL => qw( components versions );
23              
24             __PACKAGE__->mk_contextual_ro_accessors( @CONTEXTUAL );
25              
26             # fields that will be turned into JIRA::REST::Class::User objects
27             Readonly my @USERS => qw( assignee creator reporter );
28              
29             # fields that will be turned into DateTime objects
30             Readonly my @DATES => qw( created duedate lastViewed resolutiondate updated );
31              
32             # other fields we're objectifying and storing at the top level of the hash
33             Readonly my @TOP_LEVEL => qw( project issuetype status url );
34              
35             __PACKAGE__->mk_ro_accessors( @TOP_LEVEL, @USERS, @DATES );
36              
37             # fields that are under $self->{data}
38             Readonly my @DATA => qw( expand fields id key self );
39             __PACKAGE__->mk_data_ro_accessors( @DATA );
40              
41             # fields that are under $self->{data}->{fields}
42             Readonly my @FIELDS => qw( aggregateprogress aggregatetimeestimate
43             aggregatetimeoriginalestimate aggregatetimespent
44             description environment fixVersions issuelinks
45             labels priority progress resolution summary
46             timeestimate timeoriginalestimate timespent
47             votes watches workratio );
48              
49             __PACKAGE__->mk_field_ro_accessors( @FIELDS );
50              
51             #pod =head1 DESCRIPTION
52             #pod
53             #pod This object represents a JIRA issue as an object. It is overloaded so it
54             #pod returns the C<key> of the issue when stringified, and the C<id> of the issue
55             #pod when it is used in a numeric context. If two of these objects are compared
56             #pod I<as strings>, the C<key> of the issues will be used for the comparison,
57             #pod while numeric comparison will compare the C<id>s of the issues.
58             #pod
59             #pod =cut
60              
61             #<<<
62             use overload
63 0     0     '""' => sub { shift->key },
64 0     0     '0+' => sub { shift->id },
65             '<=>' => sub {
66 0     0     my( $A, $B ) = @_;
67 0 0         my $AA = ref $A ? $A->id : $A;
68 0 0         my $BB = ref $B ? $B->id : $B;
69 0           $AA <=> $BB
70             },
71             'cmp' => sub {
72 0     0     my( $A, $B ) = @_;
73 0 0         my $AA = ref $A ? $A->key : $A;
74 0 0         my $BB = ref $B ? $B->key : $B;
75 0           $AA cmp $BB
76 4     4   22 };
  4         7  
  4         55  
77             #>>>
78              
79             sub init {
80 0     0 1   my $self = shift;
81 0           $self->SUPER::init( @_ );
82              
83 0           my $jira = $self->jira;
84 0           $self->{url} = $jira->strip_protocol_and_host( $self->self );
85              
86             # make user objects
87 0           foreach my $field ( @USERS ) {
88 0           $self->populate_scalar_field( $field, 'user', $field );
89             }
90              
91             # make date objects
92 0           foreach my $field ( @DATES ) {
93 0           $self->{$field} = $self->make_date( $self->fields->{$field} );
94             }
95              
96 0           $self->populate_list_field( 'components', 'projectcomp', 'components' );
97 0           $self->populate_list_field( 'versions', 'projectvers', 'versions' );
98 0           $self->populate_scalar_field( 'project', 'project', 'project' );
99 0           $self->populate_scalar_field( 'issuetype', 'issuetype', 'issuetype' );
100 0           $self->populate_scalar_field( 'status', 'status', 'status' );
101 0           $self->populate_scalar_field( #
102             'timetracking', 'timetracking', 'timetracking',
103             );
104              
105 0 0         unless ( defined &is_bug ) {
106              
107             # if we haven't defined booleans to determine whether or not this
108             # issue is a particular type, define those methods now
109 0           foreach my $type ( $jira->issue_types ) {
110 0           ( my $subname = lc 'is_' . $type->name ) =~ s/\s+|\-/_/xmsg;
111             $self->make_subroutine(
112             $subname,
113             sub {
114 0     0     shift->fields->{issuetype}->{id} == $type->id;
115             }
116 0           );
117             }
118             }
119 0           return;
120             }
121              
122 0     0 1   sub component_count { return scalar @{ shift->{components} } }
  0            
123              
124             #
125             # rather than just use the minimal information in the issue's
126             # parent hash, fetch the parent issue fully when someone first
127             # uses the parent_accessor
128             #
129              
130 0     0 1   sub has_parent { return exists shift->fields->{parent} }
131              
132             __PACKAGE__->mk_lazy_ro_accessor(
133             'parent',
134             sub {
135             my $self = shift;
136             return unless $self->has_parent; # some issues have no parent
137              
138             my $parent = $self->fields->{parent}->{self};
139             my $url = $self->jira->strip_protocol_and_host( $parent );
140             return $self->make_object(
141             'issue',
142             {
143             data => $self->jira->get( $url )
144             }
145             );
146             }
147             );
148              
149             __PACKAGE__->mk_lazy_ro_accessor(
150             'changelog',
151             sub {
152             my $self = shift;
153             return $self->make_object( 'changelog' );
154             }
155             );
156              
157             __PACKAGE__->mk_lazy_ro_accessor(
158             'comments',
159             sub {
160             my $self = shift;
161             my $data = $self->get( '/comment' );
162             return $self->{comments} = [ #
163             map { #
164             $self->make_object( 'comment', { data => $_ } );
165             } @{ $data->{comments} }
166             ];
167             }
168             );
169              
170             __PACKAGE__->mk_lazy_ro_accessor(
171             'worklog',
172             sub {
173             my $self = shift;
174             return $self->make_object( 'worklog' );
175             }
176             );
177              
178             __PACKAGE__->mk_lazy_ro_accessor(
179             'transitions',
180             sub {
181             my $self = shift;
182             return $self->make_object( 'transitions' );
183             }
184             );
185              
186             __PACKAGE__->mk_lazy_ro_accessor(
187             'timetracking',
188             sub {
189             my $self = shift;
190             return $self->make_object( 'timetracking' );
191             }
192             );
193              
194             #pod =internal_method B<make_object>
195             #pod
196             #pod A pass-through method that calls
197             #pod L<JIRA::REST::Class::Factory::make_object()|JIRA::REST::Class::Factory/make_object>,
198             #pod but adds a weakened link to this issue in the object as well.
199             #pod
200             #pod =cut
201              
202             sub make_object {
203 0     0 1   my $self = shift;
204 0           my $type = shift;
205 0   0       my $args = shift || {};
206              
207             # if we weren't passed an issue, link to ourselves
208 0 0         unless ( exists $args->{issue} ) {
209 0           $args->{issue} = $self;
210             }
211              
212 0           my $class = $self->factory->get_factory_class( $type );
213 0           my $obj = $class->new( $args );
214              
215 0 0         if ( exists $obj->{issue} ) {
216 0           weaken $obj->{issue}; # make the link to ourselves weak
217             }
218              
219 0           $obj->init( $self->factory ); # NOW we call init
220              
221 0           return $obj;
222             }
223              
224             ###########################################################################
225              
226             #pod =method B<add_attachments>
227             #pod
228             #pod Accepts a list of filenames to be added to the issue as attachments.
229             #pod
230             #pod =cut
231              
232             sub add_attachments {
233 0     0 1   my ( $self, @args ) = @_;
234              
235 0           foreach my $file ( @args ) {
236 0 0         croak "unable to find attachment $file"
237             unless -f $file;
238              
239 0           $self->JIRA_REST->attach_files( $self->key, $file );
240             }
241 0           return;
242             }
243              
244             #pod =method B<add_attachment>
245             #pod
246             #pod Accepts a single filename to be added to the issue as an attachment.
247             #pod
248             #pod =cut
249              
250             sub add_attachment {
251 0     0 1   my $self = shift;
252 0           my $file = shift;
253              
254 0 0         croak "unable to find attachment $file"
255             unless -f $file;
256              
257 0           return $self->JIRA_REST->attach_files( $self->key, $file );
258             }
259              
260             #pod =method B<add_data_attachment>
261             #pod
262             #pod Accepts a fake filename and a scalar representing the contents of a file and
263             #pod adds it to the issue as an attachment.
264             #pod
265             #pod =cut
266              
267             sub add_data_attachment {
268 0     0 1   my $self = shift;
269 0           my $name = shift;
270 0           my $data = shift;
271 0           my $url = q{/} . join q{/}, 'issue', $self->key, 'attachments';
272              
273 0           return $self->jira->data_upload(
274             {
275             url => $url,
276             name => $name,
277             data => $data
278             }
279             );
280             }
281              
282             #pod =method B<add_comment>
283             #pod
284             #pod Adds whatever is passed in as a comment on the issue.
285             #pod
286             #pod =cut
287              
288             sub add_comment {
289 0     0 1   my $self = shift;
290 0           my $text = shift;
291 0           return $self->post( '/comment', { body => $text } );
292             }
293              
294             #pod =method B<add_label>
295             #pod
296             #pod Adds whatever is passed in as a label for the issue.
297             #pod
298             #pod =cut
299              
300             sub add_label {
301 0     0 1   my $self = shift;
302 0           my $label = shift;
303 0           return $self->update( labels => [ { add => $label } ] );
304             }
305              
306             #pod =method B<remove_label>
307             #pod
308             #pod Removes whatever is passed in from the labels for the issue.
309             #pod
310             #pod =cut
311              
312             sub remove_label {
313 0     0 1   my $self = shift;
314 0           my $label = shift;
315 0           return $self->update( labels => [ { remove => $label } ] );
316             }
317              
318             #pod =method B<has_label>
319             #pod
320             #pod Returns true if the issue has the specified label.
321             #pod
322             #pod =cut
323              
324             sub has_label {
325 0     0 1   my $self = shift;
326 0           my $label = shift;
327 0           foreach my $has ( $self->labels ) {
328 0 0         return 1 if $label eq $has;
329             }
330 0           return 0;
331             }
332              
333             #pod =method B<add_component>
334             #pod
335             #pod Adds whatever is passed in as a component for the issue.
336             #pod
337             #pod =cut
338              
339             sub add_component {
340 0     0 1   my $self = shift;
341 0           my $comp = shift;
342 0           return $self->update( components => [ { add => { name => $comp } } ] );
343             }
344              
345             #pod =method B<remove_component>
346             #pod
347             #pod Removes whatever is passed in from the components for the issue.
348             #pod
349             #pod =cut
350              
351             sub remove_component {
352 0     0 1   my $self = shift;
353 0           my $comp = shift;
354 0           return $self->update( components => [ { remove => { name => $comp } } ] );
355             }
356              
357             #pod =method B<set_assignee>
358             #pod
359             #pod Sets the assignee for the issue to be the user passed in. Can either be a
360             #pod string representing the name or a
361             #pod L<JIRA::REST::Class::User|JIRA::REST::Class::User> object.
362             #pod
363             #pod =cut
364              
365             sub set_assignee {
366 0     0 1   my ( $self, @args ) = @_;
367 0           my $name = $self->name_for_user( @args );
368 0           return $self->put_field( assignee => { name => $name } );
369             }
370              
371             #pod =method B<set_reporter>
372             #pod
373             #pod Sets the reporter for the issue to be the user passed in. Can either be a
374             #pod string representing the name or a
375             #pod L<JIRA::REST::Class::User|JIRA::REST::Class::User> object.
376             #pod
377             #pod =cut
378              
379             sub set_reporter {
380 0     0 1   my $self = shift;
381 0           my $name = $self->name_for_user( shift );
382 0           return $self->put_field( reporter => { name => $name } );
383             }
384              
385             #pod =method B<add_issue_link>
386             #pod
387             #pod Adds a link from this issue to another one. Accepts the link type (either a
388             #pod string representing the name or a
389             #pod L<JIRA::REST::Class::Issue::LinkType|JIRA::REST::Class::Issue::LinkType>),
390             #pod the issue to be linked to, and (optionally) the direction of the link
391             #pod (inward/outward). If the direction cannot be determined from the name of
392             #pod the link type, the default direction is 'inward';
393             #pod
394             #pod =cut
395              
396             sub add_issue_link {
397 0     0 1   my ( $self, $type, $key, $dir ) = @_;
398 0           $key = $self->key_for_issue( $key );
399 0           ( $type, $dir ) = $self->find_link_name_and_direction( $type, $dir );
400              
401 0           my $links = [
402             {
403             add => {
404             type => { name => $type },
405             $dir . 'Issue' => { key => $key },
406             },
407             }
408             ];
409 0           return $self->update( issuelinks => $links );
410             }
411              
412             #pod =method B<add_subtask>
413             #pod
414             #pod Adds a subtask to the current issue. Accepts a hashref with named parameters
415             #pod C<summary> and C<description>. If the parameter C<issuetype> is specified,
416             #pod then a subtask of the specified type is created. If no issuetype is
417             #pod specified, then the project is queried for valid subtask types, and, if there
418             #pod is only one, that type is used. If the project has more than one valid
419             #pod subtask type, an issuetype B<MUST> be specified.
420             #pod
421             #pod The remaining named parameters are passed to the create issue call as fields.
422             #pod
423             #pod =cut
424              
425             sub add_subtask {
426 0     0 1   my ( $self, @args ) = @_;
427              
428 0           my $fields;
429 0 0 0       if ( @args == 1 && ref $args[0] && ref $args[0] eq 'HASH' ) {
      0        
430 0           $fields = $args[0];
431             }
432             else { ## backward compatibility
433 0   0       $fields = $args[2] // {};
434              
435 0   0       $fields->{summary} //= shift @args;
436 0   0       $fields->{description} //= shift @args;
437             }
438              
439 0           my $project = $self->_get_subtask_project( $fields );
440 0           my $parent = $self->_get_subtask_parent( $fields );
441 0           my $issuetype = $self->_get_subtask_issue_type( $project, $fields );
442              
443 0           my $data = {
444             fields => {
445             project => { key => $project->key },
446             parent => { key => $parent->key },
447             issuetype => { id => $issuetype->id },
448             }
449             };
450              
451 0 0         if ( $fields ) {
452 0           foreach my $field ( keys %$fields ) {
453             next
454 0 0 0       if $field eq 'project'
      0        
455             || $field eq 'parent'
456             || $field eq 'issuetype';
457 0           $data->{fields}->{$field} = $fields->{$field};
458             }
459             }
460              
461 0           my $result = $self->jira->post( '/issue', $data );
462 0           my $url = '/issue/' . $result->{id};
463              
464 0           return $self->factory->make_object(
465             'issue',
466             {
467             data => $self->jira->get( $url )
468             }
469             );
470             }
471              
472             sub _get_subtask_project {
473 0     0     my ( $self, $fields ) = @_;
474              
475             return $self->project
476 0 0 0       unless exists $fields->{project} && defined $fields->{project};
477              
478 0           my $proj = $fields->{project};
479              
480 0 0         if ( $self->obj_isa( $proj, 'project' ) ) {
481              
482             # we were passed an issue type object
483 0           return $proj;
484             }
485              
486             my ( $project ) = grep { ##
487 0 0         $_->id eq $proj || $_->name eq $proj
  0            
488             } $self->jira->projects;
489              
490 0 0         return $project if $project;
491              
492 0           local $Carp::CarpLevel = $Carp::CarpLevel + 1;
493 0           confess "add_subtask() called with unknown project '$proj'";
494             }
495              
496             sub _get_subtask_parent {
497 0     0     my ( $self, $fields ) = @_;
498              
499             # if we're not passed a parent parameter, we're the parent
500             return $self
501 0 0 0       unless exists $fields->{parent} && defined $fields->{parent};
502              
503 0           my $issue = $fields->{parent};
504 0 0         if ( $self->obj_isa( $issue, 'issue' ) ) {
505              
506             # we were passed an issue type object
507 0           return $issue;
508             }
509              
510 0           my ( $parent ) = $self->jira->issues( $issue );
511              
512 0 0         return $parent if $parent;
513              
514 0           local $Carp::CarpLevel = $Carp::CarpLevel + 2;
515 0           confess "add_subtask() called with unknown parent '$issue'";
516             }
517              
518             sub _get_subtask_issue_type {
519 0     0     my ( $self, $project, $fields ) = @_;
520              
521             # let's set this once in case we need to throw an exception
522 0           local $Carp::CarpLevel = $Carp::CarpLevel + 2;
523              
524 0 0 0       if ( exists $fields->{issuetype} && defined $fields->{issuetype} ) {
525 0           my $type = $fields->{issuetype};
526              
527 0 0         if ( $self->obj_isa( $type, 'issuetype' ) ) {
528              
529             # we were passed an issue type object
530 0 0         return $type if $type->subtask;
531              
532 0           confess
533             "add_subtask() called with a non-subtask issue type: '$type'";
534             }
535              
536             my ( $issuetype ) = grep { ##
537 0 0         $_->id eq $type || $_->name eq $type
  0            
538             } $project->issueTypes;
539              
540 0 0 0       return $issuetype if $issuetype && $issuetype->subtask;
541              
542 0 0         if ( !defined $issuetype ) {
543 0           confess 'add_subtask() called with a value that does not '
544             . "correspond to a known issue type: '$type'";
545             }
546              
547             confess
548 0           "add_subtask() called with a non-subtask issue type: '$issuetype'";
549             }
550              
551             # we didn't get passed an issue type, so let's find one
552              
553 0           my @subtasks = $self->project->subtaskIssueTypes;
554              
555 0 0         if ( @subtasks == 1 ) {
556 0           return $subtasks[0];
557             }
558              
559 0           my $count = scalar @subtasks;
560 0           my $list = join q{, } => @subtasks;
561              
562 0           confess 'add_subtask() called without specifying a subtask type; '
563             . "there are $count subtask types: $list";
564             }
565              
566             ###########################################################################
567              
568             #pod =method B<update>
569             #pod
570             #pod Puts an update to JIRA. Accepts a hash of fields => values to be put.
571             #pod
572             #pod =cut
573              
574             sub update {
575 0     0 1   my $self = shift;
576 0           my $hash = {};
577 0           while ( @_ ) {
578 0           my $field = shift;
579 0           my $value = shift;
580 0           $hash->{$field} = $value;
581             }
582 0           return $self->put(
583             {
584             update => $hash,
585             }
586             );
587             }
588              
589             #pod =method B<put_field>
590             #pod
591             #pod Puts a value to a field. Accepts the field name and the value as parameters.
592             #pod
593             #pod =cut
594              
595             sub put_field {
596 0     0 1   my $self = shift;
597 0           my $field = shift;
598 0           my $value = shift;
599 0           return $self->put(
600             {
601             fields => { $field => $value },
602             }
603             );
604             }
605              
606             #pod =method B<reload>
607             #pod
608             #pod Reload the issue from the JIRA server.
609             #pod
610             #pod =cut
611              
612             sub reload {
613 0     0 1   my $self = shift;
614 0           $self->{data} = $self->get;
615 0           $self->init( $self->factory );
616 0           return;
617             }
618              
619             ###########################################################################
620              
621             #pod =internal_method B<get>
622             #pod
623             #pod Wrapper around C<JIRA::REST::Class>' L<get|JIRA::REST::Class/get> method that
624             #pod defaults to this issue's URL. Allows for extra parameters to be specified.
625             #pod
626             #pod =cut
627              
628             sub get {
629 0     0 1   my ( $self, $extra, @args ) = @_;
630 0   0       $extra //= q{};
631 0           return $self->jira->get( $self->url . $extra, @args );
632             }
633              
634             #pod =internal_method B<post>
635             #pod
636             #pod Wrapper around C<JIRA::REST::Class>' L<post|JIRA::REST::Class/post> method
637             #pod that defaults to this issue's URL. Allows for extra parameters to be
638             #pod specified.
639             #pod
640             #pod =cut
641              
642             sub post {
643 0     0 1   my ( $self, $extra, @args ) = @_;
644 0   0       $extra //= q{};
645 0           return $self->jira->post( $self->url . $extra, @args );
646             }
647              
648             #pod =internal_method B<put>
649             #pod
650             #pod Wrapper around C<JIRA::REST::Class>' L<put|JIRA::REST::Class/put> method that
651             #pod defaults to this issue's URL. Allows for extra parameters to be specified.
652             #pod
653             #pod =cut
654              
655             sub put {
656 0     0 1   my ( $self, @args ) = @_;
657 0           return $self->jira->put( $self->url, @args );
658             }
659              
660             #pod =internal_method B<delete>
661             #pod
662             #pod Wrapper around C<JIRA::REST::Class>' L<delete|JIRA::REST::Class/delete> method
663             #pod that defaults to this issue's URL. Allows for extra parameters to be
664             #pod specified.
665             #pod
666             #pod =cut
667              
668             sub delete { ## no critic (ProhibitBuiltinHomonyms)
669 0     0 1   my ( $self, $extra, @args ) = @_;
670 0   0       $extra //= q{};
671 0           return $self->jira->delete( $self->url . $extra, @args );
672             }
673              
674             #pod =method B<sprints>
675             #pod
676             #pod Generates a list of L<JIRA::REST::Class::Sprint|JIRA::REST::Class::Sprint>
677             #pod objects from the fields for an issue. Uses the field_name() accessor on the
678             #pod L<JIRA::REST::Class::Project|JIRA::REST::Class::Project/"field_name
679             #pod FIELD_KEY"> object to determine the name of the custom sprint
680             #pod field. Currently, this only really works if you're using L<Atlassian
681             #pod GreenHopper|https://www.atlassian.com/software/jira/agile>.
682             #pod
683             #pod =cut
684              
685             __PACKAGE__->mk_lazy_ro_accessor(
686             'sprints',
687             sub {
688             my $self = shift;
689              
690             # in my configuration, 'Sprint' is a custom field
691             my $sprint_field = $self->project->field_name( 'Sprint' );
692              
693             my @sprints;
694             foreach my $sprint ( @{ $self->fields->{$sprint_field} } ) {
695             push @sprints, $self->make_object( 'sprint', { data => $sprint } );
696             }
697             return \@sprints;
698             }
699             );
700              
701             #<<<
702              
703             #pod =method B<children>
704             #pod
705             #pod Returns a list of issue objects that are children of the issue. Currently
706             #pod requires the L<ScriptRunner
707             #pod plugin|https://marketplace.atlassian.com/plugins/com.onresolve.jira.groovy.groovyrunner/cloud/overview>.
708             #pod
709             #pod =cut
710              
711             #>>>
712              
713             sub children {
714 0     0 1   my $self = shift;
715 0           my $key = $self->key;
716 0           my $children = $self->jira->query(
717             {
718             jql => qq{issueFunction in subtasksOf("key = $key")}
719             }
720             );
721              
722 0 0         return unless $children->issue_count;
723 0           return $children->issues;
724             }
725              
726             ###########################################################################
727              
728             #pod =method B<start_progress>
729             #pod
730             #pod Moves the status of the issue to 'In Progress', regardless of what the current
731             #pod status is.
732             #pod
733             #pod =cut
734              
735             sub start_progress {
736 0     0 1   my $self = shift;
737 0           my $callback = shift;
738 0   0 0     $callback //= sub { };
739              
740 0           return $self->transitions->transition_walk(
741             'In Progress',
742             {
743             'Open' => 'In Progress',
744             'Reopened' => 'In Progress',
745             'In QA' => 'In Progress',
746             'Blocked' => 'In Progress',
747             'Resolved' => 'Reopened',
748             'Verified' => 'Reopened',
749             'Closed' => 'Reopened',
750             },
751             $callback
752             );
753             }
754              
755             #pod =method B<start_qa>
756             #pod
757             #pod Moves the status of the issue to 'In QA', regardless of what the current
758             #pod status is.
759             #pod
760             #pod =cut
761              
762             sub start_qa {
763 0     0 1   my $self = shift;
764 0           my $callback = shift;
765 0   0 0     $callback //= sub { };
766              
767 0           return $self->transitions->transition_walk(
768             'In QA',
769             {
770             'Open' => 'In Progress',
771             'In Progress' => 'Resolved',
772             'Reopened' => 'Resolved',
773             'Resolved' => 'In QA',
774             'Blocked' => 'In QA',
775             'Verified' => 'Reopened',
776             'Closed' => 'Reopened',
777             },
778             $callback
779             );
780             }
781              
782             #pod =method B<resolve>
783             #pod
784             #pod Moves the status of the issue to 'Resolved', regardless of what the current
785             #pod status is.
786             #pod
787             #pod =cut
788              
789             sub resolve {
790 0     0 1   my $self = shift;
791 0           my $callback = shift;
792 0   0 0     $callback //= sub { };
793              
794 0           return $self->transitions->transition_walk(
795             'Resolved',
796             {
797             'Open' => 'In Progress',
798             'In Progress' => 'Resolved',
799             'Reopened' => 'Resolved',
800             'Blocked' => 'In Progress',
801             'In QA' => 'In Progress',
802             'Verified' => 'Reopened',
803             'Closed' => 'Reopened',
804             },
805             $callback
806             );
807             }
808              
809             #pod =method B<open>
810             #pod
811             #pod Moves the status of the issue to 'Open', regardless of what the current status is.
812             #pod
813             #pod =cut
814              
815             sub open { ## no critic (ProhibitBuiltinHomonyms)
816 0     0 1   my $self = shift;
817 0           my $callback = shift;
818 0   0 0     $callback //= sub { };
819              
820 0           return $self->transitions->transition_walk(
821             'Open',
822             {
823             'In Progress' => 'Open',
824             'Reopened' => 'In Progress',
825             'Blocked' => 'In Progress',
826             'In QA' => 'In Progress',
827             'Resolved' => 'Reopened',
828             'Verified' => 'Reopened',
829             'Closed' => 'Reopened',
830             },
831             $callback
832             );
833             }
834              
835             #pod =method B<close>
836             #pod
837             #pod Moves the status of the issue to 'Closed', regardless of what the current status is.
838             #pod
839             #pod =cut
840              
841             sub close { ## no critic (ProhibitBuiltinHomonyms ProhibitAmbiguousNames)
842 0     0 1   my $self = shift;
843 0           my $callback = shift;
844 0   0 0     $callback //= sub { };
845              
846 0           return $self->transitions->transition_walk(
847             'Closed',
848             {
849             'Open' => 'In Progress',
850             'In Progress' => 'Resolved',
851             'Reopened' => 'Resolved',
852             'Blocked' => 'In Progress',
853             'In QA' => 'Verified',
854             'Resolved' => 'Closed',
855             'Verified' => 'Closed',
856             },
857             $callback
858             );
859             }
860              
861             1;
862              
863             #pod =accessor B<expand>
864             #pod
865             #pod A comma-separated list of fields in the issue that weren't expanded in the
866             #pod initial REST call.
867             #pod
868             #pod =accessor B<fields>
869             #pod
870             #pod Returns a reference to the fields hash for the issue.
871             #pod
872             #pod =accessor B<aggregateprogress>
873             #pod
874             #pod Returns the aggregate progress for the issue as a hash reference.
875             #pod
876             #pod TODO: Turn this into an object.
877             #pod
878             #pod =accessor B<aggregatetimeestimate>
879             #pod
880             #pod Returns the aggregate time estimate for the issue.
881             #pod
882             #pod TODO: Turn this into an object that can return either seconds or a w/d/h/m/s
883             #pod string.
884             #pod
885             #pod =accessor B<aggregatetimeoriginalestimate>
886             #pod
887             #pod Returns the aggregate time original estimate for the issue.
888             #pod
889             #pod TODO: Turn this into an object that can return either seconds or a w/d/h/m/s
890             #pod string.
891             #pod
892             #pod =accessor B<aggregatetimespent>
893             #pod
894             #pod Returns the aggregate time spent for the issue.
895             #pod
896             #pod TODO: Turn this into an object that can return either seconds or a w/d/h/m/s
897             #pod string.
898             #pod
899             #pod =accessor B<assignee>
900             #pod
901             #pod Returns the issue's assignee as a
902             #pod L<JIRA::REST::Class::User|JIRA::REST::Class::User> object.
903             #pod
904             #pod =accessor B<changelog>
905             #pod
906             #pod Returns the issue's change log as a
907             #pod L<JIRA::REST::Class::Issue::Changelog|JIRA::REST::Class::Issue::Changelog>
908             #pod object.
909             #pod
910             #pod =accessor B<comments>
911             #pod
912             #pod Returns a list of the issue's comments as
913             #pod L<JIRA::REST::Class::Issue::Comment|JIRA::REST::Class::Issue::Comment>
914             #pod objects. If called in a scalar context, returns an array reference to the
915             #pod list, not the number of elements in the list.
916             #pod
917             #pod =accessor B<components>
918             #pod
919             #pod Returns a list of the issue's components as
920             #pod L<JIRA::REST::Class::Project::Component|JIRA::REST::Class::Project::Component>
921             #pod objects. If called in a scalar context, returns an array reference to the
922             #pod list, not the number of elements in the list.
923             #pod
924             #pod =accessor B<component_count>
925             #pod
926             #pod Returns a count of the issue's components.
927             #pod
928             #pod =accessor B<created>
929             #pod
930             #pod Returns the issue's creation date as a L<DateTime|DateTime> object.
931             #pod
932             #pod =accessor B<creator>
933             #pod
934             #pod Returns the issue's assignee as a
935             #pod L<JIRA::REST::Class::User|JIRA::REST::Class::User> object.
936             #pod
937             #pod =accessor B<description>
938             #pod
939             #pod Returns the description of the issue.
940             #pod
941             #pod =accessor B<duedate>
942             #pod
943             #pod Returns the issue's due date as a L<DateTime|DateTime> object.
944             #pod
945             #pod =accessor B<environment>
946             #pod
947             #pod Returns the issue's environment as a hash reference.
948             #pod
949             #pod TODO: Turn this into an object.
950             #pod
951             #pod =accessor B<fixVersions>
952             #pod
953             #pod Returns a list of the issue's fixVersions.
954             #pod
955             #pod TODO: Turn this into a list of objects.
956             #pod
957             #pod =accessor B<issuelinks>
958             #pod
959             #pod Returns a list of the issue's links.
960             #pod
961             #pod TODO: Turn this into a list of objects.
962             #pod
963             #pod =accessor B<issuetype>
964             #pod
965             #pod Returns the issue type as a
966             #pod L<JIRA::REST::Class::Issue::Type|JIRA::REST::Class::Issue::Type> object.
967             #pod
968             #pod =accessor B<labels>
969             #pod
970             #pod Returns the issue's labels as an array reference.
971             #pod
972             #pod =accessor B<lastViewed>
973             #pod
974             #pod Returns the issue's last view date as a L<DateTime|DateTime> object.
975             #pod
976             #pod =accessor B<parent>
977             #pod
978             #pod Returns the issue's parent as a
979             #pod L<JIRA::REST::Class::Issue|JIRA::REST::Class::Issue> object.
980             #pod
981             #pod =accessor B<has_parent>
982             #pod
983             #pod Returns a boolean indicating whether the issue has a parent.
984             #pod
985             #pod =accessor B<priority>
986             #pod
987             #pod Returns the issue's priority as a hash reference.
988             #pod
989             #pod TODO: Turn this into an object.
990             #pod
991             #pod =accessor B<progress>
992             #pod
993             #pod Returns the issue's progress as a hash reference.
994             #pod
995             #pod TODO: Turn this into an object.
996             #pod
997             #pod =accessor B<project>
998             #pod
999             #pod Returns the issue's project as a
1000             #pod L<JIRA::REST::Class::Project|JIRA::REST::Class::Project> object.
1001             #pod
1002             #pod =accessor B<reporter>
1003             #pod
1004             #pod Returns the issue's reporter as a
1005             #pod L<JIRA::REST::Class::User|JIRA::REST::Class::User> object.
1006             #pod
1007             #pod =accessor B<resolution>
1008             #pod
1009             #pod Returns the issue's resolution.
1010             #pod
1011             #pod TODO: Turn this into an object.
1012             #pod
1013             #pod =accessor B<resolutiondate>
1014             #pod
1015             #pod Returns the issue's resolution date as a L<DateTime|DateTime> object.
1016             #pod
1017             #pod =accessor B<status>
1018             #pod
1019             #pod Returns the issue's status as a
1020             #pod L<JIRA::REST::Class::Issue::Status|JIRA::REST::Class::Issue::Status> object.
1021             #pod
1022             #pod =accessor B<summary>
1023             #pod
1024             #pod Returns the summary of the issue.
1025             #pod
1026             #pod =accessor B<timeestimate>
1027             #pod
1028             #pod Returns the time estimate for the issue.
1029             #pod
1030             #pod TODO: Turn this into an object that can return either seconds or a w/d/h/m/s string.
1031             #pod
1032             #pod =accessor B<timeoriginalestimate>
1033             #pod
1034             #pod Returns the time original estimate for the issue.
1035             #pod
1036             #pod TODO: Turn this into an object that can return either seconds or a w/d/h/m/s string.
1037             #pod
1038             #pod =accessor B<timespent>
1039             #pod
1040             #pod Returns the time spent for the issue.
1041             #pod
1042             #pod TODO: Turn this into an object that can return either seconds or a w/d/h/m/s string.
1043             #pod
1044             #pod =accessor B<timetracking>
1045             #pod
1046             #pod Returns the time tracking of the issue as a
1047             #pod L<JIRA::REST::Class::Issue::TimeTracking|JIRA::REST::Class::Issue::TimeTracking>
1048             #pod object.
1049             #pod
1050             #pod =accessor B<transitions>
1051             #pod
1052             #pod Returns the valid transitions for the issue as a
1053             #pod L<JIRA::REST::Class::Issue::Transitions|JIRA::REST::Class::Issue::Transitions>
1054             #pod object.
1055             #pod
1056             #pod =accessor B<updated>
1057             #pod
1058             #pod Returns the issue's updated date as a L<DateTime|DateTime> object.
1059             #pod
1060             #pod =accessor B<versions>
1061             #pod
1062             #pod versions
1063             #pod
1064             #pod =accessor B<votes>
1065             #pod
1066             #pod votes
1067             #pod
1068             #pod =accessor B<watches>
1069             #pod
1070             #pod watches
1071             #pod
1072             #pod =accessor B<worklog>
1073             #pod
1074             #pod Returns the issue's change log as a
1075             #pod L<JIRA::REST::Class::Worklog|JIRA::REST::Class::Worklog> object.
1076             #pod
1077             #pod =accessor B<workratio>
1078             #pod
1079             #pod workratio
1080             #pod
1081             #pod =accessor B<id>
1082             #pod
1083             #pod Returns the issue ID.
1084             #pod
1085             #pod =accessor B<key>
1086             #pod
1087             #pod Returns the issue key.
1088             #pod
1089             #pod =accessor B<self>
1090             #pod
1091             #pod Returns the JIRA REST API's full URL for this issue.
1092             #pod
1093             #pod =accessor B<url>
1094             #pod
1095             #pod Returns the JIRA REST API's URL for this issue in a form used by
1096             #pod L<JIRA::REST::Class|JIRA::REST::Class>.
1097             #pod
1098             #pod =for stopwords aggregatetimespent
1099             #pod
1100             #pod =cut
1101              
1102             __END__
1103              
1104             =pod
1105              
1106             =encoding UTF-8
1107              
1108             =for :stopwords Packy Anderson Alexandr Alexey Ciornii Heumann Manni Melezhik
1109             aggregatetimespent Atlassian GreenHopper JRC ScriptRunner TODO
1110             aggregateprogress aggregatetimeestimate aggregatetimeoriginalestimate
1111             assigneeType avatar avatarUrls completeDate displayName duedate
1112             emailAddress endDate fieldtype fixVersions fromString genericized iconUrl
1113             isAssigneeTypeValid issueTypes issuekeys issuelinks issuetype jira jql
1114             lastViewed maxResults originalEstimate originalEstimateSeconds parentkey
1115             projectId rapidViewId remainingEstimate remainingEstimateSeconds
1116             resolutiondate sprintlist startDate subtaskIssueTypes timeSpent
1117             timeSpentSeconds timeestimate timeoriginalestimate timespent timetracking
1118             toString updateAuthor worklog workratio
1119              
1120             =head1 NAME
1121              
1122             JIRA::REST::Class::Issue - A helper class for L<JIRA::REST::Class|JIRA::REST::Class> that represents an
1123              
1124             =head1 VERSION
1125              
1126             version 0.12
1127              
1128             =head1 DESCRIPTION
1129              
1130             This object represents a JIRA issue as an object. It is overloaded so it
1131             returns the C<key> of the issue when stringified, and the C<id> of the issue
1132             when it is used in a numeric context. If two of these objects are compared
1133             I<as strings>, the C<key> of the issues will be used for the comparison,
1134             while numeric comparison will compare the C<id>s of the issues.
1135              
1136             =head1 METHODS
1137              
1138             =head2 B<add_attachments>
1139              
1140             Accepts a list of filenames to be added to the issue as attachments.
1141              
1142             =head2 B<add_attachment>
1143              
1144             Accepts a single filename to be added to the issue as an attachment.
1145              
1146             =head2 B<add_data_attachment>
1147              
1148             Accepts a fake filename and a scalar representing the contents of a file and
1149             adds it to the issue as an attachment.
1150              
1151             =head2 B<add_comment>
1152              
1153             Adds whatever is passed in as a comment on the issue.
1154              
1155             =head2 B<add_label>
1156              
1157             Adds whatever is passed in as a label for the issue.
1158              
1159             =head2 B<remove_label>
1160              
1161             Removes whatever is passed in from the labels for the issue.
1162              
1163             =head2 B<has_label>
1164              
1165             Returns true if the issue has the specified label.
1166              
1167             =head2 B<add_component>
1168              
1169             Adds whatever is passed in as a component for the issue.
1170              
1171             =head2 B<remove_component>
1172              
1173             Removes whatever is passed in from the components for the issue.
1174              
1175             =head2 B<set_assignee>
1176              
1177             Sets the assignee for the issue to be the user passed in. Can either be a
1178             string representing the name or a
1179             L<JIRA::REST::Class::User|JIRA::REST::Class::User> object.
1180              
1181             =head2 B<set_reporter>
1182              
1183             Sets the reporter for the issue to be the user passed in. Can either be a
1184             string representing the name or a
1185             L<JIRA::REST::Class::User|JIRA::REST::Class::User> object.
1186              
1187             =head2 B<add_issue_link>
1188              
1189             Adds a link from this issue to another one. Accepts the link type (either a
1190             string representing the name or a
1191             L<JIRA::REST::Class::Issue::LinkType|JIRA::REST::Class::Issue::LinkType>),
1192             the issue to be linked to, and (optionally) the direction of the link
1193             (inward/outward). If the direction cannot be determined from the name of
1194             the link type, the default direction is 'inward';
1195              
1196             =head2 B<add_subtask>
1197              
1198             Adds a subtask to the current issue. Accepts a hashref with named parameters
1199             C<summary> and C<description>. If the parameter C<issuetype> is specified,
1200             then a subtask of the specified type is created. If no issuetype is
1201             specified, then the project is queried for valid subtask types, and, if there
1202             is only one, that type is used. If the project has more than one valid
1203             subtask type, an issuetype B<MUST> be specified.
1204              
1205             The remaining named parameters are passed to the create issue call as fields.
1206              
1207             =head2 B<update>
1208              
1209             Puts an update to JIRA. Accepts a hash of fields => values to be put.
1210              
1211             =head2 B<put_field>
1212              
1213             Puts a value to a field. Accepts the field name and the value as parameters.
1214              
1215             =head2 B<reload>
1216              
1217             Reload the issue from the JIRA server.
1218              
1219             =head2 B<sprints>
1220              
1221             Generates a list of L<JIRA::REST::Class::Sprint|JIRA::REST::Class::Sprint>
1222             objects from the fields for an issue. Uses the field_name() accessor on the
1223             L<JIRA::REST::Class::Project|JIRA::REST::Class::Project/"field_name
1224             FIELD_KEY"> object to determine the name of the custom sprint
1225             field. Currently, this only really works if you're using L<Atlassian
1226             GreenHopper|https://www.atlassian.com/software/jira/agile>.
1227              
1228             =head2 B<children>
1229              
1230             Returns a list of issue objects that are children of the issue. Currently
1231             requires the L<ScriptRunner
1232             plugin|https://marketplace.atlassian.com/plugins/com.onresolve.jira.groovy.groovyrunner/cloud/overview>.
1233              
1234             =head2 B<start_progress>
1235              
1236             Moves the status of the issue to 'In Progress', regardless of what the current
1237             status is.
1238              
1239             =head2 B<start_qa>
1240              
1241             Moves the status of the issue to 'In QA', regardless of what the current
1242             status is.
1243              
1244             =head2 B<resolve>
1245              
1246             Moves the status of the issue to 'Resolved', regardless of what the current
1247             status is.
1248              
1249             =head2 B<open>
1250              
1251             Moves the status of the issue to 'Open', regardless of what the current status is.
1252              
1253             =head2 B<close>
1254              
1255             Moves the status of the issue to 'Closed', regardless of what the current status is.
1256              
1257             =head1 READ-ONLY ACCESSORS
1258              
1259             =head2 B<expand>
1260              
1261             A comma-separated list of fields in the issue that weren't expanded in the
1262             initial REST call.
1263              
1264             =head2 B<fields>
1265              
1266             Returns a reference to the fields hash for the issue.
1267              
1268             =head2 B<aggregateprogress>
1269              
1270             Returns the aggregate progress for the issue as a hash reference.
1271              
1272             TODO: Turn this into an object.
1273              
1274             =head2 B<aggregatetimeestimate>
1275              
1276             Returns the aggregate time estimate for the issue.
1277              
1278             TODO: Turn this into an object that can return either seconds or a w/d/h/m/s
1279             string.
1280              
1281             =head2 B<aggregatetimeoriginalestimate>
1282              
1283             Returns the aggregate time original estimate for the issue.
1284              
1285             TODO: Turn this into an object that can return either seconds or a w/d/h/m/s
1286             string.
1287              
1288             =head2 B<aggregatetimespent>
1289              
1290             Returns the aggregate time spent for the issue.
1291              
1292             TODO: Turn this into an object that can return either seconds or a w/d/h/m/s
1293             string.
1294              
1295             =head2 B<assignee>
1296              
1297             Returns the issue's assignee as a
1298             L<JIRA::REST::Class::User|JIRA::REST::Class::User> object.
1299              
1300             =head2 B<changelog>
1301              
1302             Returns the issue's change log as a
1303             L<JIRA::REST::Class::Issue::Changelog|JIRA::REST::Class::Issue::Changelog>
1304             object.
1305              
1306             =head2 B<comments>
1307              
1308             Returns a list of the issue's comments as
1309             L<JIRA::REST::Class::Issue::Comment|JIRA::REST::Class::Issue::Comment>
1310             objects. If called in a scalar context, returns an array reference to the
1311             list, not the number of elements in the list.
1312              
1313             =head2 B<components>
1314              
1315             Returns a list of the issue's components as
1316             L<JIRA::REST::Class::Project::Component|JIRA::REST::Class::Project::Component>
1317             objects. If called in a scalar context, returns an array reference to the
1318             list, not the number of elements in the list.
1319              
1320             =head2 B<component_count>
1321              
1322             Returns a count of the issue's components.
1323              
1324             =head2 B<created>
1325              
1326             Returns the issue's creation date as a L<DateTime|DateTime> object.
1327              
1328             =head2 B<creator>
1329              
1330             Returns the issue's assignee as a
1331             L<JIRA::REST::Class::User|JIRA::REST::Class::User> object.
1332              
1333             =head2 B<description>
1334              
1335             Returns the description of the issue.
1336              
1337             =head2 B<duedate>
1338              
1339             Returns the issue's due date as a L<DateTime|DateTime> object.
1340              
1341             =head2 B<environment>
1342              
1343             Returns the issue's environment as a hash reference.
1344              
1345             TODO: Turn this into an object.
1346              
1347             =head2 B<fixVersions>
1348              
1349             Returns a list of the issue's fixVersions.
1350              
1351             TODO: Turn this into a list of objects.
1352              
1353             =head2 B<issuelinks>
1354              
1355             Returns a list of the issue's links.
1356              
1357             TODO: Turn this into a list of objects.
1358              
1359             =head2 B<issuetype>
1360              
1361             Returns the issue type as a
1362             L<JIRA::REST::Class::Issue::Type|JIRA::REST::Class::Issue::Type> object.
1363              
1364             =head2 B<labels>
1365              
1366             Returns the issue's labels as an array reference.
1367              
1368             =head2 B<lastViewed>
1369              
1370             Returns the issue's last view date as a L<DateTime|DateTime> object.
1371              
1372             =head2 B<parent>
1373              
1374             Returns the issue's parent as a
1375             L<JIRA::REST::Class::Issue|JIRA::REST::Class::Issue> object.
1376              
1377             =head2 B<has_parent>
1378              
1379             Returns a boolean indicating whether the issue has a parent.
1380              
1381             =head2 B<priority>
1382              
1383             Returns the issue's priority as a hash reference.
1384              
1385             TODO: Turn this into an object.
1386              
1387             =head2 B<progress>
1388              
1389             Returns the issue's progress as a hash reference.
1390              
1391             TODO: Turn this into an object.
1392              
1393             =head2 B<project>
1394              
1395             Returns the issue's project as a
1396             L<JIRA::REST::Class::Project|JIRA::REST::Class::Project> object.
1397              
1398             =head2 B<reporter>
1399              
1400             Returns the issue's reporter as a
1401             L<JIRA::REST::Class::User|JIRA::REST::Class::User> object.
1402              
1403             =head2 B<resolution>
1404              
1405             Returns the issue's resolution.
1406              
1407             TODO: Turn this into an object.
1408              
1409             =head2 B<resolutiondate>
1410              
1411             Returns the issue's resolution date as a L<DateTime|DateTime> object.
1412              
1413             =head2 B<status>
1414              
1415             Returns the issue's status as a
1416             L<JIRA::REST::Class::Issue::Status|JIRA::REST::Class::Issue::Status> object.
1417              
1418             =head2 B<summary>
1419              
1420             Returns the summary of the issue.
1421              
1422             =head2 B<timeestimate>
1423              
1424             Returns the time estimate for the issue.
1425              
1426             TODO: Turn this into an object that can return either seconds or a w/d/h/m/s string.
1427              
1428             =head2 B<timeoriginalestimate>
1429              
1430             Returns the time original estimate for the issue.
1431              
1432             TODO: Turn this into an object that can return either seconds or a w/d/h/m/s string.
1433              
1434             =head2 B<timespent>
1435              
1436             Returns the time spent for the issue.
1437              
1438             TODO: Turn this into an object that can return either seconds or a w/d/h/m/s string.
1439              
1440             =head2 B<timetracking>
1441              
1442             Returns the time tracking of the issue as a
1443             L<JIRA::REST::Class::Issue::TimeTracking|JIRA::REST::Class::Issue::TimeTracking>
1444             object.
1445              
1446             =head2 B<transitions>
1447              
1448             Returns the valid transitions for the issue as a
1449             L<JIRA::REST::Class::Issue::Transitions|JIRA::REST::Class::Issue::Transitions>
1450             object.
1451              
1452             =head2 B<updated>
1453              
1454             Returns the issue's updated date as a L<DateTime|DateTime> object.
1455              
1456             =head2 B<versions>
1457              
1458             versions
1459              
1460             =head2 B<votes>
1461              
1462             votes
1463              
1464             =head2 B<watches>
1465              
1466             watches
1467              
1468             =head2 B<worklog>
1469              
1470             Returns the issue's change log as a
1471             L<JIRA::REST::Class::Worklog|JIRA::REST::Class::Worklog> object.
1472              
1473             =head2 B<workratio>
1474              
1475             workratio
1476              
1477             =head2 B<id>
1478              
1479             Returns the issue ID.
1480              
1481             =head2 B<key>
1482              
1483             Returns the issue key.
1484              
1485             =head2 B<self>
1486              
1487             Returns the JIRA REST API's full URL for this issue.
1488              
1489             =head2 B<url>
1490              
1491             Returns the JIRA REST API's URL for this issue in a form used by
1492             L<JIRA::REST::Class|JIRA::REST::Class>.
1493              
1494             =head1 INTERNAL METHODS
1495              
1496             =head2 B<make_object>
1497              
1498             A pass-through method that calls
1499             L<JIRA::REST::Class::Factory::make_object()|JIRA::REST::Class::Factory/make_object>,
1500             but adds a weakened link to this issue in the object as well.
1501              
1502             =head2 B<get>
1503              
1504             Wrapper around C<JIRA::REST::Class>' L<get|JIRA::REST::Class/get> method that
1505             defaults to this issue's URL. Allows for extra parameters to be specified.
1506              
1507             =head2 B<post>
1508              
1509             Wrapper around C<JIRA::REST::Class>' L<post|JIRA::REST::Class/post> method
1510             that defaults to this issue's URL. Allows for extra parameters to be
1511             specified.
1512              
1513             =head2 B<put>
1514              
1515             Wrapper around C<JIRA::REST::Class>' L<put|JIRA::REST::Class/put> method that
1516             defaults to this issue's URL. Allows for extra parameters to be specified.
1517              
1518             =head2 B<delete>
1519              
1520             Wrapper around C<JIRA::REST::Class>' L<delete|JIRA::REST::Class/delete> method
1521             that defaults to this issue's URL. Allows for extra parameters to be
1522             specified.
1523              
1524             =head1 RELATED CLASSES
1525              
1526             =over 2
1527              
1528             =item * L<JIRA::REST::Class|JIRA::REST::Class>
1529              
1530             =item * L<JIRA::REST::Class::Abstract|JIRA::REST::Class::Abstract>
1531              
1532             =item * L<JIRA::REST::Class::Factory|JIRA::REST::Class::Factory>
1533              
1534             =item * L<JIRA::REST::Class::Factory::make_object|JIRA::REST::Class::Factory::make_object>
1535              
1536             =item * L<JIRA::REST::Class::Issue::Changelog|JIRA::REST::Class::Issue::Changelog>
1537              
1538             =item * L<JIRA::REST::Class::Issue::Comment|JIRA::REST::Class::Issue::Comment>
1539              
1540             =item * L<JIRA::REST::Class::Issue::LinkType|JIRA::REST::Class::Issue::LinkType>
1541              
1542             =item * L<JIRA::REST::Class::Issue::Status|JIRA::REST::Class::Issue::Status>
1543              
1544             =item * L<JIRA::REST::Class::Issue::TimeTracking|JIRA::REST::Class::Issue::TimeTracking>
1545              
1546             =item * L<JIRA::REST::Class::Issue::Transitions|JIRA::REST::Class::Issue::Transitions>
1547              
1548             =item * L<JIRA::REST::Class::Issue::Type|JIRA::REST::Class::Issue::Type>
1549              
1550             =item * L<JIRA::REST::Class::Project|JIRA::REST::Class::Project>
1551              
1552             =item * L<JIRA::REST::Class::Project::Component|JIRA::REST::Class::Project::Component>
1553              
1554             =item * L<JIRA::REST::Class::Sprint|JIRA::REST::Class::Sprint>
1555              
1556             =item * L<JIRA::REST::Class::User|JIRA::REST::Class::User>
1557              
1558             =item * L<JIRA::REST::Class::Worklog|JIRA::REST::Class::Worklog>
1559              
1560             =back
1561              
1562             =head1 AUTHOR
1563              
1564             Packy Anderson <packy@cpan.org>
1565              
1566             =head1 COPYRIGHT AND LICENSE
1567              
1568             This software is Copyright (c) 2017 by Packy Anderson.
1569              
1570             This is free software, licensed under:
1571              
1572             The Artistic License 2.0 (GPL Compatible)
1573              
1574             =cut