File Coverage

blib/lib/Comskil/JWand.pm
Criterion Covered Total %
statement 41 43 95.3
branch n/a
condition n/a
subroutine 14 14 100.0
pod n/a
total 55 57 96.4


line stmt bran cond sub pod time code
1             # File: Comskil/JWand.pm
2             # Desc: A package that performs magic with remote JIRA services.
3            
4             # #TODO Add in the sophisticated BEGIN/END package initialization blocks
5             # #TODO Add in the ability to log to a file.
6             # #TODO Run Perl Critic and Source code cleaner.
7            
8            
9             package Comskil::JWand;
10            
11             =head1 NAME
12            
13             Comskil::JWand - The great new Comskil::JWand!
14            
15             =head1 VERSION
16            
17             Version 0.1
18            
19             =cut
20            
21 1     1   23775 use 5.006;
  1         4  
  1         40  
22 1     1   5 use strict;
  1         2  
  1         32  
23 1     1   5 use warnings;
  1         6  
  1         120  
24            
25             our $VERSION = "0.1";
26             our $COPYRIGHT = "Copyright (c) 2011 Comskil, Inc. All Rights Reserved Worldwide";
27             our $PRODUCT = "Comskil::JWand";
28             our $USER_AGENT = "$PRODUCT\\$VERSION ($COPYRIGHT)";
29            
30             =head1 SYNOPSIS
31            
32             Quick summary of what the module does.
33            
34             Perhaps a little code snippet.
35            
36             use Comskil::JWand;
37            
38             my $foo = Comskil::JWand->new();
39             ...
40             =cut
41            
42             BEGIN {
43 1     1   6 use Exporter;
  1         2  
  1         171  
44 1     1   2 our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
45 1         2 $VERSION = $Comskil::VERSION;
46 1         14 @ISA = qw(Exporter);
47 1         3 @EXPORT = qw(
48             buildProjectImportFile
49             );
50 1         3 @EXPORT_OK = qw(
51             getDebug
52             getVerbose
53             setDebug
54             setVerbose
55             grabPriorities
56             grabProjectRoles
57             grabResolutions
58             grabStatuses
59             grabAttachments
60             grabComponents
61             grabProjectInfo
62             grabServerInfo
63             grabVersions
64             );
65 1         53 %EXPORT_TAGS = (
66             ALL => [ qw(
67             buildProjectImportFile
68             getDebug
69             getVerbose
70             setDebug
71             setVerbose
72             grabAttachments
73             grabComponents
74             grabPriorities
75             grabProjectInfo
76             grabProjectRoles
77             grabServerInfo
78             grabStatuses
79             grabResolutions
80             grabVersions
81             ) ]
82             );
83             }
84            
85 1     1   5 END { }
86            
87             =head1 EXPORT
88             A list of functions that can be exported. You can delete this section
89             if you don't export anything, such as for a purely object-oriented module.
90             =over 8
91             =item * new()
92             =item * grabVersions()
93             =item * grabStatuses()
94             =head1 SUBROUTINES/METHODS
95             =cut
96            
97 1     1   6 use Carp;
  1         2  
  1         87  
98            
99 1     1   1152 use Data::Dumper; my $dd;
  1         15470  
  1         205  
100            
101 1     1   11 use File::Path qw(make_path remove_tree);
  1         2  
  1         95  
102 1     1   3909 use LWP::UserAgent;
  1         87126  
  1         41  
103 1     1   11 use HTTP::Headers;
  1         2  
  1         30  
104 1     1   6 use HTTP::Response;
  1         2  
  1         30  
105 1     1   6 use HTTP::Status qw(:constants :is status_message);
  1         4  
  1         848  
106 1     1   544 use JIRA::Client;
  0         0  
  0         0  
