File Coverage

lib/App/TimeTracker/Command/RT.pm
Criterion Covered Total %
statement 29 65 44.6
branch 0 12 0.0
condition n/a
subroutine 10 17 58.8
pod 0 3 0.0
total 39 97 40.2


line stmt bran cond sub pod time code
1             package App::TimeTracker::Command::RT;
2 1     1   509 use strict;
  1         2  
  1         24  
3 1     1   3 use warnings;
  1         2  
  1         18  
4 1     1   12 use 5.010;
  1         3  
5              
6             our $VERSION = "3.000";
7             # ABSTRACT: App::TimeTracker RT plugin
8 1     1   371 use App::TimeTracker::Utils qw(error_message warning_message);
  1         8391  
  1         51  
9              
10 1     1   369 use Moose::Role;
  1         380867  
  1         4  
11 1     1   5123 use RT::Client::REST;
  1         68694  
  1         28  
12 1     1   393 use RT::Client::REST::Ticket;
  1         383994  
  1         36  
13 1     1   9 use Try::Tiny;
  1         2  
  1         51  
14 1     1   481 use Unicode::Normalize;
  1         1704  
  1         1114  
15              
16             has 'rt' => (
17             is => 'rw',
18             isa => 'TT::RT',
19             coerce => 1,
20             documentation => 'RT: Ticket number',
21             predicate => 'has_rt'
22             );
23             has 'rt_client' => (
24             is => 'ro',
25             isa => 'Maybe[RT::Client::REST]',
26             lazy_build => 1,
27             traits => ['NoGetopt'],
28             predicate => 'has_rt_client'
29             );
30             has 'rt_ticket' => (
31             is => 'ro',
32             isa => 'Maybe[RT::Client::REST::Ticket]',
33             lazy_build => 1,
34             traits => ['NoGetopt'],
35             );
36              
37             sub _build_rt_ticket {
38 0     0     my ($self) = @_;
39              
40 0 0         if ( my $ticket = $self->init_rt_ticket( $self->_current_task ) ) {
41 0           return $ticket;
42             }
43             }
44              
45             sub _build_rt_client {
46 0     0     my $self = shift;
47 0           my $config = $self->config->{rt};
48              
49 0 0         unless ($config) {
50 0           error_message("Please configure RT in your TimeTracker config");
51 0           return;
52             }
53              
54             return try {
55             my $client = RT::Client::REST->new(
56             server => $config->{server},
57             timeout => $config->{timeout},
58 0     0     );
59             $client->login(
60             username => $config->{username},
61 0           password => $config->{password} );
62 0           return $client;
63             }
64             catch {
65 0     0     error_message("Could not log in to RT: $_");
66 0           return;
67 0           };
68             }
69              
70             before [ 'cmd_start', 'cmd_continue', 'cmd_append' ] => sub {
71             my $self = shift;
72             return unless $self->has_rt;
73              
74             my $ticketname = 'RT' . $self->rt;
75             $self->insert_tag($ticketname);
76              
77             my $ticket;
78             if ( $self->rt_client ) {
79             $ticket = $self->rt_ticket;
80             if ( defined $ticket ) {
81             if ( defined $self->description ) {
82             $self->description(
83             sprintf(
84             '%s (%s)', $self->description, $ticket->subject
85             ) );
86             }
87             else {
88             $self->description( $ticket->subject );
89             }
90             }
91             }
92              
93             if ( $self->meta->does_role('App::TimeTracker::Command::Git') ) {
94             my $branch = $ticketname;
95             if ($ticket) {
96             my $subject = $self->safe_ticket_subject( $ticket->subject );
97             $branch .= '_' . $subject;
98             }
99             $self->branch($branch) unless $self->branch;
100             }
101             };
102              
103             after [ 'cmd_start', 'cmd_continue', 'cmd_append' ] => sub {
104             my $self = shift;
105             return unless $self->has_rt && $self->rt_client;
106              
107             my $ticket = $self->rt_ticket;
108              
109             return unless $ticket;
110             try {
111             my $do_store = 0;
112             if ( $self->config->{rt}{set_owner_to} ) {
113             if ( $ticket->owner() ne 'Nobody'
114             and $ticket->owner() ne $self->config->{rt}{set_owner_to} )
115             {
116             warning_message(
117             'Will not steal tickets, please do that via RT Web-UI');
118             return;
119             }
120             $ticket->owner( $self->config->{rt}{set_owner_to} );
121             $do_store = 1;
122             }
123              
124             my $status = $self->config->{rt}{set_status}{start};
125             if ( $status and $status ne $ticket->status ) {
126             $ticket->status($status);
127             $do_store = 1;
128             }
129             $ticket->store() if $do_store;
130             }
131             catch {
132             error_message( 'Could not set RT owner/status: %s', $_ );
133             };
134             };
135              
136             after 'cmd_stop' => sub {
137             my $self = shift;
138             return unless $self->rt_client;
139              
140             my $task = $self->_previous_task;
141             return unless $task;
142             my $task_rounded_minutes = $task->rounded_minutes;
143             return unless $task_rounded_minutes > 0;
144              
145             my $ticket = $self->init_rt_ticket($task);
146             if ( not $ticket ) {
147             say
148             "Last task did not contain a RT ticket id, not updating TimeWorked or Status.";
149             return;
150             }
151              
152             my $do_store = 0;
153             if ( $self->config->{rt}{update_time_worked} and $task_rounded_minutes ) {
154              
155             my $worked = $ticket->time_worked || 0;
156             $worked =~ s/\D//g
157             ; # RT stores in minutes, API give back a string like "x minutes"
158              
159             $ticket->time_worked( $worked + $task_rounded_minutes );
160             $do_store = 1;
161             }
162              
163             if ( $self->config->{rt}{update_time_left} and $ticket->time_left ) {
164             my $time_left = $ticket->time_left;
165             $time_left =~ s/\D//g
166             ; # RT stores in minutes, API give back a string like "x minutes"
167              
168             $ticket->time_left( $time_left - $task_rounded_minutes );
169             $do_store = 1;
170             }
171              
172             my $status = $self->config->{rt}{set_status}{stop};
173             # Do not change the configured stop status if it has been changed since starting the ticket
174             if ( defined $status
175             and $ticket->status() eq $self->config->{rt}{set_status}{start} )
176             {
177             $ticket->status($status);
178             $do_store = 1;
179             }
180             return unless $do_store;
181              
182             try {
183             $ticket->store;
184             }
185             catch {
186             error_message( 'Could not update ticket: %s', $_ );
187             };
188             };
189              
190             sub init_rt_ticket {
191 0     0 0   my ( $self, $task ) = @_;
192 0           my $id;
193 0 0         if ($task) {
    0          
194 0           $id = $task->rt_id;
195             }
196             elsif ( $self->rt ) {
197 0           $id = $self->rt;
198             }
199 0 0         return unless $id;
200              
201 0           my $rt_ticket = RT::Client::REST::Ticket->new(
202             rt => $self->rt_client,
203             id => $id,
204             );
205 0           $rt_ticket->retrieve;
206 0           return $rt_ticket;
207             }
208              
209             sub App::TimeTracker::Data::Task::rt_id {
210 0     0 0   my $self = shift;
211 0           foreach my $tag ( @{ $self->tags } ) {
  0            
212 0 0         next unless $tag =~ /^RT(\d+)/;
213 0           return $1;
214             }
215             }
216              
217             sub safe_ticket_subject {
218 0     0 0   my ( $self, $subject ) = @_;
219              
220 0           $subject = NFKD($subject);
221 0           $subject =~ s/\p{NonspacingMark}//g;
222 0           $subject =~ s/\W/_/g;
223 0           $subject =~ s/_+/_/g;
224 0           $subject =~ s/^_//;
225 0           $subject =~ s/_$//;
226 0           return $subject;
227             }
228              
229 1     1   627 no Moose::Role;
  1         1  
  1         10  
