File Coverage

lib/JIRA/REST/Class/Project.pm
Criterion Covered Total %
statement 51 99 51.5
branch 2 20 10.0
condition n/a
subroutine 11 24 45.8
pod 10 10 100.0
total 74 153 48.3


line stmt bran cond sub pod time code
1             package JIRA::REST::Class::Project;
2 4     4   1915 use parent qw( JIRA::REST::Class::Abstract );
  4         4  
  4         16  
3 4     4   214 use strict;
  4         5  
  4         57  
4 4     4   11 use warnings;
  4         4  
  4         76  
5 4     4   58 use 5.010;
  4         9  
6              
7             our $VERSION = '0.10';
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 a JIRA project as an object.
13              
14 4     4   12 use Readonly 2.04;
  4         40  
  4         3384  
15              
16             Readonly my @DATA => qw( avatarUrls expand id key name projectTypeKey self );
17              
18             __PACKAGE__->mk_data_ro_accessors( @DATA );
19              
20             Readonly my @LAZY => qw( category assigneeType components description
21             issueTypes subtaskIssueTypes lead roles versions );
22              
23             for my $field ( @LAZY ) {
24             __PACKAGE__->mk_lazy_ro_accessor(
25             $field,
26             sub {
27             my $self = shift;
28             $self->_do_lazy_load( @_ );
29             $self->{$field};
30             }
31             );
32             }
33              
34             #
35             # I'm putting this here as a reminder to myself and as an explanation to
36             # anyone who wonders why I'm lazily loading so much information. There are
37             # two ways this object can be instantiated: either as a request for all the
38             # information on a particular project, or as an accessor object wrapping
39             # the data returned by the GET /rest/api/latest/project API call, which
40             # only returns the following attributes: "self", "id", "key", "name",
41             # "avatarUrls", and "projectCategory". I didn't want to incur a REST API
42             # call for each of these objects if all they were being used for is
43             # accessing the data in the project list, so I made the accessors for
44             # information that wasn't in that list be lazily loaded. If the end user
45             # gets an object from the project list method and asks for information that
46             # only comes from making the full /rest/api/latest/project/{projectIdOrKey}
47             # API call, we make the call at that time.
48             #
49              
50             sub _do_lazy_load {
51 2     2   4 my ( $self, @fields ) = @_;
52              
53 2         7 ( my $url = $self->self ) =~ s{.*/project}{/project}x;
54 2         7 my $data = $self->jira->get( $url );
55              
56 2         9484 $self->{assigneeType} = $data->{assigneeType};
57              
58             my $make_component = sub {
59 0     0   0 my $comp = shift;
60 0         0 return $self->make_object( 'projectcomp', { data => $comp } );
61 2         8 };
62              
63             $self->{components} = [ ##
64 2         5 map { $make_component->( $_ ) } @{ $data->{components} }
  0         0  
  2         6  
65             ];
66              
67 2         5 $self->{description} = $data->{description};
68              
69             my $make_issue_type = sub {
70 10     10   9 my $type = shift;
71 10         39 return $self->make_object( 'issuetype', { data => $type } );
72 2         7 };
73              
74             $self->{issueTypes} = [ ##
75 2         3 map { $make_issue_type->( $_ ) } @{ $data->{issueTypes} }
  10         15  
  2         4  
76             ];
77              
78             $self->{subtaskIssueTypes} = [ ##
79 2         62 grep { $_->subtask } @{ $self->{issueTypes} }
  10         31  
  2         6  
80             ];
81              
82 2         23 $self->{lead} = $self->make_object( 'user', { data => $data->{lead} } );
83              
84 2         24 $self->{roles} = $data->{roles};
85              
86             my $make_project_version = sub {
87 6     6   3 my $pv = shift;
88 6         16 my $vers = $self->make_object( 'projectvers', { data => $pv } );
89             $self->{version_hash}->{ $vers->id }
90 6         25 = $self->{version_hash}->{ $vers->name } = $vers;
91 6         15 return $vers;
92 2         9 };
93              
94             $self->{versions} = [ ##
95 2         4 map { $make_project_version->( $_ ) } @{ $data->{versions} }
  6         10  
  2         5  
96             ];
97              
98 2         43 foreach my $field ( @fields ) {
99 0         0 $self->{lazy_loaded}->{$field} = 1;
100             }
101              
102 2         23 return;
103             }
104              
105             #pod =accessor B<assigneeType>
106             #pod
107             #pod This accessor returns the assignee type of the project.
108             #pod
109             #pod =accessor B<avatarUrls>
110             #pod
111             #pod A hashref of the different sizes available for the project's avatar.
112             #pod
113             #pod =accessor B<components>
114             #pod
115             #pod A list of the components for the project as
116             #pod L<JIRA::REST::Class::Project::Component|JIRA::REST::Class::Project::Component>
117             #pod objects.
118             #pod
119             #pod =accessor B<description>
120             #pod
121             #pod Returns the description of the project.
122             #pod
123             #pod =accessor B<expand>
124             #pod
125             #pod A comma-separated list of fields in the project that weren't expanded in the initial REST call.
126             #pod
127             #pod =accessor B<id>
128             #pod
129             #pod Returns the id of the project.
130             #pod
131             #pod =accessor B<issueTypes>
132             #pod
133             #pod A list of valid issue types for the project as
134             #pod L<JIRA::REST::Class::Issue::Type|JIRA::REST::Class::Issue::Type> objects.
135             #pod
136             #pod =accessor B<subtaskIssueTypes>
137             #pod
138             #pod Taking a page from the old SOAP interface, this accessor returns a list of
139             #pod all issue types (as
140             #pod L<JIRA::REST::Class::Issue::Type|JIRA::REST::Class::Issue::Type> objects)
141             #pod whose subtask field is true.
142             #pod
143             #pod =accessor B<key>
144             #pod
145             #pod Returns the key of the project.
146             #pod
147             #pod =accessor B<lead>
148             #pod
149             #pod Returns the project lead as a
150             #pod L<JIRA::REST::Class::User|JIRA::REST::Class::User> object.
151             #pod
152             #pod =accessor B<name>
153             #pod
154             #pod Returns the name of the project.
155             #pod
156             #pod =accessor B<category>
157             #pod
158             #pod Returns a hashref of the category of the project as
159             #pod L<JIRA::REST::Class::Project::Category|JIRA::REST::Class::Project::Category>
160             #pod objects.
161             #pod
162             #pod =accessor B<self>
163             #pod
164             #pod Returns the JIRA REST API URL of the project.
165             #pod
166             #pod =accessor B<versions>
167             #pod
168             #pod Returns a list of the versions of the project as
169             #pod L<JIRA::REST::Class::Project::Version|JIRA::REST::Class::Project::Version>
170             #pod objects.
171             #pod
172             #pod =accessor B<metadata>
173             #pod
174             #pod Returns the metadata associated with this project.
175             #pod
176             #pod =cut
177              
178             sub metadata {
179 0     0 1   my $self = shift;
180              
181 0 0         unless ( defined $self->{metadata} ) {
182 0           my ( $first_issue ) = $self->jira->issues(
183             {
184             jql => 'project = ' . $self->key,
185             maxResults => 1,
186             }
187             );
188 0           my $issuekey = $first_issue->key;
189 0           $self->{metadata} = $self->jira->get( "/issue/$issuekey/editmeta" );
190             }
191              
192 0           return $self->{metadata};
193             }
194              
195             #pod =accessor B<allowed_components>
196             #pod
197             #pod Returns a list of the allowed values for the 'components' field in the project.
198             #pod
199             #pod =cut
200              
201             sub allowed_components {
202 0     0 1   return shift->allowed_field_values( 'components', @_ );
203             }
204              
205             #pod =accessor B<allowed_versions>
206             #pod
207             #pod Returns a list of the allowed values for the 'versions' field in the project.
208             #pod
209             #pod =cut
210              
211 0     0 1   sub allowed_versions { return shift->allowed_field_values( 'versions', @_ ); }
212              
213             #pod =accessor B<allowed_fix_versions>
214             #pod
215             #pod Returns a list of the allowed values for the 'fixVersions' field in the project.
216             #pod
217             #pod =cut
218              
219             sub allowed_fix_versions {
220 0     0 1   return shift->allowed_field_values( 'fixVersions', @_ );
221             }
222              
223             #pod =accessor B<allowed_issue_types>
224             #pod
225             #pod Returns a list of the allowed values for the 'issuetype' field in the project.
226             #pod
227             #pod =cut
228              
229             sub allowed_issue_types {
230 0     0 1   return shift->allowed_field_values( 'issuetype', @_ );
231             }
232              
233             #pod =accessor B<allowed_priorities>
234             #pod
235             #pod Returns a list of the allowed values for the 'priority' field in the project.
236             #pod
237             #pod =cut
238              
239             sub allowed_priorities {
240 0     0 1   return shift->allowed_field_values( 'priority', @_ );
241             }
242              
243             #pod =internal_method B<allowed_field_values> FIELD_NAME
244             #pod
245             #pod Returns a list of allowable values for the specified field in the project.
246             #pod
247             #pod =cut
248              
249             sub allowed_field_values {
250 0     0 1   my $self = shift;
251 0           my $name = shift;
252              
253 0           my @list = map { $_->{name} }
254 0           @{ $self->field_metadata( $name )->{allowedValues} };
  0            
255              
256 0           return @list;
257             }
258              
259             #pod =internal_method B<field_metadata_exists> FIELD_NAME
260             #pod
261             #pod Boolean indicating whether there is metadata for a given field in the project. Read-only.
262             #pod
263             #pod =cut
264              
265             sub field_metadata_exists {
266 0     0 1   my $self = shift;
267 0           my $name = shift;
268 0           my $fields = $self->metadata->{fields};
269 0 0         return 1 if exists $fields->{$name};
270 0           my $name2 = $self->field_name( $name );
271 0 0         return ( exists $fields->{$name2} ? 1 : 0 );
272             }
273              
274             #pod =internal_method B<field_metadata> FIELD_NAME
275             #pod
276             #pod Looks for metadata under either a field's key or name in the project. Read-only.
277             #pod
278             #pod =cut
279              
280             sub field_metadata {
281 0     0 1   my $self = shift;
282 0           my $name = shift;
283 0           my $fields = $self->metadata->{fields};
284 0 0         if ( exists $fields->{$name} ) {
285 0           return $fields->{$name};
286             }
287 0           my $name2 = $self->field_name( $name );
288 0 0         if ( exists $fields->{$name2} ) {
289 0           return $fields->{$name2};
290             }
291 0           return;
292             }
293              
294             #pod =internal_method B<field_name> FIELD_KEY
295             #pod
296             #pod Looks up field names in the project metadata in the project. Read-only.
297             #pod
298             #pod =cut
299              
300             sub field_name {
301 0     0 1   my $self = shift;
302 0           my $name = shift;
303              
304 0 0         unless ( $self->{field_names} ) {
305 0           my $data = $self->metadata->{fields};
306              
307             $self->{field_names} = { ##
308 0           map { $data->{$_}->{name} => $_ } keys %$data
  0            
309             };
310             }
311              
312 0           return $self->{field_names}->{$name};
313             }
314              
315             #pod =head1 DESCRIPTION
316             #pod
317             #pod This object represents a JIRA project as an object. It is overloaded so it returns the C<key> of the project when stringified, and the C<id> of the project when it is used in a numeric context. Note, however, that if two of these objects are compared I<as strings>, the C<name> of the projects will be used for the comparison (numeric comparison will compare the C<id>s of the projects).
318             #pod
319             #pod =cut
320              
321             #<<<
322             use overload
323 10     10   1328 '""' => sub { shift->key },
324 0     0   0 '0+' => sub { shift->id },
325             '<=>' => sub {
326 0     0   0 my ( $A, $B ) = @_;
327 0 0       0 my $AA = ref $A ? $A->id : $A;
328 0 0       0 my $BB = ref $B ? $B->id : $B;
329 0         0 $AA <=> $BB;
330             },
331             'cmp' => sub {
332 16     16   2464 my ( $A, $B ) = @_;
333 16 50       31 my $AA = ref $A ? $A->name : $A;
334 16 50       31 my $BB = ref $B ? $B->name : $B;
335 16         24 $AA cmp $BB;
336 4     4   20 };
  4         4  
  4         33  