107            
108            
109             sub _connect {
110             my ($self,@args) = @_;
111            
112             return($self->{client_handle})
113             }
114            
115             sub _grab_list {
116             my ($self,@args) = @_;
117             my $ulist = [ ];
118             return(\$ulist);
119             }
120            
121            
122            
123             =head2 new()
124             =over 8
125             =item :username
126             =item :password
127             =item :baseurl
128             =item :
129             =back
130             =cut
131            
132             # Func: new(class_name [, options-hash] )
133             # Desc:
134             #
135             # Args: A hash containing key => value pairs to initialize the class. The
136             # valid option keys are:
137             sub __new {
138             my ($class,@args) = @_;
139             my $self = {
140             client_handle => undef ## #FIX Get rid of the leading colon on options
141             ,':user_agent' => undef
142             ,':max_results' => 13
143             ,':max_issues' => undef
144             ,':max_attachments' => undef
145             ,':max_filesize' => undef
146             ,':grab_thumbs' => 1
147             ,'_server_info' => undef
148             ,'_user_agent' => undef
149             };
150             foreach my $href (@args) {
151             @$self{ keys %{$href} } = values %{$href};
152             }
153             bless($self,$class);
154            
155             if ( (! $self->{client_handle}) && (! $self->{fileset}) ) {
156             $self->{client_handle} = JIRA::Client->new(
157             $self->{url}
158             ,$self->{username}
159             ,$self->{password}
160             );
161             $self->{'_server_info'} = eval { $self->{client_handle}->getServerInfo() };
162             croak sprintf("getServerInfo(): %s",$@) if $@;
163            
164             if ($self->{debug} >= 1) { $dd = Data::Dumper->new([$self->{_server_info}]); print $dd->Dump(); }
165             if ($self->{verbose}) {
166             print("connected to: ".$self->{_server_info}->{baseUrl}." as '".$self->{username}."'\n");
167             print("server version: ".$self->{_server_info}->{version}.
168             " build: ".$self->{_server_info}->{buildNumber}."\n");
169             }
170             }
171            
172             if ($self->{debug} >= 3) { $dd = Data::Dumper->new([$self]); print $dd->Dump(); }
173             return( $self->{client_handle} ? $self : undef);
174             }
175            
176            
177             =head2 appendToChain()
178             =cut
179            
180             sub appendToChain {
181             my ($self,$wand) = @_;
182             croak "appendToChain(undef) failed, undef not a JWand\n" if (! $wand);
183             if ($self->{_next_wand}) {
184             print($self->{bname}," -> ") if ($self->{verbose});
185             $self->{_next_wand}->appendToChain($wand);
186             } else {
187             print($self->{bname}," => ",$wand->{bname},"\n") if ($self->{verbose});
188             $self->{_next_wand} = $wand;
189             }
190            
191             if ($self->{debug} >= 3) { $dd = Data::Dumper->new([$self]); print $dd->Dump(); }
192             return($self);
193             }
194            
195             =head2 jira_handle()
196             =cut
197            
198             sub jira_handle {
199             my $self = shift;
200             return($self->{client_handle});
201             }
202            
203             =head2 grabProjectKeys()
204             =cut
205            
206             sub grabProjectKeys {
207             my($self,$regx) = @_;
208             }
209            
210             =head2 grabPriorities()
211             =cut
212            
213             sub grabPriorities {
214             my ($self,@args) = @_;
215             my ($rslt,$ulist);
216            
217             $ulist = eval { $self->{client_handle}->getPriorities() };
218             croak sprintf("getPriorities(): %s",$@) if $@;
219             foreach my $key (@{$ulist}) { $key->{bname} = $self->{bname}; }
220             if ($self->{debug} >= 1) { $dd = Data::Dumper->new([$ulist]); print $dd->Dump(); }
221            
222             if ($self->{_next_wand}) {
223             $rslt = $self->{_next_wand}->grabPriorities(@args);
224             if ($self->{debug} >= 2) { $dd = Data::Dumper->new([$rslt]); print $dd->Dump(); }
225             $ulist = [ @{$ulist}, @{$rslt} ];
226             }
227            
228             if ($self->{debug} >= 3) { $dd = Data::Dumper->new([$ulist]); print $dd->Dump(); }
229             return($ulist);
230             }
231            
232             =head2 grabStatuses()
233             =cut
234            
235             sub grabStatuses {
236             my ($self,@args) = @_;
237             my ($rslt,$ulist);
238            
239             $ulist = eval { $self->{client_handle}->getStatuses() };
240             croak sprintf("getStatuses(): %s",$@) if $@;
241             foreach my $key (@{$ulist}) { $key->{bname} = $self->{bname}; }
242             if ($self->{debug} >= 1) { $dd = Data::Dumper->new([$ulist]); print $dd->Dump(); }
243            
244             if ($self->{_next_wand}) {
245             $rslt = $self->{_next_wand}->grabStatuses(@args);
246             if ($self->{debug} >= 2) { $dd = Data::Dumper->new([$rslt]); print $dd->Dump(); }
247             $ulist = [ @{$ulist}, @{$rslt} ];
248             }
249            
250             if ($self->{debug} >= 3) { $dd = Data::Dumper->new([$ulist]); print $dd->Dump(); }
251             return($ulist);
252             }
253            
254             =head2 grabResolutions()
255             =cut
256            
257             sub grabResolutions {
258             my ($self,@args) = @_;
259             my ($rslt,$ulist);
260            
261             $ulist = eval { $self->{client_handle}->getResolutions() };
262             croak sprintf("getResolutions(): %s",$@) if $@;
263             foreach my $key (@{$ulist}) { $key->{bname} = $self->{bname}; }
264             if ($self->{debug} >= 1) { $dd = Data::Dumper->new([$ulist]); print $dd->Dump(); }
265            
266             if ($self->{_next_wand}) {
267             $rslt = $self->{_next_wand}->grabResolutions(@args);
268             if ($self->{debug} >= 2) { $dd = Data::Dumper->new([$rslt]); print $dd->Dump(); }
269             $ulist = [ @{$ulist}, @{$rslt} ];
270             }
271            
272             if ($self->{debug} >= 3) { $dd = Data::Dumper->new([$ulist]); print $dd->Dump(); }
273             return($ulist);
274             }
275            
276             =head2 grabProjectRoles()
277             =cut
278            
279             sub grabProjectRoles {
280             my ($self,@args) = @_;
281             my ($rslt,$ulist);
282            
283             $ulist = eval { $self->{client_handle}->getProjectRoles() };
284             croak sprintf("getProjectRoles(): %s",$@) if $@;
285             foreach my $key (@{$ulist}) { $key->{bname} = $self->{bname}; }
286             if ($self->{debug} >= 1) { $dd = Data::Dumper->new([$ulist]); print $dd->Dump(); }
287            
288             if ($self->{_next_wand}) {
289             $rslt = $self->{_next_wand}->grabProjectRoles(@args);
290             if ($self->{debug} >= 2) { $dd = Data::Dumper->new([$rslt]); print $dd->Dump(); }
291             $ulist = [ @{$ulist}, @{$rslt} ];
292             }
293            
294             if ($self->{debug} >= 3) { $dd = Data::Dumper->new([$ulist]); print $dd->Dump(); }
295             return($ulist);
296             }
297            
298             =head2 grabComponents()
299             =cut
300             sub grabComponents {
301             my ($self,@args) = @_;
302             my $ulist = [ ];
303            
304             foreach my $pkey (@args) {
305             my $temp = eval { $self->{client_handle}->getComponents($pkey) };
306             croak sprintf("getComponents(%s): %s",$pkey,$@) if $@;
307             $ulist = [ @{$ulist}, @{$temp} ] if ($temp);
308             }
309            
310             $dd = Data::Dumper->new([$ulist]); print $dd->Dump();
311             return(\$ulist);
312             }
313            
314             =head2 grabVersions()
315             =cut
316            
317             sub grabVersions {
318             my ($self,@args) = @_;
319             my $ulist = [ ];
320            
321             foreach my $pkey (@args) {
322             my $temp = eval { $self->{client_handle}->getVersions($pkey) };
323             croak sprintf("getVersions(%s): %s",$pkey,$@) if $@;
324             $ulist = [ @{$ulist}, @{$temp} ] if ($temp);
325             }
326            
327             $dd = Data::Dumper->new([$ulist]); print $dd->Dump();
328             return(\$ulist);
329             }
330            
331             =head2 grabProjectInfo()
332             =cut
333            
334             sub grabProjectInfo {
335             my ($self,@args) = @_;
336             my $ulist = [ ];
337            
338             foreach my $pkey (@args) {
339             my $temp = eval { $self->{client_handle}->getProjectByKey($pkey) };
340             croak sprintf("getProjectByKey(%s): %s",$pkey,$@) if $@;
341             $ulist = [ @{$ulist}, [ $temp ] ] if ($temp);
342             }
343            
344             $dd = Data::Dumper->new([$ulist]); print $dd->Dump();
345             return(\$ulist);
346             }
347            
348             =head3 grabProjectRoleActors()
349             =cut
350            
351             sub grabProjectRoleActors {
352             my ($self,$refpl,$refrl) = @_;
353             my $ulist = [ ];
354            
355             $dd = Data::Dumper->new([$refpl]); print $dd->Dump();
356             $dd = Data::Dumper->new([$refrl]); print $dd->Dump();
357            
358             foreach my $pkey (@{$refpl}) {
359             $dd = Data::Dumper->new([$pkey]); print $dd->Dump();
360             foreach my $rkey ($refrl) {
361             $dd = Data::Dumper->new([$rkey]); print $dd->Dump();
362             my $temp = eval { $self->{client_handle}->getProjectRoleActors($rkey->[0],$pkey) };
363             $dd = Data::Dumper->new([$temp]); print $dd->Dump();
364             croak sprintf("getProjectRoleActors(%s,%s): %s",$rkey,$pkey,$@) if $@;
365             $ulist = [ @{$ulist}, [ $temp ] ] if ($temp);
366             }
367             }
368            
369             $dd = Data::Dumper->new([$ulist]); print $dd->Dump();
370             return(\$ulist);
371             }
372            
373             =over 4
374            
375             =item B OUTPATH [, ([,...])]]
376             asdfasdf
377            
378             =back
379            
380             =cut
381            
382             sub grabAttachments {
383             my ($self,$path,@args) = @_;
384             my ($cnt,$ua) = (undef,undef);
385            
386             ## Verify we are connected to a remote JIRA instance.
387            
388             return(undef) if (! $self->{client_handle});
389            
390             ## If no project keys are specified find them all.
391            
392             ## Make sure we have a UserAgent to access the remote JIRA instance to grab files.
393            
394             if (! $self->{'_user_agent'}) {
395             $self->{':user_agent'} = $USER_AGENT if (! $self->{':user_agent'});
396             my $rh = HTTP::Headers->new();
397             $rh->authorization_basic($self->{':username'},$self->{':password'});
398             $ua = $self->{'_user_agent'} = LWP::UserAgent->new(
399             agent => $USER_AGENT
400             ,default_headers => $rh
401             );
402             ## $dd = Data::Dumper->new([$ua]); print $dd->Dump();
403             }
404            
405             ## Loop through the project keys grabbing all of the files on each iteration.
406            
407             my $cnt_attach = 0;
408             my $cnt_issues = 0;
409            
410             foreach my $pkey (@args) {
411            
412             my $ikey = "";
413             if ($pkey =~ m/(([A-Z]+)\-\d+)/) {
414             $pkey = $2;
415             $ikey = $1;
416             }
417            
418             print "$pkey\n";
419            
420             my $x = eval { $self->{client_handle}->getProjectByKey($pkey) };
421             if ($@) {
422             carp sprintf("getProjectByKey('%s'): %s",$pkey,$@);
423             last;
424             }
425            
426             #### Iterate through each issue in the project.
427            
428             while (1) {
429             my $jql = "project = $pkey " . (($ikey ne "") ? "and issueKey > $ikey " : "") . "order by issueKey asc";
430            
431             #print "more issues....\n";
432            
433             my $ilist = eval {
434             $self->{client_handle}->getIssuesFromJqlSearch($jql,$self->{':max_results'})
435             };
436             if ($@) {
437             carp sprintf("getIssuesFromJqlSearch('%s',%n): %s",$jql,$self->{':max_results'},$@);
438             last;
439             }
440             last if (! @{$ilist});
441            
442             foreach my $issue (@$ilist) {
443             $cnt_issues++;
444             $ikey = $issue->{'key'};
445             print "$ikey\n";
446             if (@{$issue->{'attachmentNames'}}) {
447             my $attach_list = eval { $self->{client_handle}->getAttachmentsFromIssue($ikey) };
448             croak sprintf("getAttachmentsFromIssue('%s'): %s",$ikey,$@) if $@;
449            
450             ## $dd = Data::Dumper->new([$attach_list]); print $dd->Dump();
451            
452             my $fpth = "$path/$pkey/$ikey";
453             my $thmb = 0;
454             #print "$fpth\n";
455             foreach my $attach (@$attach_list) {
456            
457             # Attachment Filespec := ///
458             # URL := /secure/attachment//
459             # https://request.siteworx.com/secure/attachment/17871/Wrong+FTP.jpg
460             # '+' :=
461             # Thumbnail Filespec := ///thumbs/_thumb_.png
462             # URL := /secure/thumbnail//_thumb_.png
463             # https://request.siteworx.com/secure/thumbnail/17871/_thumb_17871.png
464            
465             next if (defined($self->{':max_filesize'}) && ($attach->{'filesize'} > $self->{':max_filesize'}));
466            
467             my $furl = $self->{':url'}."/secure/attachment/".$attach->{'id'}."/".$attach->{'filename'};
468             my $fspc = $fpth."/".$attach->{'id'};
469             #print "$furl\n ";
470             print "=> $fspc\n";
471            
472             make_path($fpth);
473             my $rh = $self->{'_user_agent'}->mirror($furl,$fspc);
474             $cnt_attach++ if (($rh->code() == HTTP_OK) || ($rh->code() == HTTP_NOT_MODIFIED));
475            
476             next if (! $self->{':grab_thumbs'});
477            
478             my $turl = $self->{':url'}."/secure/thumbnail/".$attach->{'id'}."/_thumb_".$attach->{'id'}.".png";
479             my $tspc = $fpth."/thumbs/_thumb_".$attach->{'id'}.".png";
480             #print "$turl\n ";
481             print "=> $tspc\n";
482            
483             make_path("$fpth/thumbs");
484             $rh = $self->{'_user_agent'}->mirror($turl,$tspc);
485             $thmb++ if (($rh->code() == HTTP_OK) || ($rh->code() == HTTP_NOT_MODIFIED));
486            
487             ## $dd = Data::Dumper->new([$attach]); print $dd->Dump();
488             }
489             remove_tree("$fpth/thumbs") if (! $thmb);
490             return($cnt_issues,$cnt_attach) if ((defined($self->{':max_attachments'}) &&
491             ($cnt_attach >= $self->{':max_attachments'})) ||
492             (defined($self->{':max_issues'}) &&
493             ($cnt_issues >= $self->{':max_issues'})));
494             }
495             }
496             }
497             }
498             return($cnt_issues,$cnt_attach);
499             }
500            
501            
502            
503             =head2 getDebug()
504             =cut
505             sub getDebug {
506             my ($self,@args) = @_;
507             carp "getDebug() is not implemented";
508             return(undef);
509             }
510            
511             =head2 getVerbose()
512             =cut
513             sub getVerbose {
514             my ($self,@args) = @_;
515             carp "getVerbose() is not implemented";
516             return(undef);
517             }
518            
519             =head2 setDebug()
520             =cut
521            
522             sub setDebug {
523             my ($self,@args) = @_;
524             carp "setDebug() is not implemented";
525             return(undef);
526             }
527            
528             =head2 setVerbose()
529             =cut
530            
531             sub setVerbose {
532             my ($self,@args) = @_;
533             carp "setVerbose() is not implemented";
534             return(undef);
535             }
536            
537             sub new {
538             my ($class,@args) = @_;
539             my $self = ( );
540             foreach my $href (@args) {
541             @$self{ keys %{$href} } = values %{$href};
542             }
543             bless($self,$class);
544            
545             croak "error: configuration filename is missing" if (! $self->{filename});
546             croak "error: configuration file '".$self->{filename}."' not found" if (! -f $self->{filename});
547             if (! ($self->{config} = _get_configuration($self->{filename}))) {
548             croak "error: configuration file contains errors";
549             }
550             return($self);
551             }
552            
553            
554             my %attribs = (
555             'id' => [ 'integer', '' ]
556             ,'issue' => [ 'integer', 'as' ]
557             ,'author' => [ 'string', '' ]
558             ,'body' => [ 'text', '' ]
559             ,'created' => [ 'timestamp', '' ]
560             ,'updated' => [ 'timestamp', 'asd' ]
561             ,'key' => [ 'string', 'asd' ]
562             ,'project' => [ 'string', 'asd' ]
563             ,'reporter' => [ 'string', 'asd' ]
564             ,'assignee' => [ 'string', 'asd' ]
565             ,'type' => [ 'integer', 'type' ]
566             ,'summary' => [ 'string', 'asd' ]
567             ,'priority' => [ 'integer', 'priorities' ]
568             ,'resolution' => [ 'integer', 'resolutions' ]
569             ,'status' => [ 'integer', 'statuses' ]
570             ,'votes' => [ 'integer', 0 ]
571             ,'watched' => [ 'integer', 0 ]
572             ,'workflowId' => [ 'integer', 'asd' ]
573             ,'security' => [ 'integer', 'asd' ]
574             ,'description' => [ 'text', 'asd' ]
575             );
576            
577             my %entities = (
578             'WorkLog' => [ 'id', 'issue', 'author', 'body', 'created', 'updated', 'startdate', 'timeworked' ]
579             ,'Issue' => [ 'id', 'key', 'project', 'reporter', 'assignee', 'type', 'summary',
580             'priority', 'resolution', 'status', 'created', 'updated', 'votes', 'watched',
581             'workflowId', 'security', 'description' ]
582             );
583            
584             =head2 buildProjectImportFile
585             =cut
586            
587             sub buildProjectImportFile {
588             my ($self,@args) = @_;
589             foreach my $href (@args) {
590             @$self{ keys %{$href} } = values %{$href};
591             }
592            
593            
594            
595             return(undef);
596             }
597            
598            
599             1; ### End of 'JWand.pm'
600             __END__