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   1401 use parent qw( JIRA::REST::Class::Abstract );
  4         7  
  4         17  
3 4     4   199 use strict;
  4         29  
  4         70  
4 4     4   17 use warnings;
  4         6  
  4         71  
5 4     4   48 use 5.010;
  4         10  
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 a JIRA project as an object.
13              
14 4     4   18 use Readonly 2.04;
  4         44  
  4         3560  
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         6 ( my $url = $self->self ) =~ s{.*/project}{/project}x;
54 2         7 my $data = $self->jira->get( $url );
55              
56 2         12746 $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         13 };
62              
63             $self->{components} = [ ##
64 2         4 map { $make_component->( $_ ) } @{ $data->{components} }
  0         0  
  2         9  
65             ];
66              
67 2         6 $self->{description} = $data->{description};
68              
69             my $make_issue_type = sub {
70 10     10   16 my $type = shift;
71 10         43 return $self->make_object( 'issuetype', { data => $type } );
72 2         9 };
73              
74             $self->{issueTypes} = [ ##
75 2         5 map { $make_issue_type->( $_ ) } @{ $data->{issueTypes} }
  10         19  
  2         6  
76             ];
77              
78             $self->{subtaskIssueTypes} = [ ##
79 2         6 grep { $_->subtask } @{ $self->{issueTypes} }
  10         44  
  2         7  
80             ];
81              
82 2         24 $self->{lead} = $self->make_object( 'user', { data => $data->{lead} } );
83              
84 2         7 $self->{roles} = $data->{roles};
85              
86             my $make_project_version = sub {
87 6     6   8 my $pv = shift;
88 6         21 my $vers = $self->make_object( 'projectvers', { data => $pv } );
89             $self->{version_hash}->{ $vers->id }
90 6         21 = $self->{version_hash}->{ $vers->name } = $vers;
91 6         55 return $vers;
92 2         10 };
93              
94             $self->{versions} = [ ##
95 2         3 map { $make_project_version->( $_ ) } @{ $data->{versions} }
  6         13  
  2         6  
96             ];
97              
98 2         7 foreach my $field ( @fields ) {
99 0         0 $self->{lazy_loaded}->{$field} = 1;
100             }
101              
102 2         19 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   1465 '""' => 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   3093 my ( $A, $B ) = @_;
333 16 50       39 my $AA = ref $A ? $A->name : $A;
334 16 50       37 my $BB = ref $B ? $B->name : $B;
335 16         39 $AA cmp $BB;
336 4     4   27 };
  4         4  
  4         43  