337             #>>>
338              
339             1;
340              
341             __END__
342              
343             =pod
344              
345             =encoding UTF-8
346              
347             =for :stopwords Packy Anderson Alexey Melezhik Atlassian GreenHopper JRC ScriptRunner TODO
348             aggregateprogress aggregatetimeestimate aggregatetimeoriginalestimate
349             assigneeType avatar avatarUrls completeDate displayName duedate
350             emailAddress endDate fieldtype fixVersions fromString genericized iconUrl
351             isAssigneeTypeValid issueTypes issuekeys issuelinks issuetype jira jql
352             lastViewed maxResults originalEstimate originalEstimateSeconds parentkey
353             projectId rapidViewId remainingEstimate remainingEstimateSeconds
354             resolutiondate sprintlist startDate subtaskIssueTypes timeSpent
355             timeSpentSeconds timeestimate timeoriginalestimate timespent timetracking
356             toString updateAuthor worklog workratio
357              
358             =head1 NAME
359              
360             JIRA::REST::Class::Project - A helper class for L<JIRA::REST::Class|JIRA::REST::Class> that represents a JIRA project as an object.
361              
362             =head1 VERSION
363              
364             version 0.10
365              
366             =head1 DESCRIPTION
367              
368             This object represents a JIRA project as an object. It is overloaded so it returns the C<key> of the project when stringified, and the C<id> of the project when it is used in a numeric context. Note, however, that if two of these objects are compared I<as strings>, the C<name> of the projects will be used for the comparison (numeric comparison will compare the C<id>s of the projects).
369              
370             =head1 READ-ONLY ACCESSORS
371              
372             =head2 B<assigneeType>
373              
374             This accessor returns the assignee type of the project.
375              
376             =head2 B<avatarUrls>
377              
378             A hashref of the different sizes available for the project's avatar.
379              
380             =head2 B<components>
381              
382             A list of the components for the project as
383             L<JIRA::REST::Class::Project::Component|JIRA::REST::Class::Project::Component>
384             objects.
385              
386             =head2 B<description>
387              
388             Returns the description of the project.
389              
390             =head2 B<expand>
391              
392             A comma-separated list of fields in the project that weren't expanded in the initial REST call.
393              
394             =head2 B<id>
395              
396             Returns the id of the project.
397              
398             =head2 B<issueTypes>
399              
400             A list of valid issue types for the project as
401             L<JIRA::REST::Class::Issue::Type|JIRA::REST::Class::Issue::Type> objects.
402              
403             =head2 B<subtaskIssueTypes>
404              
405             Taking a page from the old SOAP interface, this accessor returns a list of
406             all issue types (as
407             L<JIRA::REST::Class::Issue::Type|JIRA::REST::Class::Issue::Type> objects)
408             whose subtask field is true.
409              
410             =head2 B<key>
411              
412             Returns the key of the project.
413              
414             =head2 B<lead>
415              
416             Returns the project lead as a
417             L<JIRA::REST::Class::User|JIRA::REST::Class::User> object.
418              
419             =head2 B<name>
420              
421             Returns the name of the project.
422              
423             =head2 B<category>
424              
425             Returns a hashref of the category of the project as
426             L<JIRA::REST::Class::Project::Category|JIRA::REST::Class::Project::Category>
427             objects.
428              
429             =head2 B<self>
430              
431             Returns the JIRA REST API URL of the project.
432              
433             =head2 B<versions>
434              
435             Returns a list of the versions of the project as
436             L<JIRA::REST::Class::Project::Version|JIRA::REST::Class::Project::Version>
437             objects.
438              
439             =head2 B<metadata>
440              
441             Returns the metadata associated with this project.
442              
443             =head2 B<allowed_components>
444              
445             Returns a list of the allowed values for the 'components' field in the project.
446              
447             =head2 B<allowed_versions>
448              
449             Returns a list of the allowed values for the 'versions' field in the project.
450              
451             =head2 B<allowed_fix_versions>
452              
453             Returns a list of the allowed values for the 'fixVersions' field in the project.
454              
455             =head2 B<allowed_issue_types>
456              
457             Returns a list of the allowed values for the 'issuetype' field in the project.
458              
459             =head2 B<allowed_priorities>
460              
461             Returns a list of the allowed values for the 'priority' field in the project.
462              
463             =head1 INTERNAL METHODS
464              
465             =head2 B<allowed_field_values> FIELD_NAME
466              
467             Returns a list of allowable values for the specified field in the project.
468              
469             =head2 B<field_metadata_exists> FIELD_NAME
470              
471             Boolean indicating whether there is metadata for a given field in the project. Read-only.
472              
473             =head2 B<field_metadata> FIELD_NAME
474              
475             Looks for metadata under either a field's key or name in the project. Read-only.
476              
477             =head2 B<field_name> FIELD_KEY
478              
479             Looks up field names in the project metadata in the project. Read-only.
480              
481             =head1 RELATED CLASSES
482              
483             =over 2
484              
485             =item * L<JIRA::REST::Class|JIRA::REST::Class>
486              
487             =item * L<JIRA::REST::Class::Abstract|JIRA::REST::Class::Abstract>
488              
489             =item * L<JIRA::REST::Class::Issue::Type|JIRA::REST::Class::Issue::Type>
490              
491             =item * L<JIRA::REST::Class::Project::Category|JIRA::REST::Class::Project::Category>
492              
493             =item * L<JIRA::REST::Class::Project::Component|JIRA::REST::Class::Project::Component>
494              
495             =item * L<JIRA::REST::Class::Project::Version|JIRA::REST::Class::Project::Version>
496              
497             =item * L<JIRA::REST::Class::User|JIRA::REST::Class::User>
498              
499             =back
500              
501             =head1 AUTHOR
502              
503             Packy Anderson <packy@cpan.org>
504              
505             =head1 COPYRIGHT AND LICENSE
506              
507             This software is Copyright (c) 2017 by Packy Anderson.
508              
509             This is free software, licensed under:
510              
511             The Artistic License 2.0 (GPL Compatible)
512              
513             =cut