230             1;
231              
232             __END__
233              
234             =pod
235              
236             =encoding UTF-8
237              
238             =head1 NAME
239              
240             App::TimeTracker::Command::RT - App::TimeTracker RT plugin
241              
242             =head1 VERSION
243              
244             version 3.000
245              
246             =head1 DESCRIPTION
247              
248             This plugin takes a lot of hassle out of working with Best Practical's
249             RequestTracker available for free from
250             L<http://bestpractical.com/rt/>.
251              
252             It can set the description and tags of the current task based on data
253             entered into RT, set the owner of the ticket and update the
254             time-worked as well as time-left in RT. If you also use the C<Git> plugin, this plugin will
255             generate very nice branch names based on RT information.
256              
257             =head1 CONFIGURATION
258              
259             =head2 plugins
260              
261             Add C<RT> to the list of plugins.
262              
263             =head2 rt
264              
265             add a hash named C<rt>, containing the following keys:
266              
267             =head3 server [REQUIRED]
268              
269             The server name RT is running on.
270              
271             =head3 username [REQUIRED]
272              
273             Username to connect with. As the password of this user might be distributed on a lot of computer, grant as little rights as needed.
274              
275             =head3 password [REQUIRED]
276              
277             Password to connect with.
278              
279             =head3 timeout
280              
281             Time in seconds to wait for an connection to be established. Default: 300 seconds (via RT::Client::REST)
282              
283             =head3 set_owner_to
284              
285             If set, set the owner of the current ticket to the specified value during C<start> and/or C<stop>.
286              
287             =head3 update_time_worked
288              
289             If set, updates the time worked on this task also in RT.
290              
291             =head3 update_time_left
292              
293             If set, updates the time left property on this task also in RT using the time worked tracker value.
294              
295             =head1 NEW COMMANDS
296              
297             none
298              
299             =head1 CHANGES TO OTHER COMMANDS
300              
301             =head2 start, continue
302              
303             =head3 --rt
304              
305             ~/perl/Your-Project$ tracker start --rt 1234
306              
307             If C<--rt> is set to a valid ticket number:
308              
309             =over
310              
311             =item * set or append the ticket subject in the task description ("Rev up FluxCompensator!!")
312              
313             =item * add the ticket number to the tasks tags ("RT1234")
314              
315             =item * if C<Git> is also used, determine a save branch name from the ticket number and subject, and change into this branch ("RT1234_rev_up_fluxcompensator")
316              
317             =item * set the owner of the ticket in RT (if C<set_owner_to> is set in config)
318              
319             =item * updates the status of the ticket in RT (if C<set_status/start> is set in config)
320              
321             =back
322              
323             =head2 stop
324              
325             If <update_time_worked> is set in config, adds the time worked on this task to the ticket.
326             If <update_time_left> is set in config, reduces the time left on this task to the ticket.
327             If <set_status/stop> is set in config, updates the status of the ticket
328              
329             =head1 AUTHOR
330              
331             Thomas Klausner <domm@cpan.org>
332              
333             =head1 COPYRIGHT AND LICENSE
334              
335             This software is copyright (c) 2014 - 2019 by Thomas Klausner.
336              
337             This is free software; you can redistribute it and/or modify it under
338             the same terms as the Perl 5 programming language system itself.
339              
340             =cut