337             #>>>
338              
339             1;
340              
341             __END__
342              
343             =pod
344              
345             =encoding UTF-8
346              
347             =for :stopwords Packy Anderson Alexandr Alexey Ciornii Heumann Manni Melezhik Atlassian
348             GreenHopper JRC ScriptRunner TODO aggregateprogress aggregatetimeestimate
349             aggregatetimeoriginalestimate assigneeType avatar avatarUrls completeDate
350             displayName duedate emailAddress endDate fieldtype fixVersions fromString
351             genericized iconUrl isAssigneeTypeValid issueTypes issuekeys issuelinks
352             issuetype jira jql lastViewed maxResults originalEstimate
353             originalEstimateSeconds parentkey projectId rapidViewId remainingEstimate
354             remainingEstimateSeconds resolutiondate sprintlist startDate
355             subtaskIssueTypes timeSpent timeSpentSeconds timeestimate
356             timeoriginalestimate timespent timetracking toString updateAuthor worklog
357             workratio
358              
359             =head1 NAME
360              
361             JIRA::REST::Class::Project - A helper class for L<JIRA::REST::Class|JIRA::REST::Class> that represents a JIRA project as an object.
362              
363             =head1 VERSION
364              
365             version 0.12
366              
367             =head1 DESCRIPTION
368              
369             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).
370              
371             =head1 READ-ONLY ACCESSORS
372              
373             =head2 B<assigneeType>
374              
375             This accessor returns the assignee type of the project.
376              
377             =head2 B<avatarUrls>
378              
379             A hashref of the different sizes available for the project's avatar.
380              
381             =head2 B<components>
382              
383             A list of the components for the project as
384             L<JIRA::REST::Class::Project::Component|JIRA::REST::Class::Project::Component>
385             objects.
386              
387             =head2 B<description>
388              
389             Returns the description of the project.
390              
391             =head2 B<expand>
392              
393             A comma-separated list of fields in the project that weren't expanded in the initial REST call.
394              
395             =head2 B<id>
396              
397             Returns the id of the project.
398              
399             =head2 B<issueTypes>
400              
401             A list of valid issue types for the project as
402             L<JIRA::REST::Class::Issue::Type|JIRA::REST::Class::Issue::Type> objects.
403              
404             =head2 B<subtaskIssueTypes>
405              
406             Taking a page from the old SOAP interface, this accessor returns a list of
407             all issue types (as
408             L<JIRA::REST::Class::Issue::Type|JIRA::REST::Class::Issue::Type> objects)
409             whose subtask field is true.
410              
411             =head2 B<key>
412              
413             Returns the key of the project.
414              
415             =head2 B<lead>
416              
417             Returns the project lead as a
418             L<JIRA::REST::Class::User|JIRA::REST::Class::User> object.
419              
420             =head2 B<name>
421              
422             Returns the name of the project.
423              
424             =head2 B<category>
425              
426             Returns a hashref of the category of the project as
427             L<JIRA::REST::Class::Project::Category|JIRA::REST::Class::Project::Category>
428             objects.
429              
430             =head2 B<self>
431              
432             Returns the JIRA REST API URL of the project.
433              
434             =head2 B<versions>
435              
436             Returns a list of the versions of the project as
437             L<JIRA::REST::Class::Project::Version|JIRA::REST::Class::Project::Version>
438             objects.
439              
440             =head2 B<metadata>
441              
442             Returns the metadata associated with this project.
443              
444             =head2 B<allowed_components>
445              
446             Returns a list of the allowed values for the 'components' field in the project.
447              
448             =head2 B<allowed_versions>
449              
450             Returns a list of the allowed values for the 'versions' field in the project.
451              
452             =head2 B<allowed_fix_versions>
453              
454             Returns a list of the allowed values for the 'fixVersions' field in the project.
455              
456             =head2 B<allowed_issue_types>
457              
458             Returns a list of the allowed values for the 'issuetype' field in the project.
459              
460             =head2 B<allowed_priorities>
461              
462             Returns a list of the allowed values for the 'priority' field in the project.
463              
464             =head1 INTERNAL METHODS
465              
466             =head2 B<allowed_field_values> FIELD_NAME
467              
468             Returns a list of allowable values for the specified field in the project.
469              
470             =head2 B<field_metadata_exists> FIELD_NAME
471              
472             Boolean indicating whether there is metadata for a given field in the project. Read-only.
473              
474             =head2 B<field_metadata> FIELD_NAME
475              
476             Looks for metadata under either a field's key or name in the project. Read-only.
477              
478             =head2 B<field_name> FIELD_KEY
479              
480             Looks up field names in the project metadata in the project. Read-only.
481              
482             =head1 RELATED CLASSES
483              
484             =over 2
485              
486             =item * L<JIRA::REST::Class|JIRA::REST::Class>
487              
488             =item * L<JIRA::REST::Class::Abstract|JIRA::REST::Class::Abstract>
489              
490             =item * L<JIRA::REST::Class::Issue::Type|JIRA::REST::Class::Issue::Type>
491              
492             =item * L<JIRA::REST::Class::Project::Category|JIRA::REST::Class::Project::Category>
493              
494             =item * L<JIRA::REST::Class::Project::Component|JIRA::REST::Class::Project::Component>
495              
496             =item * L<JIRA::REST::Class::Project::Version|JIRA::REST::Class::Project::Version>
497              
498             =item * L<JIRA::REST::Class::User|JIRA::REST::Class::User>
499              
500             =back
501              
502             =head1 AUTHOR
503              
504             Packy Anderson <packy@cpan.org>
505              
506             =head1 COPYRIGHT AND LICENSE
507              
508             This software is Copyright (c) 2017 by Packy Anderson.
509              
510             This is free software, licensed under:
511              
512             The Artistic License 2.0 (GPL Compatible)
513              
514             =cut