File Coverage

blib/lib/WebService/ReviewBoard/Review.pm
Criterion Covered Total %
statement 18 126 14.2
branch 0 16 0.0
condition 0 6 0.0
subroutine 6 32 18.7
pod 21 21 100.0
total 45 201 22.3


line stmt bran cond sub pod time code
1             package WebService::ReviewBoard::Review;
2              
3 6     6   34 use strict;
  6         10  
  6         237  
4 6     6   30 use warnings;
  6         10  
  6         173  
5              
6 6     6   29 use Data::Dumper;
  6         12  
  6         381  
7 6     6   34 use Log::Log4perl qw(:easy);
  6         10  
  6         61  
8              
9             sub new {
10 0     0 1   my $proto = shift;
11 0           my $args = shift;
12              
13 0   0       my $class = ref $proto || $proto;
14 0           my $self = bless {
15             id => delete $args->{id},
16             reviewers => delete $args->{reviewers},
17             bugs => delete $args->{bugs},
18             summary => delete $args->{summary},
19             description => delete $args->{description},
20             groups => delete $args->{groups},
21             }, $class;
22              
23 0 0         $self->{review_board} = delete $args->{review_board}
24             or LOGCONFESS "new() missing review_board arg (WebService::ReviewBoard object)";
25              
26 0           return $self;
27             }
28              
29             # this module returns review object as string
30             sub as_string {
31 0     0 1   my $self = shift;
32              
33             return
34 0           "[REVIEW " . $self->get_id() . '] ' . $self->get_summary() . "\n"
35             . " Description: " . $self->get_description() . "\n"
36 0           . " Reviewers: " . join( ", ", @{ $self->get_reviewers() } ) . "\n"
37 0           . " Bugs: " . join( ", ", @{ $self->get_bugs() } ) . "\n"
38 0           . " Groups: " . join( ", ", @{ $self->get_groups() } ) . "\n";
39             }
40              
41             # this is a constructor
42             sub create {
43 0     0 1   my $self = new( shift, shift );
44 0           my $args = shift;
45              
46 0           my $json
47             = $self->_get_rb()->api_post( $self->get_ua(), '/api/json/reviewrequests/new/', $args );
48 0 0         if ( !$json->{review_request} ) {
49 0           LOGDIE "create couldn't determine ID from this JSON that it got back from the server: "
50             . Dumper $json;
51             }
52              
53 0           $self->{id} = $json->{review_request}->{id};
54              
55 0           return $self;
56             }
57              
58             # this is a constructor
59             sub fetch {
60 0     0 1   my $proto = shift;
61 0           my $args = shift;
62              
63 0           my $from_user = delete $args->{from_user};
64 0           my $id = $args->{id};
65              
66 0           my $rb = $args->{review_board};
67 0           my $self = new( $proto, $args );
68              
69 0           my @reviews;
70             my $json;
71            
72             my $create_review = sub {
73 0     0     my $rr = shift;
74              
75 0           return $self->new(
76             {
77             review_board => $rb,
78             id => $rr->{id},
79             bugs => $rr->{bugs_closed},
80 0           reviewers => [ map { $_->{username} } @{ $rr->{target_people} } ],
  0            
81             description => $rr->{description},
82             summary => $rr->{summary},
83 0           groups => [ map { $_->{name} } @{ $rr->{target_groups} } ],
  0            
84             }
85             );
86 0           };
87              
88 0 0         if ($from_user) {
    0          
89 0           $json = $self->_get_rb()
90             ->api_get( $self->get_ua(), '/api/json/reviewrequests/from/user/' . $from_user );
91              
92 0           foreach my $rr ( @{ $json->{review_requests} } ) {
  0            
93 0           push @reviews, &$create_review( $rr );
94             }
95             }
96             elsif ($id) {
97 0           $json = $self->_get_rb()->api_get( $self->get_ua(), '/api/json/reviewrequests/' . $id );
98 0           push @reviews, &$create_review( $json->{review_request} );
99             }
100             else {
101 0           LOGDIE "fetch() must get either from_user or id as an argument";
102             }
103              
104             # they wanted just one item, but there weren't any, so we'll return undef
105 0 0 0       if ( !wantarray && !$reviews[0] ) {
106 0           return;
107             }
108              
109 0 0         return wantarray ? @reviews : $reviews[0];
110             }
111              
112             # this method returns user agent for given reviewboard
113 0     0 1   sub get_ua { return shift->_get_rb()->get_ua(); }
114              
115             # this method makes POST call to reviewboard and performs required action
116             sub review_api_post {
117 0     0 1   my $self = shift;
118 0           my $action = shift;
119              
120 0           return $self->_get_rb()
121             ->api_post( $self->get_ua(), "/api/json/reviewrequests/" . $self->get_id() . "/$action/",
122             @_ );
123             }
124              
125 0     0     sub _get_rb { return shift->{review_board}; }
126              
127 0     0 1   sub get_id { return shift->_get_field('id'); }
128 0     0 1   sub get_description { return shift->_get_field('description'); }
129 0     0 1   sub get_bugs { return shift->_get_field('bugs'); }
130 0     0 1   sub get_summary { return shift->_get_field('summary'); }
131 0     0 1   sub get_reviewers { return shift->_get_field('reviewers'); }
132              
133             # return groups for given review object
134 0     0 1   sub get_groups { return shift->_get_field('groups'); }
135              
136             sub _get_field {
137 0     0     my $self = shift;
138 0           my $field = shift;
139              
140 0 0         if ( !$self->{$field} ) {
141 0           LOGDIE "requested $field, but $field isn't set";
142             }
143              
144 0           return $self->{$field};
145             }
146              
147 0     0 1   sub set_description { return shift->_set_field( 'description', @_ ); }
148 0     0 1   sub set_summary { return shift->_set_field( 'summary', @_ ); }
149              
150             sub set_bugs {
151 0     0 1   my $self = shift;
152 0           $self->{bugs} = [@_];
153              
154 0           return $self->_set_field( 'bugs_closed', join( ',', @{ $self->{bugs} } ) );
  0            
155             }
156              
157             sub set_reviewers {
158 0     0 1   my $self = shift;
159 0           $self->{reviewers} = [@_];
160              
161 0           my $json = $self->_set_field( "target_people", join( ',', @{ $self->{reviewers} } ) );
  0            
162              
163             #XXX parse json and return a list of reviewers actually added:
164             #{"stat": "ok",
165             # "invalid_target_people": [],
166             # "target_people": [{"username": "jaybuff", "url": "\/users \/jaybuff\/", "fullname": "Jay Buffington", "id": 1, "email": "jaybuff@foo.com"}, {"username": "jdagnall", "url": "\/users\/jdagnall\/", "fullname": "Jud Dagnall", "id": 2, "email": "jdagnall@foo .com"}]}
167              
168 0           return 1;
169             }
170              
171             # sets groups for given review object
172             sub set_groups {
173 0     0 1   my $self = shift;
174 0           $self->{groups} = [@_];
175 0           return $self->_set_field( 'target_groups', join( ',', @{ $self->{groups} } ) );
  0            
176             }
177              
178             sub _set_field {
179 0     0     my $self = shift;
180 0           my $field = shift;
181 0           my $value = shift;
182              
183             # stick it in the object so the getters can access it later
184 0           $self->{$field} = $value;
185 0           return $self->review_api_post( "draft/set/$field", [ value => $value, ] );
186             }
187              
188             sub _set_review_action {
189 0     0     my $self = shift;
190 0           my $action = shift;
191 0           my $ua = $self->get_ua();
192 6     6   12053 use HTTP::Request::Common;
  6         10  
  6         1797  
193 0           my $request = POST( $self->_get_rb()->get_review_board_url() . "/r/" . $self->get_id() . "/" . $action . "/");
194 0           DEBUG "Doing request:\n" . $request->as_string();
195 0           my $response = $ua->request($request);
196 0           DEBUG "Got response:\n" . $response->as_string();
197 0           return 1;
198             }
199              
200             # discards given review object
201             sub discard_review_request {
202 0     0 1   my $self = shift;
203 0           return $self->_set_review_action("discard", @_);
204             }
205              
206             # set status as submit for given review object
207             sub submit_review_request {
208 0     0 1   my $self = shift;
209 0           return $self->_set_review_action("submitted", @_);
210             }
211              
212             sub publish {
213 0     0 1   my $self = shift;
214              
215 0           my $path = "/r/" . $self->get_id() . "/publish/";
216 0           my $ua = $self->get_ua();
217              
218             #XXX I couldn't get reviews/draft/publish from the web api to work, so I did this hack for now:
219             # I asked the review-board mailing list about this. Waiting for a response...
220 6     6   40 use HTTP::Request::Common;
  6         13  
  6         1326  
221              
222 0           my $request = POST( $self->_get_rb()->get_review_board_url() . $path );
223 0           DEBUG "Doing request:\n" . $request->as_string();
224 0           my $response = $ua->request($request);
225 0           DEBUG "Got response:\n" . $response->as_string();
226              
227             # $self->review_api_post(
228             # 'reviews/draft/publish',
229             # [
230             # diff_revision => 1,
231             # shipit => 0,
232             # body_top => undef,
233             # body_bottom => undef,
234             # ]
235             # );
236              
237 0           return 1;
238             }
239              
240             sub add_diff {
241 0     0 1   my $self = shift;
242 0           my $file = shift;
243 0           my $basedir = shift;
244              
245 0           my $args = [ path => [$file] ];
246              
247             # base dir is used only for some SCMs (like SVN) (I think)
248 0 0         if ($basedir) {
249 0           push @{$args}, ( basedir => $basedir );
  0            
250             }
251              
252 0           $self->review_api_post( 'diff/new', Content_Type => 'form-data', Content => $args );
253              
254 0           return 1;
255             }
256              
257             1;
258              
259             __END__
260              
261             WebService::ReviewBoard::Review - An object that represents a review on the review board system
262              
263             =head1 SYNOPSIS
264              
265             use WebService::ReviewBoard;
266              
267             my $rb = WebService::ReviewBoard->new( 'http://demo.review-board.org' );
268             $rb->login( 'username', 'password' );
269              
270             # there are two ways to create an object, create or fetch
271             # both these take a list of arguments, which must include
272             # a WebService::ReviewBoard object so it knows how to
273             # talk to review board.
274              
275             # the second arg is sent directly to the review board web services API
276             my $review = WebService::ReviewBoard::Review->create( { review_board => $rb }, [ repository_id => 1 ] );
277             $review->set_bugs( 1728212, 1723823 );
278             $review->set_reviewers( qw( jdagnall gno ) );
279             $review->set_summary( "this is the summary" );
280             $review->set_description( "this is the description" );
281             $review->set_groups('reviewboard');
282             $review->add_diff( '/tmp/patch' );
283             $review->publish();
284              
285             # alternatively, you can get existing reviews
286             foreach my $review ( WebService::ReviewBoard::Review->fetch( { review_board => $rb, from_user => 'jaybuff' } ) ) {
287             print "[REVIEW " . $review->get_id() . "] " . $review->get_summary() . "\n";
288             }
289              
290             # fetch uses the perl function wantarray so you can just get one review if you want:
291             my $review = WebService::ReviewBoard::Review->fetch( { review_board => $rb, id => 123 } );
292              
293             # set status as submit
294             $review->submit_review_request;
295              
296             # discard review request
297             $review->discard_review_request;
298            
299             =head1 DESCRIPTION
300              
301             =head1 INTERFACE
302              
303             =over
304              
305             =item C<< new() >>
306              
307             Do not use this constructor. To construct a C<< WebService::ReviewBoard::Review >> object use the
308             C<< create_review >> method in the C<< WebService::ReviewBoard >> class.
309              
310              
311             =item C<< create( { review_board => $rb }, $args ) >>
312              
313             C<<review_board>> must be a C<<WebService::ReviewBoard>> object. C<<$args>> is passed directly to the HTTP UserAgent when it does the request.
314              
315             C<<$args>> must contain which repository to use. Using one of these (from the ReviewBoard API documentation):
316              
317             * repository_path: The repository to create the review request against. If not specified, the DEFAULT_REPOSITORY_PATH setting will be used. If both this and repository_id are set, repository_path's value takes precedence.
318             * repository_id: The ID of the repository to create the review request against.
319              
320             Example:
321              
322             my $rb = WebService::ReviewBoard->new( 'http://demo.review-board.org' );
323             $rb->login( 'username', 'password' );
324             my $review = WebService::ReviewBoard::Request->create( { review_board => $rb }, [ repository_id => 1 ] );
325              
326             =item C<< fetch( $args_hash_ref ) >>
327              
328             Fetch one or more review objects from the reviewboard server. It uses wantarray(), so
329             you can fetch one or many.
330              
331             Valid values in C<<$args_hash_ref>>:
332              
333             * review_board - (required) must be a WebService::ReviewBoard object.
334             * from_user - the review board username of a person that submitted reviews
335             * id - the id of a review request
336              
337             C<<fetch>> is a constructor.
338              
339             =item C<< get_id() >>
340              
341             Returns the id of this review request
342              
343             =item C<< get_bugs() >>
344              
345             Returns an array.
346              
347             =item C<< get_reviewers() >>
348              
349             Returns an array.
350              
351             =item C<< get_summary() >>
352              
353             =item C<< get_description() >>
354              
355             =item C<< get_groups() >>
356              
357             =item C<< set_groups() >>
358              
359             =item C<< set_bugs( @bug_ids ) >>
360              
361             =item C<< set_reviewers( @review_board_users ) >>
362              
363             =item C<< set_summary( $summary ) >>
364              
365             =item C<< set_description( $description ) >>
366              
367             =item C<< add_diff( $diff_file ) >>
368              
369             C<< $diff_file >> should be a file that contains the diff that you want to be reviewed.
370              
371             =item C<< publish( ) >>
372              
373             Mark the review request as ready to be reviewed. This will send out notification emails if review board
374             is configured to do that.
375              
376             =item C<< discard_review_request() >>
377             Mark the review request as discarded. This will delete review request from review board.
378              
379             =item C<< submit_review_request() >>
380             Mark the review request as submitted.
381              
382             =item C<< as_string() >>
383              
384             returns a string that is a representation of the review request
385              
386             =item C<< get_ua() >>
387              
388             returns an LWP::UserAgent that will be used to do requests. You can overload this method to use custom user agents.
389              
390             =item C<< review_api_post() >>
391              
392             makes POST call to reviewboard and performs required action.
393              
394             =back
395              
396             =head1 DIAGNOSTICS
397              
398             =over
399              
400             =item C<< "create couldn't determine ID from this JSON that it got back from the server: %s" >>
401             =item C<< "new() missing review_board arg (WebService::ReviewBoard object)" >>
402             =item C<< "requested id, but id isn't set" >>
403             =item C<< "fetch() must get either from_user or id as an argument" >>
404             =item C<< "no review requests matching your critera were found" >>
405             =item C<< "requested $field, but $field isn't set" >>
406              
407             =back
408              
409             =head1 CONFIGURATION AND ENVIRONMENT
410              
411             C<< WebService::ReviewBoard::Review >> requires no configuration files or environment variables.
412              
413             =head1 DEPENDENCIES
414              
415             C<< WebService::ReviewBoard >>
416              
417             =head1 INCOMPATIBILITIES
418              
419             None reported.
420              
421             =head1 BUGS AND LIMITATIONS
422              
423             No bugs have been reported.
424              
425             Please report any bugs or feature requests to
426             C<bug-webservice-reviewboard@rt.cpan.org>, or through the web interface at
427             L<http://rt.cpan.org>.
428              
429             =head1 AUTHOR
430              
431             Jay Buffington C<< <jaybuffington@gmail.com> >>
432              
433             =head1 LICENCE AND COPYRIGHT
434              
435             Copyright (c) 2008, Jay Buffington C<< <jaybuffington@gmail.com> >>. All rights reserved.
436              
437             This module is free software; you can redistribute it and/or
438             modify it under the same terms as Perl itself. See L<perlartistic>.
439              
440             =head1 DISCLAIMER OF WARRANTY
441              
442             BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
443             FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
444             OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
445             PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
446             EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
447             WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
448             ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
449             YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
450             NECESSARY SERVICING, REPAIR, OR CORRECTION.
451              
452             IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
453             WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
454             REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
455             LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
456             OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
457             THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
458             RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
459             FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
460             SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
461             SUCH DAMAGES.