integrate RTx::Statistics package, part of merging spiritone RT changes (#1661)
authorivan <ivan>
Wed, 20 Jun 2007 22:28:28 +0000 (22:28 +0000)
committerivan <ivan>
Wed, 20 Jun 2007 22:28:28 +0000 (22:28 +0000)
52 files changed:
CREDITS
rt/html/Callbacks/ActivityReports/Elements/Tabs/Default [new file with mode: 0644]
rt/html/Callbacks/ActivityReports/NoAuth/webrt.css/Default [new file with mode: 0644]
rt/html/Callbacks/ActivityReports/Search/Results.html/SearchActions [new file with mode: 0644]
rt/html/Callbacks/RT-WebCronTool/Elements/Tabs/Default [new file with mode: 0644]
rt/html/Developer/CronTool/autohandler [new file with mode: 0644]
rt/html/Developer/CronTool/index.html [new file with mode: 0644]
rt/html/RTx/Statistics/CallsMultiQueue/Elements/Chart [new file with mode: 0755]
rt/html/RTx/Statistics/CallsMultiQueue/index.html [new file with mode: 0755]
rt/html/RTx/Statistics/CallsQueueDay/Elements/Chart [new file with mode: 0755]
rt/html/RTx/Statistics/CallsQueueDay/Results.tsv [new file with mode: 0644]
rt/html/RTx/Statistics/CallsQueueDay/index.html [new file with mode: 0755]
rt/html/RTx/Statistics/DayOfWeek/Elements/Chart [new file with mode: 0755]
rt/html/RTx/Statistics/DayOfWeek/index.html [new file with mode: 0755]
rt/html/RTx/Statistics/DurationAsString [new file with mode: 0755]
rt/html/RTx/Statistics/Elements/CollectionAsTable/Header [new file with mode: 0644]
rt/html/RTx/Statistics/Elements/CollectionAsTable/ParseFormat [new file with mode: 0644]
rt/html/RTx/Statistics/Elements/CollectionAsTable/Row [new file with mode: 0644]
rt/html/RTx/Statistics/Elements/ControlsAsTable/ControlBox [new file with mode: 0644]
rt/html/RTx/Statistics/Elements/ControlsAsTable/UpdatePage [new file with mode: 0644]
rt/html/RTx/Statistics/Elements/DateSelectRow [new file with mode: 0644]
rt/html/RTx/Statistics/Elements/DurationAsString [new file with mode: 0755]
rt/html/RTx/Statistics/Elements/GraphBox [new file with mode: 0644]
rt/html/RTx/Statistics/Elements/SelectMultiQueue [new file with mode: 0755]
rt/html/RTx/Statistics/Elements/StatColumnMap [new file with mode: 0644]
rt/html/RTx/Statistics/Elements/Tabs [new file with mode: 0755]
rt/html/RTx/Statistics/FAQ/index.html [new file with mode: 0644]
rt/html/RTx/Statistics/OpenStalled/Elements/Chart [new file with mode: 0755]
rt/html/RTx/Statistics/OpenStalled/Results.tsv [new file with mode: 0644]
rt/html/RTx/Statistics/OpenStalled/index.html [new file with mode: 0755]
rt/html/RTx/Statistics/Resolution/Elements/Chart [new file with mode: 0755]
rt/html/RTx/Statistics/Resolution/index.html [new file with mode: 0644]
rt/html/RTx/Statistics/TimeToResolve/Elements/Chart [new file with mode: 0755]
rt/html/RTx/Statistics/TimeToResolve/index.html [new file with mode: 0755]
rt/html/RTx/Statistics/UserTest/Elements/Chart [new file with mode: 0755]
rt/html/RTx/Statistics/UserTest/index.html [new file with mode: 0755]
rt/html/RTx/Statistics/index.html [new file with mode: 0755]
rt/html/Reports/Activity/ActivityDetail.html [new file with mode: 0644]
rt/html/Reports/Activity/ActivitySummary.html [new file with mode: 0644]
rt/html/Reports/Activity/Elements/LimitReport [new file with mode: 0644]
rt/html/Reports/Activity/Elements/MiniPlot [new file with mode: 0644]
rt/html/Reports/Activity/Elements/PrintFooter [new file with mode: 0644]
rt/html/Reports/Activity/Elements/PrintHeader [new file with mode: 0644]
rt/html/Reports/Activity/Elements/ScreenFooter [new file with mode: 0644]
rt/html/Reports/Activity/Elements/ScreenHeader [new file with mode: 0644]
rt/html/Reports/Activity/Elements/Tabs [new file with mode: 0644]
rt/html/Reports/Activity/Elements/Wrapper [new file with mode: 0644]
rt/html/Reports/Activity/ResolutionComments.html [new file with mode: 0644]
rt/html/Reports/Activity/ResolutionStatistics.html [new file with mode: 0644]
rt/html/Reports/Activity/index.html [new file with mode: 0644]
rt/lib/RT/Extension/ActivityReports.pm [new file with mode: 0644]
rt/lib/RTx/Statistics.pm [new file with mode: 0755]

diff --git a/CREDITS b/CREDITS
index 72b0491..b3ae265 100644 (file)
--- a/CREDITS
+++ b/CREDITS
@@ -121,8 +121,16 @@ Latex invoice template based on a template from eBills
 <http://ebills.sourceforge.net/> by Mark Asplen-Taylor <mark@asplen.co.uk>,
 licensed under the terms fo the GNU GPL.
 
 <http://ebills.sourceforge.net/> by Mark Asplen-Taylor <mark@asplen.co.uk>,
 licensed under the terms fo the GNU GPL.
 
-Contains "Request Tracker" <http://www.bestpractical.com/rt/> by Jesse
-Vincent <jesse@bestpractical.com> licensed under the terms of the GNU GPL.
+Contains "Request Tracker" <http://www.bestpractical.com/rt/> and 
+"RTx::Extension::ActivityReports" from Best Practical Solutions, licensed under
+the terms of the GNU GPL.
+
+Contains "RTx::Statistics Package"
+<http://wiki.bestpractical.com/view/RT3StatisticsPackage> from Kelly Hickel
+<kfh@mqsoftware.com>, licensed under the same terms as Perl (GPL/Artistic).
+
+Contains "RTx::WebCronTool" <http://search.cpan.org/dist/RTx-WebCronTool/> from
+Audrey Tang, licensed under the same terms as Perl (GPL/Artistic).
 
 #not yet used...
 # Contains "SQL Ledger" <http://www.sql-ledger.com/> by DWS Systems Inc. and
 
 #not yet used...
 # Contains "SQL Ledger" <http://www.sql-ledger.com/> by DWS Systems Inc. and
diff --git a/rt/html/Callbacks/ActivityReports/Elements/Tabs/Default b/rt/html/Callbacks/ActivityReports/Elements/Tabs/Default
new file mode 100644 (file)
index 0000000..f85d2e0
--- /dev/null
@@ -0,0 +1,7 @@
+<%init>
+if ($ARGS{current_toptab} eq "Tools/Offline.html") {
+    $ARGS{tabs}{r} ||= { path  => 'Reports/Activity/index.html',
+                         title => 'Reports',
+                       };
+}
+</%init>
\ No newline at end of file
diff --git a/rt/html/Callbacks/ActivityReports/NoAuth/webrt.css/Default b/rt/html/Callbacks/ActivityReports/NoAuth/webrt.css/Default
new file mode 100644 (file)
index 0000000..30480f7
--- /dev/null
@@ -0,0 +1,71 @@
+table.miniplot {
+    width: 100%;
+    border-collapse: collapse;
+}
+table.miniplot td {
+    margin: 0;
+    padding: 0;
+    border-bottom: 1px solid black;
+}
+table.miniplot .graph {
+    margin-left: auto;
+    margin-right: auto;
+    position: relative;
+    width: 60px;
+}
+table.miniplot .graph ul { 
+    height: 100px; 
+    margin: 0;
+    padding: 0;
+}
+table.miniplot .graph ul li {  
+    list-style: none;
+    position: absolute; 
+    bottom: 0px; 
+    padding: 0 !important; 
+    margin: 0 !important; 
+    border-bottom: none;
+}
+table.miniplot .graph ul li .data {
+    display: none;
+}
+
+.miniplot .demoblock { margin: 0 10px; padding: 0 30px; }
+
+.miniplot .c1 { border: 2px solid #990000; background: #ff0000; }
+.miniplot .c2 { border: 2px solid #996600; background: #ff9900; }
+.miniplot .c3 { border: 2px solid #009900; background: #00ff00; }
+.miniplot .c4 { border: 2px solid #009999; background: #00ffff; }
+.miniplot .c5 { border: 2px solid #000099; background: #0000ff; }
+.miniplot .c6 { border: 2px solid #990099; background: #ff00ff; }
+graph .c5 { border: 2px solid #000099; background: #0000ff; }
+.graph .c6 { border: 2px solid #990099; background: #ff00ff; }
+
+tr.titlerow  th { 
+
+  border-bottom: solid black 1px;
+  margin: 0;
+ font-size:80%;
+  text-wrap:  none;
+
+}
+
+tr.grandtotal td{
+    border-top: 1px solid black;
+}
+
+tr.grandtotal th{
+    border-top: 1px solid black;
+}
+
+th.label { 
+ align: left;
+
+}
+
+table.miniplot th.legend {
+ font-style: normal;
+ font-size: 80%;
+
+}
+
diff --git a/rt/html/Callbacks/ActivityReports/Search/Results.html/SearchActions b/rt/html/Callbacks/ActivityReports/Search/Results.html/SearchActions
new file mode 100644 (file)
index 0000000..4775a9a
--- /dev/null
@@ -0,0 +1,7 @@
+<a href="<% $RT::WebPath %>/Reports/Activity/index.html?<% $QueryString %>">Generate reports</a>
+<%init>
+use YAML;
+my %args = $m->caller_args(2);
+
+my $QueryString = $m->comp('/Elements/QueryString', query => $args{Query});
+</%init>
\ No newline at end of file
diff --git a/rt/html/Callbacks/RT-WebCronTool/Elements/Tabs/Default b/rt/html/Callbacks/RT-WebCronTool/Elements/Tabs/Default
new file mode 100644 (file)
index 0000000..db74ced
--- /dev/null
@@ -0,0 +1,13 @@
+%# The day after tomorrow is the third day of the rest of your life.
+<%INIT>
+if ($session{'CurrentUser'}->UserObj->HasRight(
+    Right => 'SuperUser',
+    Object => $RT::System,
+)) {
+    $toptabs->{'ZZ-RT-WebCronTool'} = { title =>loc("Web CronTool"),
+                          path  => "Developer/CronTool/index.html" };
+}
+</%init>
+<%args>
+$toptabs =>undef
+</%args>
diff --git a/rt/html/Developer/CronTool/autohandler b/rt/html/Developer/CronTool/autohandler
new file mode 100644 (file)
index 0000000..7daa09e
--- /dev/null
@@ -0,0 +1,9 @@
+%# All theoretical chemistry is really physics;
+%# and all theoretical chemists know it.
+%#             -- Richard P. Feynman
+<%INIT>
+$m->call_next(%ARGS) if $session{'CurrentUser'}->UserObj->HasRight(
+    Right => 'SuperUser',
+    Object => $RT::System,
+);
+</%INIT>
diff --git a/rt/html/Developer/CronTool/index.html b/rt/html/Developer/CronTool/index.html
new file mode 100644 (file)
index 0000000..67c9e56
--- /dev/null
@@ -0,0 +1,116 @@
+% if ($@) {\r
+<P><FONT Color="red"><% $@ %></FONT></P>\r
+% }\r
+% if (!$NoUI) {\r
+<HR>\r
+<FORM Action="index.html" Method="POST">\r
+<TABLE>\r
+% foreach my $class (qw( Search Condition Action )) {\r
+<TR><TH>\r
+<% loc($class) %>\r
+</TH><TD>\r
+<SELECT NAME="<% $class %>">\r
+%  require File::Find;\r
+%  my @modules;\r
+%  File::Find::find(sub {\r
+%    push @modules, $1 if /^(?!Generic|UserDefined)(\w+)\.pm$/i;\r
+%  }, grep -d, map "$_/RT/$class", @INC);\r
+<OPTION <% $ARGS{$class} ? '' : 'SELECTED' %>></OPTION>\r
+%  foreach my $module (sort @modules) {\r
+%    my $fullname = "RT::$class\::$module";\r
+    <OPTION VALUE="<% $fullname %>" <% ($fullname eq $ARGS{$class}) ? 'SELECTED' : '' %>><% $module %></OPTION>\r
+%  }\r
+</SELECT>\r
+</TD><TH>\r
+<&|/l&>Parameter</&>\r
+</TH><TD>\r
+<INPUT NAME="<% $class %>Arg" VALUE="<% $ARGS{$class.'Arg'} %>">\r
+</TD></TR>\r
+% }\r
+<TR>\r
+<TD COLSPAN="4" ALIGN="Right">\r
+<LABEL>\r
+<INPUT TYPE="CheckBox" NAME="Verbose" <% $Verbose ? 'CHECKED' : '' %>><&|/l&>Verbose</&>\r
+</LABEL>\r
+<INPUT TYPE="Submit" VALUE="<&|/l&>Run</&>">\r
+</TD>\r
+</TABLE>\r
+</FORM>\r
+<HR>\r
+% }\r
+<%INIT>\r
+$m->print("<H1>", loc("Web CronTool"), "</H1>");\r
+if ($Search) {\r
+    my $load_module = sub {\r
+       my $modname = $_[0];\r
+       $modname =~ s{::}{/}g;\r
+       require "$modname.pm" or die (\r
+           loc( "Failed to load module [_1]. ([_2])", $_[0], $@ ) . "\n"\r
+       );\r
+    };\r
+    $m->print(loc("Starting..."), "<UL>");\r
+    eval {\r
+       $load_module->($Search);\r
+       $load_module->($Action) if $Action;\r
+       $load_module->($Condition) if $Condition;\r
+\r
+       if ($TemplateId and !$TemplateObj) {\r
+           $TemplateObj = RT::Template->new($RT::Nobody);\r
+           $TemplateObj->LoadById($TemplateId);\r
+       }\r
+\r
+       my $tickets = RT::Tickets->new($RT::SystemUser);\r
+       my $search  = $Search->new( TicketsObj => $tickets, Argument => $SearchArg );\r
+       $search->Prepare;\r
+       my $tickets_found = $search->TicketsObj;\r
+\r
+       #for each ticket we've found\r
+       while ( my $ticket = $tickets_found->Next ) {\r
+           $m->print("<LI>" . $ticket->Id . ": ") if $Verbose;\r
+           $m->print(loc("Checking...")) if $Verbose;\r
+\r
+           # perform some more advanced check\r
+           if ($Condition) {\r
+               my $ConditionObj = $Condition->new(\r
+                   TicketObj => $ticket,\r
+                   Argument  => $ConditionArg\r
+               );\r
+\r
+               # if the condition doesn't apply, get out of here\r
+               next unless ( $ConditionObj->IsApplicable );\r
+               $m->print(loc("Condition matches...")) if $Verbose;\r
+           }\r
+\r
+           if ($Action) {\r
+               #prepare our action\r
+               my $ActionObj = $Action->new(\r
+                   TicketObj => $ticket,\r
+                   TemplateObj => $TemplateObj,\r
+                   Argument  => $ActionArg\r
+               );\r
+\r
+               #if our preparation, move onto the next ticket\r
+               next unless ( $ActionObj->Prepare );\r
+               $m->print(loc("Action prepared...")) if $Verbose;\r
+\r
+               #commit our action.\r
+               next unless ( $ActionObj->Commit );\r
+               $m->print(loc("Action committed.")) if $Verbose;\r
+           }\r
+       }\r
+    };\r
+    $m->print('</UL>', loc("Finished."));\r
+}\r
+</%INIT>\r
+<%ARGS>\r
+$Search => undef\r
+$SearchArg => undef\r
+$Condition => undef\r
+$ConditionArg => undef\r
+$Action => undef\r
+$ActionArg => undef\r
+$TemplateId => undef\r
+$TemplateObj => undef\r
+$Verbose => 1\r
+$NoUI => 0\r
+</%ARGS>\r
diff --git a/rt/html/RTx/Statistics/CallsMultiQueue/Elements/Chart b/rt/html/RTx/Statistics/CallsMultiQueue/Elements/Chart
new file mode 100755 (executable)
index 0000000..02a183b
--- /dev/null
@@ -0,0 +1,39 @@
+<%perl>
+$r->content_type("image/$format");
+print $graph->plot(\@data)->$format();
+$m->abort();
+</%perl>
+<em><&|/l, $#data+1&>[_1] Plot Elements</&></em><p>
+% foreach my $value (@data) {
+<% $value %><p>
+% }
+<em><&|/l&>x_labels</&>:</em><p>
+<% $ARGS{x_labels} %>
+<p>
+<em><&|/l&>legend</&>:</em><p>
+<% $ARGS{set_legend} %>
+<p>
+<em><&|/l, (keys %ARGS) - 2&>[_1] data sets</&>:</em><p>
+
+% for (1..(scalar keys %ARGS)-2) {
+<% $_ %> <% $ARGS{"data$_"} %><p>
+% }
+
+<%INIT>
+use GD::Graph::lines;
+
+my @data;
+my $graph = GD::Graph::lines->new($Statistics::GraphWidth,$Statistics::GraphHeight);
+$graph->set(export_format => "png",
+            x_label       => 'Day of Week',
+            y_label       => 'Tickets per day');
+$graph->set_legend(split /,/ , $ARGS{set_legend});
+my $format = $graph->export_format;
+push @data, [split /,/ , $ARGS{x_labels}];
+for (1..((scalar keys %ARGS)-2)) {
+  push @data, [split /,/  , $ARGS{"data".$_}];
+}
+
+</%INIT>
+<%ARGS>
+</%ARGS>
diff --git a/rt/html/RTx/Statistics/CallsMultiQueue/index.html b/rt/html/RTx/Statistics/CallsMultiQueue/index.html
new file mode 100755 (executable)
index 0000000..abf8aa7
--- /dev/null
@@ -0,0 +1,330 @@
+<& /Elements/Header, Title => loc('Tickets per day in Multiple queues') &>
+<& /RTx/Statistics/Elements/Tabs, Title => loc('Tickets per day in Multiple Queues by status') &>
+
+<h3>Description</h3>
+<p>This chart shows details of tickets per day by their status. You can select multiple queues to display at the same time, but only one status. You can chose any of the defined status values. 
+There is also the option to display all available queues at the same time.
+The default display shows tickets resolved in your default queue (General unless altered locally).
+The line chart below shows the same information in a graphical form.
+
+<br />
+
+<form method="POST" action="index.html">
+
+%# Build Legend
+% my @legend;
+% for (sort keys %queues_to_show) {
+%   push @legend, $_;
+% }
+
+%my $title = "Tickets with Status $status in " . join(', ', @queues) . ", per day from " .
+%        Statistics::FormatDate($Statistics::PerDayDateFormat, $dates[0]) . " through " .
+%        Statistics::FormatDate($Statistics::PerDayDateFormat, $dates[$#dates-1]);
+
+<& /Elements/TitleBoxStart, title => $title, title_href => "/RTx/Statistics/OpenStalled/index.html?$QueryString"&>
+<TABLE BORDER=0 cellspacing=0 cellpadding=1 WIDTH="100%">
+% if ($ShowHeader) {
+<& /RTx/Statistics/Elements/CollectionAsTable/Header, 
+    Format => \@RowFormat, 
+    FormatString => $RowFormat,
+    AllowSorting => $AllowSorting, 
+    Order => $Order, 
+    Query => undef,
+    Rows => $Rows,
+    Page => $Page,
+    OrderBy => $OrderBy , 
+    BaseURL => $BaseURL,
+    maxitems => $maxitems &> 
+% }
+% my $line = 0;
+% LINE: for my $d (0..$#dates) {
+%   if ($d == $#dates ){
+%     next LINE;
+%   }
+%   $line++;
+%   my $x = 1;
+%   $values{Statistics_Date} = Statistics::FormatDate($dateformat, $dates[$d]);
+%   my $row_total=0;
+%   foreach my $q (sort keys %queues_to_show) {
+%     my $tix = new RT::Tickets($session{'CurrentUser'});
+%     if ($status eq "resolved") {
+%       $tix->LimitStatus(VALUE => $status);
+%       $tix->LimitResolved(VALUE => $dates[$d]->ISO, OPERATOR => ">=");
+%       if ($dates[$d+1]) {
+%         $tix->LimitResolved(VALUE => $dates[$d+1]->ISO, OPERATOR => "<=");
+%       }
+%     } 
+%     elsif ($status eq "new") {
+%       $tix->LimitCreated(VALUE => $dates[$d]->ISO, OPERATOR => ">=");
+%       if ($dates[$d+1]) {
+%         $tix->LimitCreated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<=");
+%       }
+%     } 
+%     elsif ($status eq "deleted") {
+%       $tix->LimitStatus(VALUE => $status);
+%       $tix->LimitLastUpdated(VALUE => $dates[$d]->ISO, OPERATOR => ">=");
+%       if ($dates[$d+1]) {
+%         $tix->LimitLastUpdated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<=");
+%       }
+%     }
+%     elsif ($status eq "stalled") {
+%       $tix->LimitStatus(VALUE => $status);
+%       $tix->LimitLastUpdated(VALUE => $dates[$d]->ISO, OPERATOR => ">=");
+%       if ($dates[$d+1]) {
+%         $tix->LimitLastUpdated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<=");
+%       }
+%     }
+%     elsif ($status eq "open") {
+%       $tix->LimitStatus(VALUE => $status);
+%       $tix->LimitLastUpdated(VALUE => $dates[$d]->ISO, OPERATOR => ">=");
+%       if ($dates[$d+1]) {
+%         $tix->LimitLastUpdated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<=");
+%       }
+%     }
+%     elsif ($status eq "rejected") {
+%       $tix->LimitStatus(VALUE => $status);
+%       $tix->LimitLastUpdated(VALUE => $dates[$d]->ISO, OPERATOR => ">=");
+%       if ($dates[$d+1]) {
+%         $tix->LimitLastUpdated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<=");
+%       }
+%     }
+%     $tix->LimitQueue (VALUE => $q);
+%     $values{$q} = $tix->Count;
+%     $row_total += $tix->Count;
+%     $data[$x++][$d] = $tix->Count;
+%   }
+%   $values{Statistics_Totals} = $row_total;
+<&  /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@RowFormat, i => $line, record => $record, maxitems => $maxitems &>
+% }
+</table>
+<& /Elements/TitleBoxEnd&>
+
+<hr>
+
+<BR />
+<BR />
+
+<!--    <td>Show:</td>
+    <td COLSPAN=2><SELECT NAME="status">
+% for (qw(resolved new deleted stalled rejected open)) {
+    <OPTION VALUE="<% $_ %>" <% $_ eq $status && "SELECTED" %>>
+    <% loc($_) %></OPTION>
+% }
+--!>
+
+<%perl>
+# Create the graph URL
+my $url = 'Elements/Chart?x_labels=';
+#$url .= join ",", @{ shift @data } . "&";
+for (0..$max) {
+     $url .=  $m->interp->apply_escapes($data[0][$_],'u') . ",";
+}
+chop $url;
+$url .= "&";
+shift @data;
+$url .=  'set_legend='.(join ",", @legend)."&";
+for (0..$#data) {
+  $url .= "data".(1+$_)."=". (join ",", @{$data[$_]})."&";
+}
+chop $url;
+</%perl>
+
+<& /RTx/Statistics/Elements/GraphBox, GraphURL => $url &>
+
+<& /RTx/Statistics/Elements/ControlsAsTable/ControlBox, 
+         Title => "Change Status, Queues or Dates", 
+         ShowDates => 1, sMonth => \$sMonth, sDay => \$sDay, sYear => \$sYear,
+                         eMonth => \$eMonth, eDay => \$eDay, eYear => \$eYear,
+                         weekends => $weekends,
+         ShowMultiQueues => 1, queues_ref => \@queues,
+        ShowStatus => 1, Status => $status
+ &>
+
+</form>
+
+<a href="<%$RT::WebPath%>/RTx/Statistics/CallsMultiQueue/index.html?<% $QueryString %>"><&|/l&>Bookmarkable link</&></a>
+%# | <a href="<%$RT::WebPath%>/RTx/Statistics/CallsMultiQueue/Results.tsv?<%$QueryString%>"><&|/l&>spreadsheet</&></a>
+<BR>
+<BR>
+
+<%ARGS>
+$status => $Statistics::MultiQueueStatus
+$max => $Statistics::MultiQueueMaxRows
+@queues => @Statistics::MultiQueueQueueList
+$weekends => $Statistics::PerDayWeekends;
+$sMonth=>undef
+$sDay=>undef
+$sYear=>undef
+$eMonth=>undef
+$eDay=>undef
+$eYear=>undef
+$days=>undef
+$dateformat => $Statistics::MultiQueueDateFormat
+$currentMonth=>undef
+
+$AllowSorting => undef
+$Order => undef
+$OrderBy => undef
+$ShowNavigation => 1
+$ShowHeader => 1
+$Rows => 50
+$Page => 1
+$BaseURL => undef
+$AddAllCheck => undef
+</%ARGS>
+
+<%INIT>
+
+use RTx::Statistics;
+use Time::Local;
+my $n = 0;
+my @data = ([]);
+my @dates;
+my @msgs;
+my $selected;
+my $diff;
+my %queues_to_show;
+my $secsPerDay=86400;
+my $sEpoch;
+my $eEpoch;
+my $QueryString;
+my $maxitems;
+my $RowFormat;
+my $BoldRowFormat;
+my %record;
+my %values;
+my $record = \%record;
+
+$record{values} = \%values;
+
+Statistics::DebugClear();
+Statistics::DebugLog("CallsQueueDay/index.html ARGS:\n");
+for my $key (keys %ARGS) {
+  Statistics::DebugLog("ARG{ $key }=" . $ARGS{$key} . "\n");
+}
+
+
+  # Handle the Add All Checkbox
+  if($AddAllCheck eq "on") {
+    $AddAllCheck = undef;
+    undef (@queues);
+    my $q=new RT::Queues($session{'CurrentUser'});
+    $q->UnLimit;
+    while (my $queue=$q->Next) {
+      next if !$queue->CurrentUserHasRight('SeeQueue');
+      push @queues, $queue->Name;
+    }
+  }
+
+  # If the user has the right to see the queue, put it into the map
+  for my $q (@queues) {
+      my $Queueobj = new RT::Queue($session{'CurrentUser'});
+      $Queueobj->Load($q);
+      next if !$Queueobj->CurrentUserHasRight('SeeQueue');
+      $queues_to_show{$q} = 1;
+  }
+
+  $maxitems = (scalar @queues) + 2;
+
+  # Build the format strings
+  $RowFormat = "'__Statistics_Date__'";
+  $BoldRowFormat = "'<B>__Statistics_Date__</B>'";
+  for my $q (@queues) {
+      $RowFormat .= ",'__Statistics_Dynamic__/KEY:$q/TITLE:$q/STYLE:text-align:right;'";
+      $BoldRowFormat .= ",'<B>__Statistics_Dynamic__</B>/KEY:$q/TITLE:$q/STYLE:text-align:right;'";
+  }
+  $RowFormat .= ",'<B>__Statistics_Totals__</B>/STYLE:text-align:right;'";
+  $BoldRowFormat .= ",'<B>__Statistics_Totals__</B>/STYLE:text-align:right;'";
+  # Parse the formats into structures.
+  my (@RowFormat) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $RowFormat);
+  my (@BoldRowFormat) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $BoldRowFormat);
+
+if ($sDay > $Statistics::monthsMaxDay{$sMonth}) {
+  $sDay = $Statistics::monthsMaxDay{$sMonth};
+}
+
+if ($eDay > $Statistics::monthsMaxDay{$eMonth}) {
+  $eDay = $Statistics::monthsMaxDay{$eMonth};
+}
+
+if ($sYear){
+       $sEpoch = timelocal(0, 0, 0, $sDay, $sMonth, $sYear-1900);
+}
+if ($eYear){
+Statistics::DebugLog("eMonth = " . $eMonth . "\n");
+       $eEpoch = timelocal(0, 0, 0, $eDay, $eMonth, $eYear-1900);
+} else {
+        # This case happens when the page is first loaded
+       my @local = localtime(time);
+       ($eDay, $eMonth, $eYear) = ($local[3], $local[4], $local[5]);
+       $eYear += 1900; 
+       $eEpoch = timelocal(0, 0, 0, $local[3], $local[4], $local[5], $local[6], $local[7], $local[8]);
+Statistics::DebugLog("Setting eEpoch=$eEpoch from current time.\n");
+}
+
+if (($eEpoch < $sEpoch) || ($sEpoch == 0)) {
+    # We have an end, but not a start, or, overlapping.
+    
+    # if $currentMonth is set, just set the day to 1
+    if($currentMonth) {
+      # set start vars from end, but with day set to 1
+      (undef, undef, undef, $sDay, $sMonth, $sYear) = localtime($eEpoch);
+      $sDay=1;
+      $sEpoch = timelocal(0, 0, 0, $sDay, $sMonth, $sYear);
+    } else {
+      # If the user has specified how many days back to go, use that,
+      # If not, set start to configured default period before end
+      if(defined $days) {
+        $sEpoch = $eEpoch - ($days * $Statistics::secsPerDay);
+      } else {
+        $sEpoch = $eEpoch - ($Statistics::PerDayPeriod * $Statistics::secsPerDay);
+      }
+      (undef, undef, undef, $sDay, $sMonth, $sYear) = localtime($sEpoch);
+    }
+    $sYear += 1900;
+}
+
+# Compute days to chart.
+# The +1 is because we need to generate one more date. If the user
+# selected a 10 day range, we need to generate 11 days.
+$diff = int(($eEpoch - $sEpoch + $Statistics::secsPerDay - 1) / $Statistics::secsPerDay)+1;
+Statistics::DebugLog("Setting diff=$diff\n");
+
+Statistics::DebugLog("sEpoch=$sEpoch, components=" . join(',', localtime($sEpoch)) . "\n");
+Statistics::DebugLog("eEpoch=$eEpoch, components=" . join(',', localtime($eEpoch)) . "\n");
+
+# Build the new query string
+$QueryString = "queues=" . join("&queues=", @queues);
+$QueryString .= "&sDay=$sDay&sMonth=$sMonth&sYear=$sYear&eDay=$eDay&eMonth=$eMonth&eYear=$eYear&weekends=$weekends";
+
+
+
+
+# Set up the end date to be midnight(morning) of the date after the one the user wanted.
+my $endRange = $eEpoch + $Statistics::secsPerDay;
+$n = 0;
+until ($#dates == $diff) {
+    my $date = new RT::Date($session{CurrentUser});
+    $date->Set(Value=>$endRange - $n, Format => 'unix');
+    # Note: we used to adjust the time to local midnight, but
+    # none of the other date entry fields in RT seem to adjust, so we've stopped.
+    #Statistics::DebugLog("Before adjust to midnight date " . Statistics::FormatDate("%c", $date) . "\n");
+    $n+= $Statistics::secsPerDay;
+    # If we aren't showing weekends and this is one, decrement the number
+    # of days to show and skip to the next date.
+    if(!$weekends and Statistics::RTDateIsWeekend($date)) {$diff--; next;}
+    unshift @dates, $date;
+Statistics::DebugLog("pushing date " . Statistics::FormatDate("%c", $date) . "\n");
+    unshift @{ $data[0] }, Statistics::FormatDate($Statistics::PerDayLabelDateFormat, $date);
+}
+
+# We put an extra day into the lists to cover up till midnight of the next day,
+# But we don't want that to appear in the labels, so pop it off.
+pop( @{ $data[0] } );
+
+my $queue = new RT::Queues($session{CurrentUser});
+$queue->UnLimit;
+
+my $QueueObj = new RT::Queue($session{'CurrentUser'});
+$QueueObj->Load($queue);
+</%INIT>
diff --git a/rt/html/RTx/Statistics/CallsQueueDay/Elements/Chart b/rt/html/RTx/Statistics/CallsQueueDay/Elements/Chart
new file mode 100755 (executable)
index 0000000..9a3a505
--- /dev/null
@@ -0,0 +1,29 @@
+<%perl>
+$r->content_type("image/$format");
+print $graph->plot(\@data)->$format();
+$m->abort();
+print $#data+1 . " Elements:<p>";
+for (0..$#data) {
+print $data[$_];
+print "<p>";
+}
+</%perl>
+<%INIT>
+use GD::Graph::lines;
+
+my @data;
+my $graph = GD::Graph::lines->new($Statistics::GraphWidth,$Statistics::GraphHeight);
+$graph->set(export_format => "png",
+            x_label       => 'Day of Week',
+            y_label       => 'Tickets per Day',
+           x_labels_vertical => 1,
+       );
+my $format = $graph->export_format;
+$graph->set_legend(split /,/ , $ARGS{set_legend});
+push @data, [split /,/ , $ARGS{x_labels}];
+push @data, [split /,/ , $ARGS{data1}];
+push @data, [split /,/ , $ARGS{data2}];
+push @data, [split /,/ , $ARGS{data3}];
+</%INIT>
+<%ARGS>
+</%ARGS>
diff --git a/rt/html/RTx/Statistics/CallsQueueDay/Results.tsv b/rt/html/RTx/Statistics/CallsQueueDay/Results.tsv
new file mode 100644 (file)
index 0000000..23f0c69
--- /dev/null
@@ -0,0 +1,191 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%# 
+%# COPYRIGHT:
+%#  
+%# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC 
+%#                                          <jesse@bestpractical.com>
+%# 
+%# (Except where explicitly superseded by other copyright notices)
+%# 
+%# 
+%# LICENSE:
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+%# 
+%# 
+%# CONTRIBUTION SUBMISSION POLICY:
+%# 
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%# 
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%# 
+%# END BPS TAGGED BLOCK }}}
+<%ARGS>
+$Queue => undef
+$weekends => $Statistics::PerDayWeekends;
+$sMonth=>undef
+$sDay=>undef
+$sYear=>undef
+$eMonth=>undef
+$eDay=>undef
+$eYear=>undef
+$days=>undef
+$currentMonth=>undef
+</%ARGS>
+
+<%INIT>
+use RTx::Statistics;
+use Time::Local;
+my @dates;
+my $n = 0;
+my %Totals;
+my $now = new RT::Date($session{CurrentUser});
+my $sEpoch;
+my $eEpoch;
+
+if (!defined $Queue) {
+  $Queue = $Statistics::PerDayQueue;
+}
+
+if ($sDay > $Statistics::monthsMaxDay{$sMonth}) {
+  $sDay = $Statistics::monthsMaxDay{$sMonth};
+}
+
+if ($eDay > $Statistics::monthsMaxDay{$eMonth}) {
+  $eDay = $Statistics::monthsMaxDay{$eMonth};
+}
+
+if ($sYear){
+       $sEpoch = timelocal(0, 0, 0, $sDay, $sMonth, $sYear-1900);
+}
+if ($eYear){
+Statistics::DebugLog("eMonth = " . $eMonth . "\n");
+       $eEpoch = timelocal(0, 0, 0, $eDay, $eMonth, $eYear-1900);
+} else {
+        # This case happens when the page is first loaded
+       my @local = localtime(time);
+       ($eDay, $eMonth, $eYear) = ($local[3], $local[4], $local[5]);
+       $eYear += 1900; 
+       $eEpoch = timelocal(0, 0, 0, $local[3], $local[4], $local[5], $local[6], $local[7], $local[8]);
+Statistics::DebugLog("Setting eEpoch=$eEpoch from current time.\n");
+}
+
+if (($eEpoch < $sEpoch) || ($sEpoch == 0)) {
+    # We have an end, but not a start, or, overlapping.
+    
+    # if $currentMonth is set, just set the day to 1
+    if($currentMonth) {
+      # set start vars from end, but with day set to 1
+      (undef, undef, undef, $sDay, $sMonth, $sYear) = localtime($eEpoch);
+      $sDay=1;
+      $sEpoch = timelocal(0, 0, 0, $sDay, $sMonth, $sYear);
+    } else {
+      # If the user has specified how many days back to go, use that,
+      # If not, set start to configured default period before end
+      if(defined $days) {
+        $sEpoch = $eEpoch - ($days * $Statistics::secsPerDay);
+      } else {
+        $sEpoch = $eEpoch - ($Statistics::PerDayPeriod * $Statistics::secsPerDay);
+      }
+      (undef, undef, undef, $sDay, $sMonth, $sYear) = localtime($sEpoch);
+    }
+    $sYear += 1900;
+}
+
+# set content type
+$r->content_type('application/vnd.ms-excel');
+
+# Put out some data about the generation of this file
+$m->out("Tickets per day for Queue:\t" . $Queue . "\tGenerated at:\t" . Statistics::FormatDate("%x %X", $now). "\n\n");
+
+
+# Compute days to chart.
+# The +1 is because we need to generate one more date. If the user
+# selected a 10 day range, we need to generate 11 days.
+my $diff = int(($eEpoch - $sEpoch + $Statistics::secsPerDay - 1) / $Statistics::secsPerDay)+1;
+
+# Build array of dates
+my $endRange = $eEpoch + $Statistics::secsPerDay;
+my $QueueObj = new RT::Queue($session{'CurrentUser'});
+$QueueObj->Load($Queue);
+until ($#dates == $diff) {
+    my $date = new RT::Date($session{CurrentUser});
+    $date->Set(Value=>$endRange - $n, Format => 'unix');
+    # Note: we used to adjust the time to local midnight, but
+    # none of the other date entry fields in RT seem to adjust, so we've stopped.
+    #Statistics::DebugLog("Before adjust to midnight date " . Statistics::FormatDate("%c", $date) . "\n");
+    $n+= $Statistics::secsPerDay;
+    # If we aren't showing weekends and this is one, decrement the number
+    # of days to show and skip to the next date.
+    if(!$weekends and Statistics::RTDateIsWeekend($date)) {$diff--; next;}
+    unshift @dates, $date;
+}
+
+# Output header row
+$m->out("Date\tcreate\tresolved\tdeleted\n");
+
+
+LINE: for my $d (0..$#dates) {
+  if ($d == $#dates){
+    next LINE;
+  }
+  my $x = 1;
+  # Output the date for this row
+  $m->out(Statistics::FormatDate($Statistics::PerDayDateFormat, $dates[$d]));
+  
+  # output the 3 columns for this row
+  for my $status (qw(created resolved deleted)) {
+    my $tix = new RT::Tickets($session{'CurrentUser'});
+    if ($status eq "created") {
+      $tix->LimitCreated(VALUE => $dates[$d]->ISO, OPERATOR => ">=");
+      if ($dates[$d+1]) {
+        $tix->LimitCreated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<=");
+      }
+    } elsif ($status eq "resolved") {
+      $tix->LimitStatus(VALUE => $status);
+      $tix->LimitResolved(VALUE => $dates[$d]->ISO, OPERATOR => ">=");
+      if ($dates[$d+1]) {
+         $tix->LimitResolved(VALUE => $dates[$d+1]->ISO, OPERATOR => "<=");
+      }
+    } elsif ($status eq "deleted") {
+      $tix->LimitStatus(VALUE => $status);
+      $tix->LimitLastUpdated(VALUE => $dates[$d]->ISO, OPERATOR => ">=");
+      if ($dates[$d+1]) {
+        $tix->LimitLastUpdated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<=");
+      }
+    }
+    $tix->LimitQueue (VALUE => $Queue);
+    $m->out( "\t" . $tix->Count ); 
+    $Totals{$status} += $tix->Count;
+  }
+  $m->out("\n");
+}
+
+# Output the totals
+$m->out("Totals\t$Totals{created}\t$Totals{resolved}\t$Totals{deleted}\n");
+
+$m->abort();
+</%INIT>
diff --git a/rt/html/RTx/Statistics/CallsQueueDay/index.html b/rt/html/RTx/Statistics/CallsQueueDay/index.html
new file mode 100755 (executable)
index 0000000..06fc484
--- /dev/null
@@ -0,0 +1,275 @@
+<& /Elements/Header, Title => loc("Tickets per day in Queue:" . $QueueObj->Name()) &>
+<& /RTx/Statistics/Elements/Tabs,  Title => loc("Tickets by status per day in Queue:" . $QueueObj->Name()) &>
+
+<h3>Description</h3>
+<p>This page displays details about tickets in the selected queue over the date range chosen. It shows how many tickets were created on
+each day in the chosen range, and how many of those were either Resolved or Deleted.</p>
+<p>To always show the current month to date, bookmark this <a href="<%$RT::WebPath%>/RTx/Statistics/CallsQueueDay/index.html?currentMonth=1">link</a>, or 
+for a spreadsheet, use this <a href="<%$RT::WebPath%>/RTx/Statistics/CallsQueueDay/Results.tsv?currentMonth=1">link</a>.</p>
+
+<form method="POST" action="index.html">
+
+% Statistics::DebugLog("queue name=" . $QueueObj->Name() . "\n");
+
+%my $title = "Ticket counts in " . $QueueObj->Name() . " by status per day from " . 
+%        Statistics::FormatDate($Statistics::PerDayDateFormat, $dates[0]) . " through " .
+%        Statistics::FormatDate($Statistics::PerDayDateFormat, $dates[$#dates-1]);
+<&|/Elements/TitleBox, 
+       title => $title,
+       title_href => "/RTx/Statistics/CallsQueueDay/index.html?$QueryString" &>
+<TABLE BORDER=0 cellspacing=0 cellpadding=1 WIDTH=100%>
+% if ($ShowHeader) {
+<& /RTx/Statistics/Elements/CollectionAsTable/Header, 
+    Format => \@Format, 
+    FormatString => $Format,
+    AllowSorting => $AllowSorting, 
+    Order => $Order, 
+    Query => undef,
+    Rows => $Rows,
+    Page => $Page,
+    OrderBy => $OrderBy , 
+    BaseURL => $BaseURL,
+    maxitems => $maxitems &> 
+% }
+% my $line = 1;
+% LINE: for my $d (0..$#dates) {
+% if ($d == $#dates){
+%       next LINE;
+% }
+%     my $x = 1;
+%     $values{Statistics_Date} = Statistics::FormatDate($Statistics::PerDayDateFormat, $dates[$d]);
+%# NOTE need to handle all status values here....
+%     for my $status (qw(created resolved deleted)) {
+%         my $tix = new RT::Tickets($session{'CurrentUser'});
+%         $tix->LimitQueue (VALUE => $Queue);
+%         if ($status eq "created") {
+%             $tix->LimitCreated(VALUE => $dates[$d]->ISO, OPERATOR => ">=");
+%             if ($dates[$d+1]) {
+%                 $tix->LimitCreated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<=");
+%            }
+%            $values{Statistics_Created_Count} = $tix->Count;
+%            $Totals{Statistics_Created_Count} += $tix->Count;
+%         }
+%         elsif ($status eq "resolved") {
+%             $tix->LimitStatus(VALUE => $status);
+%             $tix->LimitResolved(VALUE => $dates[$d]->ISO, OPERATOR => ">=");
+%             if ($dates[$d+1]) {
+%                 $tix->LimitResolved(VALUE => $dates[$d+1]->ISO, OPERATOR => "<=");
+%             }
+%            $values{Statistics_Resolved_Count} = $tix->Count;
+%            $Totals{Statistics_Resolved_Count} += $tix->Count;
+%         } 
+%         elsif ($status eq "deleted") {
+%             $tix->LimitStatus(VALUE => $status);
+%             $tix->LimitLastUpdated(VALUE => $dates[$d]->ISO, OPERATOR => ">=");
+%             if ($dates[$d+1]) {
+%                 $tix->LimitLastUpdated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<=");
+%             }
+%            $values{Statistics_Deleted_Count} = $tix->Count;
+%            $Totals{Statistics_Deleted_Count} += $tix->Count;
+%         }
+%         $data[$x++][$d] = $tix->Count;
+%     }
+<&   /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@Format, i => $line, record => $record, maxitems => $maxitems &>
+%    $line++;
+% }
+% $values {Statistics_Date} = "Totals";
+% $values {Statistics_Created_Count} = $Totals{Statistics_Created_Count};
+% $values {Statistics_Resolved_Count} = $Totals{Statistics_Resolved_Count};
+% $values {Statistics_Deleted_Count} = $Totals{Statistics_Deleted_Count};
+<&   /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@BoldFormat, i => $line, record => $record, maxitems => $maxitems &>
+</table>
+</&>
+
+<hr>
+
+<BR />
+<BR />
+
+<%perl>
+# Create the graph URL
+my $url= 'Elements/Chart?x_labels=';
+for (1..$diff) {
+    $url .= $data[0][$_] . ",";
+}
+chop $url;
+$url .= "&";
+shift @data;
+for (0..$#data) {
+    $url .= "data".(1+$_)."=".(join ",", @{$data[$_]})."&";
+}
+chop $url;
+$url .= "&set_legend=Created,Resolved,Deleted";
+</%perl>
+
+<& /RTx/Statistics/Elements/GraphBox, GraphURL => $url &>
+
+<& /RTx/Statistics/Elements/ControlsAsTable/ControlBox, 
+         Title => "Change Queue or Dates", 
+         ShowDates => 1, sMonth => \$sMonth, sDay => \$sDay, sYear => \$sYear,
+                         eMonth => \$eMonth, eDay => \$eDay, eYear => \$eYear,
+                         weekends => $weekends,
+         ShowSingleQueue => 1, Queue => $Queue
+ &>
+
+</form>
+
+<a href="<%$RT::WebPath%>/RTx/Statistics/CallsQueueDay/index.html?<% $QueryString %>"><&|/l&>Bookmarkable link</&></a> |
+<a href="<%$RT::WebPath%>/RTx/Statistics/CallsQueueDay/Results.tsv?<%$QueryString%>"><&|/l&>spreadsheet</&></a>
+<BR>
+<BR>
+
+
+% Statistics::DebugLog("ref of eMonth is " . ref($eMonth) . "\n");
+% Statistics::DebugInit( $m );
+
+<%ARGS>
+$Queue => undef
+$weekends => $Statistics::PerDayWeekends;
+$sMonth=>undef
+$sDay=>undef
+$sYear=>undef
+$eMonth=>undef
+$eDay=>undef
+$eYear=>undef
+$days=>undef
+$currentMonth=>undef
+
+$AllowSorting => undef
+$Order => undef
+$OrderBy => undef
+$ShowNavigation => 1
+$ShowHeader => 1
+$Rows => 50
+$Page => 1
+$BaseURL => undef
+</%ARGS>
+
+<%INIT>
+use RTx::Statistics;
+use Time::Local;
+my $selected;
+my $n = 0;
+my @data = ([]);
+my @dates;
+my @msgs;
+my $diff;
+my $sEpoch=0;
+my $eEpoch=0;
+my %Totals;
+my $QueryString;
+my $maxitems = 4;
+my %record;
+my %values;
+my $record = \%record;
+
+$record{values} = \%values;
+
+
+# If debugging, set things up and display all the args
+Statistics::DebugClear();
+Statistics::DebugLog("CallsQueueDay/index.html ARGS:\n");
+for my $key (keys %ARGS) {
+  Statistics::DebugLog("ARG{ $key }=" . $ARGS{$key} . "\n");
+}
+
+my $Format = qq{ Statistics_Date, 
+                 '__Statistics_Created_Count__/STYLE:text-align:right;', 
+                 '__Statistics_Resolved_Count__/STYLE:text-align:right;', 
+                '__Statistics_Deleted_Count__/STYLE:text-align:right;' };
+my $BoldFormat = qq{ '<B>__Statistics_Date__</B>', 
+                     '<B>__Statistics_Created_Count__</B>/STYLE:text-align:right;',
+                     '<B>__Statistics_Resolved_Count__</B>/STYLE:text-align:right;',
+                    '<B>__Statistics_Deleted_Count__</B>/STYLE:text-align:right;' };
+my (@Format) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $Format);
+my (@BoldFormat) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $BoldFormat);
+Statistics::DebugLog("CallsQueueDay/index.html Format array=" . join(',', @Format) . "\n");
+
+if (!defined $Queue) {
+  my $QueueObj = new RT::Queue($session{'CurrentUser'});
+  $QueueObj->Load($Statistics::PerDayQueue);
+  $Queue = $QueueObj->Id();
+}
+
+if ($sDay > $Statistics::monthsMaxDay{$sMonth}) {
+  $sDay = $Statistics::monthsMaxDay{$sMonth};
+}
+
+if ($eDay > $Statistics::monthsMaxDay{$eMonth}) {
+  $eDay = $Statistics::monthsMaxDay{$eMonth};
+}
+
+if ($sYear){
+       $sEpoch = timelocal(0, 0, 0, $sDay, $sMonth, $sYear-1900);
+}
+if ($eYear){
+Statistics::DebugLog("eMonth = " . $eMonth . "\n");
+       $eEpoch = timelocal(0, 0, 0, $eDay, $eMonth, $eYear-1900);
+} else {
+        # This case happens when the page is first loaded
+       my @local = localtime(time);
+       ($eDay, $eMonth, $eYear) = ($local[3], $local[4], $local[5]);
+       $eYear += 1900; 
+       $eEpoch = timelocal(0, 0, 0, $local[3], $local[4], $local[5], $local[6], $local[7], $local[8]);
+Statistics::DebugLog("Setting eEpoch=$eEpoch from current time.\n");
+}
+
+if (($eEpoch < $sEpoch) || ($sEpoch == 0)) {
+    # We have an end, but not a start, or, overlapping.
+    
+    # if $currentMonth is set, just set the day to 1
+    if($currentMonth) {
+      # set start vars from end, but with day set to 1
+      (undef, undef, undef, $sDay, $sMonth, $sYear) = localtime($eEpoch);
+      $sDay=1;
+      $sEpoch = timelocal(0, 0, 0, $sDay, $sMonth, $sYear);
+    } else {
+      # If the user has specified how many days back to go, use that,
+      # If not, set start to configured default period before end
+      if(defined $days) {
+        $sEpoch = $eEpoch - ($days * $Statistics::secsPerDay);
+      } else {
+        $sEpoch = $eEpoch - ($Statistics::PerDayPeriod * $Statistics::secsPerDay);
+      }
+      (undef, undef, undef, $sDay, $sMonth, $sYear) = localtime($sEpoch);
+    }
+    $sYear += 1900;
+}
+
+# Compute days to chart.
+# The +1 is because we need to generate one more date. If the user
+# selected a 10 day range, we need to generate 11 days.
+$diff = int(($eEpoch - $sEpoch + $Statistics::secsPerDay - 1) / $Statistics::secsPerDay)+1;
+Statistics::DebugLog("Setting diff=$diff\n");
+
+Statistics::DebugLog("sEpoch=$sEpoch, components=" . join(',', localtime($sEpoch)) . "\n");
+Statistics::DebugLog("eEpoch=$eEpoch, components=" . join(',', localtime($eEpoch)) . "\n");
+
+# Set up the string for the current query for bookmarkable link
+$QueryString = "sDay=$sDay&sMonth=$sMonth&sYear=$sYear&eDay=$eDay&eMonth=$eMonth&eYear=$eYear&weekends=$weekends&Queue=$Queue";
+
+# Set up the end date to be midnight(morning) of the date after the one the user wanted.
+my $endRange = $eEpoch + $Statistics::secsPerDay;
+my $QueueObj = new RT::Queue($session{'CurrentUser'});
+$QueueObj->Load($Queue);
+$n = 0;
+until ($#dates == $diff) {
+    my $date = new RT::Date($session{CurrentUser});
+    $date->Set(Value=>$endRange - $n, Format => 'unix');
+    # Note: we used to adjust the time to local midnight, but
+    # none of the other date entry fields in RT seem to adjust, so we've stopped.
+    #Statistics::DebugLog("Before adjust to midnight date " . Statistics::FormatDate("%c", $date) . "\n");
+    $n+= $Statistics::secsPerDay;
+    # If we aren't showing weekends and this is one, decrement the number
+    # of days to show and skip to the next date.
+    if(!$weekends and Statistics::RTDateIsWeekend($date)) {$diff--; next;}
+    unshift @dates, $date;
+Statistics::DebugLog("pushing date " . Statistics::FormatDate("%c", $date) . "\n");
+    unshift @{ $data[0] }, Statistics::FormatDate($Statistics::PerDayLabelDateFormat, $date);
+}
+
+# We put an extra day into the lists to cover up till midnight of the next day,
+# But we don't want that to appear in the labels, so pop it off.
+pop( @{ $data[0] } );
+
+</%INIT>
diff --git a/rt/html/RTx/Statistics/DayOfWeek/Elements/Chart b/rt/html/RTx/Statistics/DayOfWeek/Elements/Chart
new file mode 100755 (executable)
index 0000000..239c095
--- /dev/null
@@ -0,0 +1,26 @@
+% $r->content_type("image/$format");
+% $m->print($graph->plot(\@data)->$format());
+% $m->abort();
+<&|/l, $#data+1&>[_1] Elements</&>:<p>
+% for (0..$#data) {
+<% $data[$_] %><p>
+% }
+<%INIT>
+use GD::Graph::bars;
+
+my @data;
+my $graph = GD::Graph::bars->new($Statistics::GraphWidth,$Statistics::GraphHeight);
+$graph->set(export_format => "png",
+             x_label      => 'Day of Week',
+             y_label      => 'Ticket actions per Day by type');
+$graph->set_legend(split /,/ , $ARGS{set_legend});
+push @data, [split /,/ , $ARGS{x_labels}];
+push @data, [split /,/ , $ARGS{data1}];
+push @data, [split /,/ , $ARGS{data2}];
+push @data, [split /,/ , $ARGS{data3}];
+
+my $format = $graph->export_format;
+$r->content_type("image/$format");
+</%INIT>
+<%ARGS>
+</%ARGS>
diff --git a/rt/html/RTx/Statistics/DayOfWeek/index.html b/rt/html/RTx/Statistics/DayOfWeek/index.html
new file mode 100755 (executable)
index 0000000..2e82b9c
--- /dev/null
@@ -0,0 +1,155 @@
+<& /Elements/Header, Title =>loc('Tickets by Day Of Week in Queue:' . $QueueObj->Name()) &>
+<& /RTx/Statistics/Elements/Tabs, Title =>loc('Trends in ticket status by Day Of Week in Queue:' . $QueueObj->Name()) &>
+
+<h3>Description</h3>
+<p>The purpose of this page is to show historical trends for each day of the week. 
+It displays details of number of tickets created in your
+selected queue for each day. It also hows how many of those created tickets were Resolved or Deleted</p>
+
+<form method="POST" action="index.html">
+
+
+%my $title = "Ticket counts by day of week in " . $QueueObj->Name();
+<&|/Elements/TitleBox, 
+       title => $title,
+       title_href => "/RTx/Statistics/DayOfWeek/index.html?$QueryString" &>
+<TABLE BORDER=0 cellspacing=0 cellpadding=1 WIDTH=100%>
+% if ($ShowHeader) {
+<& /RTx/Statistics/Elements/CollectionAsTable/Header, 
+    Format => \@Format, 
+    FormatString => $Format,
+    AllowSorting => $AllowSorting, 
+    Order => $Order, 
+    Query => undef,
+    Rows => $Rows,
+    Page => $Page,
+    OrderBy => $OrderBy , 
+    BaseURL => $BaseURL,
+    maxitems => $maxitems &> 
+% }
+% my $line = 1;
+% for my $d (0..$#days) {
+%     my $x = 1;
+%     $values{Statistics_Date} = $days[$d];
+%# NOTE Show all status values???
+%     $values{Statistics_Created_Count} = $counts[$d]{new};
+%     $values{Statistics_Resolved_Count} = $counts[$d]{resolved};
+%     $values{Statistics_Deleted_Count} = $counts[$d]{deleted};
+<&   /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@Format, i => $line, record => $record, maxitems => $maxitems &>
+%    $line++;
+% }
+% $values {Statistics_Date} = "Totals";
+% $values {Statistics_Created_Count} = $Totals{new};
+% $values {Statistics_Resolved_Count} = $Totals{resolved};
+% $values {Statistics_Deleted_Count} = $Totals{deleted};
+<&   /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@BoldFormat, i => $line, record => $record, maxitems => $maxitems &>
+</table>
+</&>
+
+<hr>
+
+<BR />
+<BR />
+
+<%perl>
+my $url = 'Elements/Chart?&x_labels=';
+for (0..$#days) {
+  $url .= $days[$_] . "," ;
+}
+chop $url;
+$url .= "&";
+
+my @things = qw(new resolved deleted);
+for my $th (0..$#things) {
+  $url .= "data".(1+$th)."=".(join ",", map { $counts[$_]{$things[$th]} } (0..6))."&";
+}
+chop $url;
+$url .= '&set_legend=Created,Resolved,Deleted';
+</%perl>
+
+<& /RTx/Statistics/Elements/GraphBox, GraphURL => $url &>
+
+% Statistics::DebugLog("queue name=" . $QueueObj->Id() . "\n");
+
+<& /RTx/Statistics/Elements/ControlsAsTable/ControlBox, 
+         Title => "Change Queue", 
+         ShowSingleQueue => 1, Queue => $QueueObj->Id()
+ &>
+
+</form>
+
+% Statistics::DebugInit( $m );
+
+<%ARGS>
+$Queue => $Statistics::DayOfWeekQueue
+
+$AllowSorting => undef
+$Order => undef
+$OrderBy => undef
+$ShowNavigation => 1
+$ShowHeader => 1
+$Rows => 50
+$Page => 1
+$BaseURL => undef
+</%ARGS>
+
+<%INIT>
+use GD::Graph;
+use RTx::Statistics;
+my @days = qw(Sun Mon Tue Wed Thu Fri Sat);
+my $n = 0;
+my @data = ([]);
+my @msgs;
+my @counts;
+my %Totals = (
+  resolved => 0,
+  deleted => 0,
+  new => 0
+);
+my $QueryString = "Queue=$Queue";
+my $maxitems = 4;
+my %record;
+my %values;
+my $record = \%record;
+
+$record{values} = \%values;
+
+my $Format = qq{ Statistics_Date, 
+                 '__Statistics_Created_Count__/STYLE:text-align:right;', 
+                 '__Statistics_Resolved_Count__/STYLE:text-align:right;', 
+                '__Statistics_Deleted_Count__/STYLE:text-align:right;' };
+my $BoldFormat = qq{ '<B>__Statistics_Date__</B>', 
+                     '<B>__Statistics_Created_Count__</B>/STYLE:text-align:right;',
+                     '<B>__Statistics_Resolved_Count__</B>/STYLE:text-align:right;',
+                    '<B>__Statistics_Deleted_Count__</B>/STYLE:text-align:right;' };
+my (@Format) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $Format);
+my (@BoldFormat) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $BoldFormat);
+
+my $QueueObj = new RT::Queue($session{'CurrentUser'});
+$QueueObj->Load($Queue);
+$RT::Logger->warning("Loaded queue $Queue, name=". $QueueObj->Name());
+
+my $tix = new RT::Tickets($session{'CurrentUser'});
+$tix->LimitQueue (VALUE => $Queue);
+$tix->UnLimit;
+if ($tix->Count) {
+    # Initialize the counters to zero, so that all the cells show up
+    foreach my $day (0..@days) {
+        $counts[$day]{resolved} = 0;
+        $counts[$day]{deleted} = 0;
+        $counts[$day]{new} = 0;
+    }
+    while (my $t = $tix->RT::SearchBuilder::Next) {  # BLOODY HACK
+        if($t->Status eq "resolved") {
+          $counts[(localtime($t->ResolvedObj->Unix))[6]]{resolved}++;
+         $Totals{resolved}++;
+       }
+       if($t->Status eq "deleted") {
+         $counts[(localtime($t->LastUpdatedObj->Unix))[6]]{deleted}++;
+         $Totals{deleted}++;
+        }
+        $counts[(localtime($t->CreatedObj->Unix))[6]]{new}++;
+       $Totals{new}++;
+    }
+}
+</%INIT>
diff --git a/rt/html/RTx/Statistics/DurationAsString b/rt/html/RTx/Statistics/DurationAsString
new file mode 100755 (executable)
index 0000000..c0b4d9a
--- /dev/null
@@ -0,0 +1,18 @@
+<%$days|'00'%> days <%$hours|'00'%>:<%$minutes|'00'%> 
+<%INIT>
+
+my $MINUTE = 60;
+my $HOUR =  $MINUTE*60;
+my $DAY = $HOUR * 24;
+my $WEEK = $DAY * 7;
+my $days = int($Duration / $DAY);
+$Duration = $Duration % $DAY;
+my $hours = int($Duration / $HOUR);
+$hours = sprintf("%02d", $hours);
+$Duration = $Duration % $HOUR;
+my $minutes = int($Duration/$MINUTE);
+$minutes = sprintf("%02d", $minutes);
+</%INIT>
+<%ARGS>
+$Duration => undef
+</%ARGS>
diff --git a/rt/html/RTx/Statistics/Elements/CollectionAsTable/Header b/rt/html/RTx/Statistics/Elements/CollectionAsTable/Header
new file mode 100644 (file)
index 0000000..cecb02e
--- /dev/null
@@ -0,0 +1,126 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%# 
+%# COPYRIGHT:
+%#  
+%# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC 
+%#                                          <jesse@bestpractical.com>
+%# 
+%# (Except where explicitly superseded by other copyright notices)
+%# 
+%# 
+%# LICENSE:
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+%# 
+%# 
+%# CONTRIBUTION SUBMISSION POLICY:
+%# 
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%# 
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%# 
+%# END BPS TAGGED BLOCK }}}
+<%ARGS>
+@Format => undef
+$FormatString => undef
+$AllowSorting => undef
+$Order=>undef
+$BaseURL => undef
+$Query => undef
+$Rows => undef
+$Page => undef
+$maxitems => undef
+</%ARGS>
+<TR class="collection-as-table">
+<%perl>
+
+my %generic_query_args = ( Query => $Query, Rows => $Rows, Page => $Page, Format => $FormatString );
+
+my $item = 0;
+foreach my $col (@Format) {
+    $item++;
+    if ( $col->{title} eq 'NEWLINE' ) {
+        while ( $item < $maxitems ) {
+            $m->out(qq{<th class="collection-as-table">&nbsp;</th>\n});
+            $item++;
+        }
+
+        $item = 0;
+        $m->out(qq{</TR>\n<TR class="collection-as-table">});
+    }
+    else {
+        $m->out('<TH class="collection-as-table" ');
+       $m->out( 'align="' . $col->{align} . '"' ) if ( $col->{align} );
+       $m->out( 'style="' . $col->{style} . '"' ) if ( $col->{style} );
+       $m->out('>');
+        my $title = $col->{title};
+        $title =~ s/^__(.*)__$/$1/o;
+        $title = (
+            $m->comp(
+                '/RTx/Statistics/Elements/StatColumnMap',
+                Name => $title,
+                Attr => 'title'
+              )
+              || $title
+        );
+        if (
+               $AllowSorting
+            && $col->{'attribute'}
+            && $m->comp(
+                '/RTx/Statistics/Elements/StatColumnMap',
+                Name => $col->{'attribute'},
+                Attr => 'attribute'
+            )
+          )
+        {
+
+            $m->out(
+                '<a href="' . $BaseURL
+                  . $m->comp(
+                    '/Elements/QueryString',
+                    %generic_query_args,
+                    OrderBy => (
+                        $m->comp(
+                            '/RTx/Statistics/Elements/StatColumnMap',
+                            Name => $col->{'attribute'},
+                            Attr => 'attribute'
+                          )
+                          || $col->{'attribute'}
+                    ),
+                    Order => ( $ARGS{'Order'} eq 'ASC' ? 'DESC' : 'ASC' )
+                  )
+                  . '">'
+                  . loc($title) . '</a>'
+            );
+        }
+        else {
+            $m->out( loc($title) );
+        }
+        $m->out('</TH>');
+    }
+}
+</%perl>
+</TR>
diff --git a/rt/html/RTx/Statistics/Elements/CollectionAsTable/ParseFormat b/rt/html/RTx/Statistics/Elements/CollectionAsTable/ParseFormat
new file mode 100644 (file)
index 0000000..a482f81
--- /dev/null
@@ -0,0 +1,109 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%# 
+%# COPYRIGHT:
+%#  
+%# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC 
+%#                                          <jesse@bestpractical.com>
+%# 
+%# (Except where explicitly superseded by other copyright notices)
+%# 
+%# 
+%# LICENSE:
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+%# 
+%# 
+%# CONTRIBUTION SUBMISSION POLICY:
+%# 
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%# 
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%# 
+%# END BPS TAGGED BLOCK }}}
+<%ARGS>
+$Format
+</%ARGS>
+
+<%init>
+use Regexp::Common;
+my @Columns;
+
+while ($Format =~ /($RE{delimited}{-delim=>qq{\'"}}|[{}\w.]+)/go) {
+    my $col = $1;
+
+    if ($col =~ /^$RE{quoted}$/o) {
+        substr($col,0,1) = "";
+        substr($col,-1,1) = "";
+    }
+
+    my $colref;
+
+    # kfh at mqsoftware.com added this to be able
+    # to create columns where the actual heading and value
+    # aren't know ahead of time.  For instance queue names.
+    # it will work with subcols, but all subcols will have the same KEY
+    if ( $col =~ s!/KEY:([^/]+)!!io ) {
+        $colref->{'keyname'} = $1;
+    }
+    if ( $col =~ s!/STYLE:([^/]+)!!io ) {
+        $colref->{'style'} = $1;
+    }
+    if ( $col =~ s!/CLASS:([^/]+)!!io ) {
+        $colref->{'class'} = $1;
+    }
+    if ( $col =~ s!/TITLE:([^/]+)!!io ) {
+        $colref->{'title'} = $1;
+    }
+    if ( $col =~ s!/ALIGN:([^\/]+)!!io ) {
+        $colref->{'align'} = $1;
+    }
+    if ( $col =~ /__(.*?)__/gio ) {
+        my @subcols;
+        while ( $col =~ s/^(.*?)__(.*?)__//o ) {
+            push ( @subcols, $1 ) if ($1);
+            push ( @subcols, "__$2__" );
+            $colref->{'attribute'} = $2;
+        }
+        push ( @subcols, $col );
+        @{ $colref->{'output'} } = @subcols;
+    }
+    else {
+        @{ $colref->{'output'} } = ( "__" . $col . "__" );
+        $colref->{'attribute'} = $col;
+    }
+    
+    if ( !$colref->{'title'} && grep { /^__(.*?)__$/io }
+        @{ $colref->{'output'} } )
+    {   
+        $colref->{'title'}     = $1;
+        $colref->{'attribute'} = $1;
+    }
+
+
+    push @Columns, $colref;
+}
+    return(@Columns);
+</%init>
diff --git a/rt/html/RTx/Statistics/Elements/CollectionAsTable/Row b/rt/html/RTx/Statistics/Elements/CollectionAsTable/Row
new file mode 100644 (file)
index 0000000..bcfabe5
--- /dev/null
@@ -0,0 +1,112 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%# 
+%# COPYRIGHT:
+%#  
+%# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC 
+%#                                          <jesse@bestpractical.com>
+%# 
+%# (Except where explicitly superseded by other copyright notices)
+%# 
+%# 
+%# LICENSE:
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+%# 
+%# 
+%# CONTRIBUTION SUBMISSION POLICY:
+%# 
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%# 
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%# 
+%# END BPS TAGGED BLOCK }}}
+<%ARGS>
+$i => undef
+@Format => undef
+$record => undef
+$maxitems => undef
+$Depth => undef
+$Warning => undef
+</%ARGS>
+
+<%PERL>
+$m->out('<TR class="' . ( $Warning ? 'warnline' : $i % 2 ? 'oddline' : 'evenline' ) . '" >' );
+my $item;
+foreach my $column (@Format) {
+    if ( $column->{title} eq 'NEWLINE' ) {
+        while ( $item < $maxitems ) {
+            $m->out(qq{<td class="collection-as-table">&nbsp;</td>\n});
+            $item++;
+        }
+        $item = 0;
+        $m->out('</TR>');
+        $m->out('<TR class="'
+              . ( $Warning ? 'warnline' : $i % 2 ? 'oddline' : 'evenline' )
+              . '" >' );
+        next;
+    }
+    $item++;
+    $m->out('<td class="collection-as-table" ');
+    $m->out( 'align="' . $column->{align} . '"' ) if ( $column->{align} );
+    $m->out( 'style="' . $column->{style} . '"' ) if ( $column->{style} );
+    $m->out('>');
+    foreach my $subcol ( @{ $column->{output} } ) {
+        if ( $subcol =~ /^__(.*?)__$/o ) {
+            my $col   = $1;
+            my $value = $m->comp(
+                '/RTx/Statistics/Elements/StatColumnMap',
+                Name => $col,
+                Attr => 'value'
+            );
+            my @out;
+
+            if ( $value && ref($value) ) {
+
+                # All HTML snippets are returned by the callback function
+                # as scalar references.  Data fetched from the objects are
+                # plain scalars, and needs to be escaped properly.
+                @out =
+                    map {
+                        ref($_) ? $$_ : $m->interp->apply_escapes( $_ => 'h' )
+                      } &{$value}( $record, $i, $column->{keyname} );
+                ;
+            }
+            else {
+
+                # Simple value; just escape it.
+                @out = $m->interp->apply_escapes( $value => 'h' );
+            }
+            s/\n/<br>/gs for @out;
+            $m->out( @out );
+        }
+        else {
+            $m->out($subcol);
+        }
+    }
+    $m->out('</td>');
+}
+$m->out('</TR>');
+</%PERL>
diff --git a/rt/html/RTx/Statistics/Elements/ControlsAsTable/ControlBox b/rt/html/RTx/Statistics/Elements/ControlsAsTable/ControlBox
new file mode 100644 (file)
index 0000000..ce043e2
--- /dev/null
@@ -0,0 +1,103 @@
+<table class="box" bgcolor="#336699" style="border-style:none solid solid solid;border-width:1px;border-color:#2E2E8C;" cellpadding="0" cellspacing="0">
+  <tbody>
+    <tr>
+      <th style="color: rgb(51, 102, 153);" class="titlebox">
+        <span class="titleboxclose">
+          <a href="#" onclick="hideshow('stats_control')">X</a></span>&nbsp;
+        <span class="titleboxtitle" style="color: rgb(255, 255, 255);">
+          <b><% $Title %></b></span>
+      </th>
+      <th style="color: rgb(51, 102, 153);" class="titleboxright">
+      <span class="titleboxright">&nbsp;</span>
+      </th>
+    </tr>
+    <tr id="element-stats_control">
+      <td colspan="3" class="" bgcolor="#dddddd">
+        <table border="0" cellpadding="1" cellspacing="0">
+% if (defined $ShowStatus) {
+          <tr>
+            <td class="collection-as-table" style="text-align:left;">Show Status:</td>
+            <td COLSPAN=3 class="collection-as-table" style="text-align:left;">
+             <& /Elements/SelectStatus, Name=>"status", Default => $Status, DefaultValue => undef &>
+           </td>
+          </tr>
+% }
+% if (defined $ShowSingleQueue) {
+          <tr>
+            <td class="collection-as-table" style="text-align:left;">Show Queue:</td>
+            <td COLSPAN=3 class="collection-as-table" style="text-align:left;">
+             <& /Elements/SelectQueue, Name=>"Queue", Default=>$Queue ,ShowNullOption=>0, 
+                    CheckQueueRight=>'SeeQueue' &>
+           </td>
+          </tr>
+% }
+% if (defined $ShowDates) {
+          <tr>
+           <& /RTx/Statistics/Elements/DateSelectRow, Label => "Start Date:", 
+              refMonth => $sMonth, nameMonth => "sMonth", 
+             refDay => $sDay, nameDay => "sDay",
+             refYear => $sYear, nameYear => "sYear" &>
+          </tr>
+          <tr>
+           <& /RTx/Statistics/Elements/DateSelectRow, Label => "End Date:", 
+              refMonth => $eMonth, nameMonth => "eMonth", 
+             refDay => $eDay, nameDay => "eDay",
+             refYear => $eYear, nameYear => "eYear" &>
+          </tr>
+          <tr>
+           <td class="collection-as-table" style="text-align:left;">Show Weekends:</td>
+           <td class="collection-as-table" style="text-align:left;">
+             <select name=weekends>
+               <option value=0 <% (!$weekends) && 'selected' %> >No</option>
+               <option value=1 <% $weekends && 'selected' %> >Yes</option>
+             </select>
+           </td>
+         </tr>
+% }
+% if (defined $ShowMultiQueues) {
+          <tr>
+% if (defined $ShowDates) {
+%# If we're showing the dates, we put these side by side.
+            <td COLSPAN=2 class="collection-as-table" style="text-align:left;">Select All Queues: <input type=checkbox name="AddAllCheck"></td>
+           <td COLSPAN=3 class="collection-as-table" >
+             <& /RTx/Statistics/Elements/SelectMultiQueue, Name=>"queues", Selected=>$queues_ref, 
+                 ShowNullOption=>0, CheckQueueRight=>'SeeQueue', Size => 10, NamedValues => 1 &>
+            </td>
+% } else {
+            <td COLSPAN=3 class="collection-as-table" style="text-align:left;">
+             <& /RTx/Statistics/Elements/SelectMultiQueue, Name=>"queues", Selected=>$queues_ref, 
+                 ShowNullOption=>0, CheckQueueRight=>'SeeQueue', Size => 10, NamedValues => 1 &>
+            </td>
+          </tr>
+          <tr>
+            <td class="collection-as-table" style="text-align:left;">Select All Queues: <input type=checkbox name="AddAllCheck"></td>
+% }
+          </tr>
+% }
+         <& /RTx/Statistics/Elements/ControlsAsTable/UpdatePage &>
+        </table>
+      </td>
+    </tr>
+  </tbody>
+</table>
+
+
+<BR>
+<%args>
+$Title => undef
+$ShowMultiQueues => undef
+$queues_ref => undef
+$ShowDates => undef
+$sMonth => undef
+$sDay => undef
+$sYear => undef
+$eMonth => undef
+$eDay => undef
+$eYear => undef
+$weekends => undef
+$ShowSingleQueue => undef
+$Queue => undef
+$ShowStatus => undef
+$Status => undef
+</%args>
+
diff --git a/rt/html/RTx/Statistics/Elements/ControlsAsTable/UpdatePage b/rt/html/RTx/Statistics/Elements/ControlsAsTable/UpdatePage
new file mode 100644 (file)
index 0000000..b4ccfd5
--- /dev/null
@@ -0,0 +1,5 @@
+<tr>
+  <td colspan="4" style="text-align:center;padding-top:3px; background-color:#C8C8C8;">
+    <INPUT TYPE="submit" VALUE="<&|/l&>Update Page</&>">
+  </td>
+</tr>
diff --git a/rt/html/RTx/Statistics/Elements/DateSelectRow b/rt/html/RTx/Statistics/Elements/DateSelectRow
new file mode 100644 (file)
index 0000000..325e168
--- /dev/null
@@ -0,0 +1,55 @@
+    <td class="collection-as-table" style="text-align:left;"><% $Label %></td>
+    <td class="collection-as-table" style="text-align:left;">
+      <select name=<% $nameMonth %> >
+% for ($n=0;$n<=$#Statistics::months;$n++){ 
+%      if ($$refMonth eq $n){  
+%              $selected ="selected";
+%      }else {
+%              $selected ="";
+%      }
+         <option  value=<% $n %> <% $selected %> ><% $Statistics::months[$n] %></option>
+%}
+      </select>
+    </td>
+    <td class="collection-as-table" style="text-align:left;">
+      <select name=<% $nameDay %> >
+% for ($n=1;$n<=31;$n++){
+%      if ($$refDay == $n ){
+%              $selected ="selected";
+%      }else {
+%              $selected ="";
+%      }
+           <option  value=<% $n %> <% $selected %> ><% $n  %></option>
+% }
+      </select>
+    </td>
+    <td class="collection-as-table" style="text-align:left;">
+         <select name=<% $nameYear %> >
+% 
+% for ($n=0;$n <= scalar @Statistics::years-1;$n++){
+%      if ($Statistics::years[$n] == $$refYear){
+%              $selected ="selected";
+%      }else{
+%              $selected ="";
+%      }
+         <option value=<% $Statistics::years[$n] %> <% $selected %> ><% $Statistics::years[$n] %></option>
+% }      
+         </select>
+    </td>
+
+
+<%args>
+$Label => undef
+$refMonth => undef
+$nameMonth => undef
+$refDay => undef
+$nameDay => undef
+$refYear => undef
+$nameYear => undef
+</%args>
+<%init>
+use RTx::Statistics;
+my $n;
+my $selected;
+
+</%init>
diff --git a/rt/html/RTx/Statistics/Elements/DurationAsString b/rt/html/RTx/Statistics/Elements/DurationAsString
new file mode 100755 (executable)
index 0000000..c0b4d9a
--- /dev/null
@@ -0,0 +1,18 @@
+<%$days|'00'%> days <%$hours|'00'%>:<%$minutes|'00'%> 
+<%INIT>
+
+my $MINUTE = 60;
+my $HOUR =  $MINUTE*60;
+my $DAY = $HOUR * 24;
+my $WEEK = $DAY * 7;
+my $days = int($Duration / $DAY);
+$Duration = $Duration % $DAY;
+my $hours = int($Duration / $HOUR);
+$hours = sprintf("%02d", $hours);
+$Duration = $Duration % $HOUR;
+my $minutes = int($Duration/$MINUTE);
+$minutes = sprintf("%02d", $minutes);
+</%INIT>
+<%ARGS>
+$Duration => undef
+</%ARGS>
diff --git a/rt/html/RTx/Statistics/Elements/GraphBox b/rt/html/RTx/Statistics/Elements/GraphBox
new file mode 100644 (file)
index 0000000..3dc0697
--- /dev/null
@@ -0,0 +1,27 @@
+<div style="float:left; padding-right:30px;">
+<table class="box" bgcolor="#336699" style="border-style:none solid solid solid;border-width:1px;border-color:#2E2E8C;" cellpadding="0" cellspacing="0">
+  <tbody><tr>
+    <th style="color: rgb(51, 102, 153);" class="titlebox">
+      <span class="titleboxclose">
+        <a href="#" onclick="hideshow('stats_chart')">X</a></span>&nbsp;
+
+      <span class="titleboxtitle">
+        <b><a href="<% $GraphURL %>">Download Chart as Image</a></b>
+      </span>
+    </th>
+    <th style="color: rgb(51, 102, 153);" class="titleboxright">
+      <span class="titleboxright">&nbsp;</span>
+    </th>
+  </tr>
+
+  <tr id="element-stats_chart">
+    <td colspan="3" class="" bgcolor="#dddddd">
+       <img src="<% $GraphURL %>" ALT="Result Graph" >
+     </td>
+  </tr>
+  </tbody>
+</table>
+</div>
+<%args>
+$GraphURL => undef
+</%args>
diff --git a/rt/html/RTx/Statistics/Elements/SelectMultiQueue b/rt/html/RTx/Statistics/Elements/SelectMultiQueue
new file mode 100755 (executable)
index 0000000..637f6dc
--- /dev/null
@@ -0,0 +1,81 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%# 
+%# COPYRIGHT:
+%#  
+%# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC 
+%#                                          <jesse@bestpractical.com>
+%# 
+%# (Except where explicitly superseded by other copyright notices)
+%# 
+%# 
+%# LICENSE:
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+%# 
+%# 
+%# CONTRIBUTION SUBMISSION POLICY:
+%# 
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%# 
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%# 
+%# END BPS TAGGED BLOCK }}}
+<SELECT NAME ="<%$Name%>" multiple size="<% $Size %>">
+% if ($ShowNullOption) {
+<OPTION VALUE="">-</OPTION>
+% }
+% while (my $queue=$q->Next) {
+% if ($ShowAllQueues || $queue->CurrentUserHasRight($CheckQueueRight)) {
+%  my $targ="," . $queue->Name . ",";
+<OPTION VALUE="<%($NamedValues ? $queue->Name : $queue->Id) %>" <%( ($sel_list =~ m/$targ/) ? 'SELECTED' : '')%>><%$queue->Name%>
+%   if (($Verbose) and ($queue->Description) ){
+(<%$queue->Description%>)
+%  }
+</OPTION>
+% }
+% }
+</SELECT>
+<%ARGS>
+$CheckQueueRight => 'CreateTicket'
+$ShowNullOption => 1
+$ShowAllQueues => 1
+$Name => undef
+$Verbose => undef
+$NamedValues => 0
+$Selected => undef  # ref to array containing selected queue names
+$Lite => 0
+$Size => 5
+</%ARGS>
+
+<%INIT>
+
+# put list of queue names into string, starting and ending with commas
+my $sel_list = "," . join(",", @$Selected) . ",";
+
+my $q=new RT::Queues($session{'CurrentUser'});
+$q->UnLimit;
+
+</%INIT>
diff --git a/rt/html/RTx/Statistics/Elements/StatColumnMap b/rt/html/RTx/Statistics/Elements/StatColumnMap
new file mode 100644 (file)
index 0000000..aef9e2f
--- /dev/null
@@ -0,0 +1,173 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%# 
+%# COPYRIGHT:
+%#  
+%# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC 
+%#                                          <jesse@bestpractical.com>
+%# 
+%# (Except where explicitly superseded by other copyright notices)
+%# 
+%# 
+%# LICENSE:
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+%# 
+%# 
+%# CONTRIBUTION SUBMISSION POLICY:
+%# 
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%# 
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%# 
+%# END BPS TAGGED BLOCK }}}
+<%ARGS>
+$Name => undef
+$Attr => undef
+</%ARGS>
+
+
+<%ONCE>
+our ( $STAT_COLUMN_MAP );
+
+sub StatColumnMap {
+    my $name = shift;
+    my $attr = shift;
+
+    # First deal with the simple things from the map
+    if ( $STAT_COLUMN_MAP->{$name} ) {
+        return ( $STAT_COLUMN_MAP->{$name}->{$attr} );
+    }
+
+    # now, let's deal with harder things, like Custom Fields
+
+    elsif ( $name =~ /^(?:CF|CustomField)\.\{(.+)\}$/ ) {
+        my $field = $1;
+
+        if ( $attr eq 'attribute' ) {
+            return (undef);
+        }
+        elsif ( $attr eq 'title' ) {
+            return ( $field );
+        }
+        elsif ( $attr eq 'value' ) {
+           # Display custom field contents, separated by newlines.
+            # For Image custom fields we also show a thumbnail here.
+            return sub {
+                my $values = $_[0]->CustomFieldValues($field);
+                return map {
+                    (
+                        ($_->CustomFieldObj->Type eq 'Image')
+                            ? \($m->scomp( '/Elements/ShowCustomFieldImage', Object => $_ ))
+                            : $_->Content
+                    ),
+                    \'<br>',
+                } @{ $values->ItemsArrayRef }
+           };
+        }
+    }
+}
+
+sub LinkCallback {
+    my $method = shift;
+
+    my $mode            = $RT::Ticket::LINKTYPEMAP{$method}{Mode};
+    my $type            = $RT::Ticket::LINKTYPEMAP{$method}{Type};
+    my $mode_uri        = $mode.'URI';
+    my $local_type      = 'Local'.$mode;
+
+    return sub {
+        map {
+            \'<A HREF="',
+            $_->$mode_uri->Resolver->HREF,
+            \'">',
+            ( $_->$mode_uri->IsLocal ? $_->$local_type : $_->$mode ),
+            \'</A><BR>',
+        } @{ $_[0]->Links($mode,$type)->ItemsArrayRef }
+    }
+}
+
+$STAT_COLUMN_MAP = {
+    LastUpdated => {
+        attribute => 'LastUpdated',
+        title     => 'Last Updated',
+        value     => sub { return $_[0]->LastUpdatedObj->AsString }
+    },
+
+    Statistics_Date => {
+       title => 'Date',
+       value => sub { return $_[0]{values}{Statistics_Date} }
+    },
+
+    Statistics_Created_Count => {
+       title => 'Created',
+       value => sub { return $_[0]{values}{Statistics_Created_Count} }
+    },
+
+    Statistics_Resolved_Count => {
+       title => 'Resolved',
+       value => sub { return $_[0]{values}{Statistics_Resolved_Count} }
+    },
+
+    Statistics_Deleted_Count => {
+       title => 'Deleted',
+       value => sub { return $_[0]{values}{Statistics_Deleted_Count} }
+    },
+
+    Statistics_Totals => {
+       title => 'Totals',
+       value => sub { return $_[0]{values}{Statistics_Totals} }
+    },
+
+    Statistics_Status => {
+       title => 'Status',
+       value => sub { return $_[0]{values}{Statistics_Status} }
+    },
+
+    Statistics_Dynamic => {
+       # Depends on having a KEY as second param
+       value => sub { 
+           my $record = shift;
+           my $line = shift;
+           my $key = shift;
+           return $$record{values}{$key} 
+       }
+    },
+
+    # Everything from LINKTYPEMAP
+    (map {
+        $_ => { value => LinkCallback( $_ ) }
+    } keys %RT::Ticket::LINKTYPEMAP),
+
+    '_CLASS' => {
+        value => sub { return $_[1] % 2 ? 'oddline' : 'evenline' }
+    },
+
+};
+</%ONCE>
+<%init>
+$m->comp( '/Elements/Callback', STAT_COLUMN_MAP    => $STAT_COLUMN_MAP, _CallbackName => 'StatColumnMap');
+return StatColumnMap($Name, $Attr);
+</%init>
diff --git a/rt/html/RTx/Statistics/Elements/Tabs b/rt/html/RTx/Statistics/Elements/Tabs
new file mode 100755 (executable)
index 0000000..4fde113
--- /dev/null
@@ -0,0 +1,72 @@
+%# BEGIN LICENSE BLOCK
+%# 
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%# 
+%# (Except where explictly superceded by other copyright notices)
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%# 
+%# 
+%# END LICENSE BLOCK
+<& /Elements/Tabs, 
+    tabs => $tabs, 
+    current_toptab => 'RTx/Statistics/index.html', 
+    current_tab => $current_tab, 
+    Title => $Title &>
+
+<%INIT>
+  my $tabs = { A => { title => loc('Tickets per Day'),
+                         path => 'RTx/Statistics/CallsQueueDay/index.html',
+                       },
+              B => { title => loc('Tickets by status'),
+                          path => 'RTx/Statistics/OpenStalled/index.html',
+                        },
+              C => { title => loc('Multiple Queues'),
+                          path => 'RTx/Statistics/CallsMultiQueue/index.html',
+                        },
+              D => { title => loc('Ticket Trends by Day'),
+                          path => 'RTx/Statistics/DayOfWeek/index.html',
+                        },
+              E => { 'title' => loc('Time to Resolve'),
+                          path => 'RTx/Statistics/Resolution/index.html',
+                        },
+              F => { 'title' => loc('Resolve Time Graph'),
+                          path => 'RTx/Statistics/TimeToResolve/index.html',
+                        },
+              Z => { 'title' => loc('FAQ'),
+                          path => 'RTx/Statistics/FAQ/index.html',
+                        },
+            };
+
+  # Now let callbacks add their extra tabs
+  $m->comp('/Elements/Callback', tabs => $tabs, %ARGS);
+
+  foreach my $tab (sort keys %{$tabs}) {
+    if ($tabs->{$tab}->{'path'} eq $current_tab) {
+      $tabs->{$tab}->{"subtabs"} = $subtabs;
+      $tabs->{$tab}->{"current_subtab"} = $current_subtab;
+    }
+  }
+
+</%INIT>
+
+
+<%ARGS>
+$subtabs => undef
+$current_tab => undef
+$current_subtab => undef
+$Title => undef
+</%ARGS>
diff --git a/rt/html/RTx/Statistics/FAQ/index.html b/rt/html/RTx/Statistics/FAQ/index.html
new file mode 100644 (file)
index 0000000..e7839ea
--- /dev/null
@@ -0,0 +1,23 @@
+<& /Elements/Header, Title => 'FAQ and known issues' &>
+<& /RTx/Statistics/Elements/Tabs, Title => loc("FAQ and Known Issues") &>
+<hr noshade size="1">
+<p>This page will be used to contain known issues and FAQ`s for the Statistics
+package<br />
+This will also be used to clarify limitations of the package as they stand.</p>
+
+<p><strong>What Version of the Statistics package is this?</strong></p>
+<p>0.1.8</p>
+
+<p><strong>What time zone are the charts set to?</strong></p>
+<p>Because of the new programming method of the date functions, the charts are currently built in GMT(UTC). This may once again be
+customisable in a future release.</p>
+
+<p><strong>What is the default date period and queue?</strong></p>
+<p>The default date period is the previous 10 days, except where the chart is over a fixed 7 day period. The default queue is either
+General, or another queue set in your local configuration.</p>
+
+<p><strong>What are the limitations of the date function?</strong></p>
+<p>It has few, but it will not let you chose less than one day. you cannot select an end date before the start date and it is not
+recommended to select a date in the future or an illegal date, such at 30th February. Code has been put in place to trap these, but it may
+not be fool proof.</p>
+<hr size="1" noshade>
diff --git a/rt/html/RTx/Statistics/OpenStalled/Elements/Chart b/rt/html/RTx/Statistics/OpenStalled/Elements/Chart
new file mode 100755 (executable)
index 0000000..9505881
--- /dev/null
@@ -0,0 +1,27 @@
+<%perl>
+$r->content_type("image/$format");
+print $graph->plot(\@data)->$format();
+$m->abort();
+print $#data+1 . " Elements:<p>";
+for (0..$#data) {
+print $data[$_];
+print "<p>";
+}
+</%perl>
+<%INIT>
+use GD::Graph::bars;
+
+my @data;
+my $graph = GD::Graph::bars->new($Statistics::GraphWidth,$Statistics::GraphHeight);
+$graph->set(export_format => "png",
+            x_label       => 'Queue name',
+            y_label       => 'Total per queue by status');
+my $format = $graph->export_format;
+$graph->set_legend(split /,/ , $ARGS{set_legend});
+push @data, [split /,/ , $ARGS{x_labels}];
+push @data, [split /,/ , $ARGS{data1}];
+push @data, [split /,/ , $ARGS{data2}];
+push @data, [split /,/ , $ARGS{data3}];
+</%INIT>
+<%ARGS>
+</%ARGS>
diff --git a/rt/html/RTx/Statistics/OpenStalled/Results.tsv b/rt/html/RTx/Statistics/OpenStalled/Results.tsv
new file mode 100644 (file)
index 0000000..2ec1e0c
--- /dev/null
@@ -0,0 +1,114 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%# 
+%# COPYRIGHT:
+%#  
+%# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC 
+%#                                          <jesse@bestpractical.com>
+%# 
+%# (Except where explicitly superseded by other copyright notices)
+%# 
+%# 
+%# LICENSE:
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+%# 
+%# 
+%# CONTRIBUTION SUBMISSION POLICY:
+%# 
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%# 
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%# 
+%# END BPS TAGGED BLOCK }}}
+<%ARGS>
+@queues => @Statistics::OpenStalledQueueList
+</%ARGS>
+
+<%INIT>
+use RTx::Statistics;
+use Time::Local;
+
+  my $n = 0;
+  my @data; 
+  my @msgs;
+  my %totals;
+  my $QueryString;
+  my $now = new RT::Date($session{CurrentUser});
+  my $tix = new RT::Tickets($session{'CurrentUser'});
+
+  my %queues = map { 
+    $_ => 1;
+  } (@queues);
+
+  # set content type
+  $r->content_type('application/vnd.ms-excel');
+
+  $QueryString = "queues=" . join("&queues=", @queues);
+
+  my $queue = new RT::Queues($session{CurrentUser});
+  $queue->UnLimit;
+
+  my $QueueObj = new RT::Queue($session{'CurrentUser'});
+  $QueueObj->Load($queue);
+
+  # Put out some data about the generation of this file
+  $m->out("Tickets by Status by Queue for Queues:\t" . join(',', @queues) . "\tGenerated at:\t" . Statistics::FormatDate("%x %X", $now). "\n\n");
+
+  # basically the same as index.html
+
+  # Output header row
+  $m->out("Status");
+  for ( sort keys %queues) {
+      push @data, $_;
+      my $Queueobj = new RT::Queue($session{'CurrentUser'});
+      $Queueobj->Load($_);
+      next if !$Queueobj->CurrentUserHasRight('SeeQueue');
+      $m->out("\t" . $_);
+  }
+  $m->out("\tTotals\n");
+
+  foreach my $s (qw(new open stalled)) {
+    $m->out("$s");
+    my $total=0;
+    foreach my $q (sort keys %queues)  {
+      $tix = new RT::Tickets($session{'CurrentUser'});
+      $tix->LimitQueue(VALUE => "$q");
+      $tix->LimitStatus(VALUE => "$s");
+      $totals{$q} += $tix->Count; # Add up columns for each queue
+      $m->out("\t" . $tix->Count);
+      $total += $tix->Count;
+    }
+    $m->out("\t$total\n");
+    $totals{"Totals"} += $total;
+  }
+  $m->out("Totals");
+  foreach my $q (sort keys %queues) {
+    $m->out("\t" . $totals{$q});
+  }
+  $m->out("\t" . $totals{"Totals"} . "\n");
+
+  $m->abort();
+</%INIT>
diff --git a/rt/html/RTx/Statistics/OpenStalled/index.html b/rt/html/RTx/Statistics/OpenStalled/index.html
new file mode 100755 (executable)
index 0000000..d0cd9f1
--- /dev/null
@@ -0,0 +1,188 @@
+<& /Elements/Header, Title => loc('New, Open and Stalled tickets by Queue') &>
+<& /RTx/Statistics/Elements/Tabs, Title => loc('New, Open and Stalled tickets by Queue') &>
+
+<h3>Description</h3>
+<p>The purpose of this page is to show a snapshot of the current status of tickets by Queue. You can multi select Queues from the dropdown
+list or simply show all available queues. This will indicate how many tickets have not yet been viewed (New), how many have been at least
+viewed once (Open) and how many have had their status changed to stalled.</p>
+
+<form method="POST" action="index.html">
+
+%my $tix = new RT::Tickets($session{'CurrentUser'});
+%if ($queue) {
+%        $tix->LimitQueue (VALUE => $queue);
+%}
+
+
+%my $title = "New, Open and Stalled Tickets in " . join(', ', @queues);
+<& /Elements/TitleBoxStart, title => $title, title_href => "/RTx/Statistics/OpenStalled/index.html?$QueryString"&>
+<TABLE BORDER=0 cellspacing=0 cellpadding=1 WIDTH="100%">
+% if ($ShowHeader) {
+<& /RTx/Statistics/Elements/CollectionAsTable/Header, 
+    Format => \@RowFormat, 
+    FormatString => $RowFormat,
+    AllowSorting => $AllowSorting, 
+    Order => $Order, 
+    Query => undef,
+    Rows => $Rows,
+    Page => $Page,
+    OrderBy => $OrderBy , 
+    BaseURL => $BaseURL,
+    maxitems => $maxitems &> 
+% }
+
+%    for ( sort keys %queues_to_show) {
+%        push @data, $_;
+%    }
+%    my @legend;
+%    my $total = 0;
+%    my $line = 0;
+%# NOTE need to handle all status values (see share/html/Elements/SelectStatus).
+%    foreach my $s (qw(new open stalled)) {
+%      $line++;
+%      push @legend, $s;
+%      $total=0;
+%      foreach my $q (sort keys %queues_to_show)  {
+%        $tix = new RT::Tickets($session{'CurrentUser'});
+%        $tix->LimitQueue(VALUE => "$q");
+%        $tix->LimitStatus(VALUE => "$s");
+%        push @data, $tix->Count;
+%        $totals{$q} += $tix->Count; # Add up columns for each queue
+%        $total += $tix->Count;
+%        $values{$q} = $tix->Count;
+%      }
+%      $totals{"Totals"} += $total;
+%      $values{Statistics_Status} = $s;
+%      $values{Statistics_Totals} = $total;
+<&     /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@RowFormat, i => $line, record => $record, maxitems => $maxitems &>
+%    }
+%    $values{Statistics_Status} = "Totals";
+%    foreach my $q (sort keys %queues_to_show) {
+%      $values{$q} = $totals{$q};
+%    }
+%    $values{Statistics_Totals} = $totals{"Totals"};
+<&   /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@BoldRowFormat, i => $line+1, record => $record, maxitems => $maxitems &>
+</table>
+<& /Elements/TitleBoxEnd&>
+
+<hr>
+
+<BR />
+<BR />
+
+% use Data::Dumper;
+% Statistics::DebugLog("Dump of data array is " . Dumper(@data) . "\n");
+%  my $url = 'Elements/Chart?x_labels=';
+%  for (1..(scalar keys %queues_to_show)) {
+%    $url .=  $m->interp->apply_escapes((shift @data),'u')  . ',';
+%  }
+%  chop $url;
+%  $url .= '&data1=' ;
+%  for (1..(scalar keys %queues_to_show)) {
+%    $url .=  $m->interp->apply_escapes((shift @data),'u') . ',';
+%  }
+%  chop $url;
+%  $url .= '&data2=' ;
+%  for (1..(scalar keys %queues_to_show)) {
+%    $url .=  $m->interp->apply_escapes((shift @data),'u') . ',';
+%  }
+%  chop $url;
+%  $url .= '&data3=' ;
+%  for (1..(scalar keys %queues_to_show)) {
+%    $url .=  $m->interp->apply_escapes((shift @data),'u') . ',';
+%  }
+%  $url .= '&set_legend='.(join ",", @legend);
+
+
+<& /RTx/Statistics/Elements/GraphBox, GraphURL => $url &>
+
+<& /RTx/Statistics/Elements/ControlsAsTable/ControlBox, Title => "Select Queues", ShowMultiQueues => 1, queues_ref => \@queues &>
+
+<a href="<%$RT::WebPath%>/RTx/Statistics/OpenStalled/index.html?<% $QueryString %>"><&|/l&>Bookmarkable link</&></a>
+%# | <a href="<%$RT::WebPath%>/RTx/Statistics/OpenStalled/Results.tsv?<%$QueryString%>"><&|/l&>spreadsheet</&></a>
+<BR>
+<BR>
+
+</FORM>
+
+% Statistics::DebugInit( $m );
+
+<%ARGS>
+@queues => @Statistics::OpenStalledQueueList
+$AllowSorting => undef
+$Order => undef
+$OrderBy => undef
+$ShowNavigation => 1
+$ShowHeader => 1
+$Rows => 50
+$Page => 1
+$BaseURL => undef
+$AddAllCheck => undef
+</%ARGS>
+
+<%INIT>
+  use RTx::Statistics;
+
+  my $n = 0;
+  my @data; 
+  my @msgs;
+  my %totals;
+  my $QueryString;
+  my %queues_to_show;
+  my $maxitems;
+  my $RowFormat;
+  my $BoldRowFormat;
+  my %record;
+  my %values;
+  my $record = \%record;
+
+  $record{values} = \%values;
+
+  Statistics::DebugClear();
+
+  # Handle the Add All Checkbox
+  if($AddAllCheck eq "on") {
+    $AddAllCheck = undef;
+    undef (@queues);
+    my $q=new RT::Queues($session{'CurrentUser'});
+    $q->UnLimit;
+    while (my $queue=$q->Next) {
+      next if !$queue->CurrentUserHasRight('SeeQueue');
+      push @queues, $queue->Name;
+    }
+  }
+
+  # If the user has the right to see the queue, put it into the map
+  for my $q (@queues) {
+      my $Queueobj = new RT::Queue($session{'CurrentUser'});
+      $Queueobj->Load($q);
+      next if !$Queueobj->CurrentUserHasRight('SeeQueue');
+      $queues_to_show{$q} = 1;
+  }
+
+  $maxitems = (scalar @queues) + 2;
+
+  # Build the new query string
+  $QueryString = "queues=" . join("&queues=", @queues);
+
+  # Build the format strings
+  $RowFormat = "'__Statistics_Status__'";
+  $BoldRowFormat = "'<B>__Statistics_Status__</B>'";
+  for my $q (@queues) {
+      $RowFormat .= ",'__Statistics_Dynamic__/KEY:$q/TITLE:$q/STYLE:text-align:right;'";
+      $BoldRowFormat .= ",'<B>__Statistics_Dynamic__</B>/KEY:$q/TITLE:$q/STYLE:text-align:right;'";
+  }
+  $RowFormat .= ",'<B>__Statistics_Totals__</B>/STYLE:text-align:right;'";
+  $BoldRowFormat .= ",'<B>__Statistics_Totals__</B>/STYLE:text-align:right;'";
+  # Parse the formats into structures.
+  my (@RowFormat) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $RowFormat);
+  my (@BoldRowFormat) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $BoldRowFormat);
+
+
+  my $queue = new RT::Queues($session{CurrentUser});
+  $queue->UnLimit;
+
+  my $QueueObj = new RT::Queue($session{'CurrentUser'});
+  $QueueObj->Load($queue);
+
+</%INIT>
diff --git a/rt/html/RTx/Statistics/Resolution/Elements/Chart b/rt/html/RTx/Statistics/Resolution/Elements/Chart
new file mode 100755 (executable)
index 0000000..fa0ac55
--- /dev/null
@@ -0,0 +1,29 @@
+<%perl>
+$r->content_type("image/$format");
+print $graph->plot(\@data)->$format();
+$m->abort();
+print $#data+1 . " Elements:<p>";
+for (0..$#data) {
+print $data[$_];
+print "<p>";
+}
+</%perl>
+<%INIT>
+use GD::Graph::lines;
+
+my @data;
+my $graph = GD::Graph::lines->new($Statistics::GraphWidth,$Statistics::GraphHeight);
+$graph->set(export_format => "png",
+            x_label           => 'Days',
+            y_label           => 'Average time in Days');
+
+push @data, [split /,/ , $ARGS{x_labels}];
+push @data, [split /,/ , $ARGS{data1}];
+push @data, [split /,/ , $ARGS{data2}];
+push @data, [split /,/ , $ARGS{data3}];
+
+my $format = $graph->export_format;
+#$r->content_type("image/$format");
+</%INIT>
+<%ARGS>
+</%ARGS>
diff --git a/rt/html/RTx/Statistics/Resolution/index.html b/rt/html/RTx/Statistics/Resolution/index.html
new file mode 100644 (file)
index 0000000..d9885b0
--- /dev/null
@@ -0,0 +1,269 @@
+<& /Elements/Header, Title => 'Time to Resolution' &>
+<& /RTx/Statistics/Elements/Tabs, Title => loc("Time To Resolve tickets by Queue for : " .$QueueObj->Name()) &>
+<h3>Description</h3>
+<p>This page shows details of resolution of tickets in the selected queue. It displays tickets created on each day in your selected date
+range. Of those tickets created on that day, how many have been resolved and the total time it has taken for all tickets created on that
+day to be resolved.</p>
+<p>At the bottom of the chart is shows total time taken to resolve all tickets
+in the selected date range and the average time per ticket to
+resolve.</p>
+
+<form method="POST" action="index.html">
+
+%my $title = "Time to resolve in " . $QueueObj->Name() . " per day from " . 
+%        Statistics::FormatDate($Statistics::PerDayDateFormat, $dates[0]) . " through " .
+%        Statistics::FormatDate($Statistics::PerDayDateFormat, $dates[$#dates-1]);
+<&|/Elements/TitleBox, 
+       title => $title,
+       title_href => "/RTx/Statistics/Resolution/index.html?$QueryString" &>
+<TABLE BORDER=0 cellspacing=0 cellpadding=1 WIDTH=100%>
+% if ($ShowHeader) {
+<& /RTx/Statistics/Elements/CollectionAsTable/Header, 
+    Format => \@Format, 
+    FormatString => $Format,
+    AllowSorting => $AllowSorting, 
+    Order => $Order, 
+    Query => undef,
+    Rows => $Rows,
+    Page => $Page,
+    OrderBy => $OrderBy , 
+    BaseURL => $BaseURL,
+    maxitems => $maxitems &> 
+% }
+% my $line = 1;
+% LINE: for my $d (0..$#dates ) {
+%      if ($d == $#dates ){
+%              next LINE;
+%      }
+%    my $x = 1;
+%    $values{Statistics_Date} = Statistics::FormatDate($Statistics::PerDayDateFormat, $dates[$d]);
+%    my $tix = new RT::Tickets($session{'CurrentUser'});
+%    $tix->LimitCreated(VALUE => $dates[$d]->ISO, OPERATOR => ">=");
+%    if ($dates[$d+1]) {
+%        $tix->LimitCreated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<=");
+%    }         
+%    if ($Queue) {
+%        $tix->LimitQueue (VALUE => $Queue);
+%    }
+%    $values{Statistics_Created_Count} = $tix->Count;
+%    $tix->LimitStatus(VALUE => "resolved");
+%    $values{Statistics_Resolved_Count} = $tix->Count;
+%    if ($tix->Count) {
+%       my @tix = @{$tix->ItemsArrayRef};
+%       my $total;
+%       $total += ($_->ResolvedObj->Unix - $_->CreatedObj->Unix) for @tix;
+%              $size+= ($#tix +1);
+%              $grandtotal += $total;                                                     
+%       $values{Duration} = Statistics::DurationAsString($total);
+%      $data[$x++][$d] =  int ($total );
+%    } else {
+%       $values{Duration} = "N/A";
+%    }
+<&   /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@Format, i => $line, record => $record, maxitems => $maxitems &>
+%    $line++;
+%}
+%    $size =1 if $size==0;
+%    $values{text} = "Average time to resolve = " . Statistics::DurationAsString($grandtotal /  $size);
+<&   /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@OneCellFormat, i => $line, record => $record, maxitems => $maxitems &>
+%    $line++;
+%    $values{text} = "Total time to resolve = " . Statistics::DurationAsString( $grandtotal );
+<&   /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@OneCellFormat, i => $line, record => $record, maxitems => $maxitems &>
+%    $line++;
+</table>
+</&>
+
+<hr>
+
+<BR />
+<BR />
+
+<%perl>
+# Create the graph URL
+
+# change the total time to resolve to a floating point number of days
+foreach my $dat(@{$data[1]} ){
+  $dat = ($dat / $Statistics::secsPerDay);
+  $dat = sprintf("%0.4f", $dat); 
+}
+
+my $url = 'Elements/Chart?x_labels=';
+for (0..$diff-1) {
+  $url .= $data[0][$_] . ",";
+}
+chop $url;
+shift @data;
+$url .= "&data1=";
+for(0..$diff-1) {
+  $data[0][$_] = 0 if !$data[0][$_];
+  $url .= $data[0][$_] . ",";
+}
+</%perl>
+
+<& /RTx/Statistics/Elements/GraphBox, GraphURL => $url &>
+
+<& /RTx/Statistics/Elements/ControlsAsTable/ControlBox, 
+         Title => "Change Queue or Dates", 
+         ShowDates => 1, sMonth => \$sMonth, sDay => \$sDay, sYear => \$sYear,
+                         eMonth => \$eMonth, eDay => \$eDay, eYear => \$eYear,
+                         weekends => $weekends,
+         ShowSingleQueue => 1, Queue => $Queue
+ &>
+
+</form>
+
+<%ARGS>
+$max => $Statistics::TimeToResolveMaxRows
+$Queue => undef
+$weekends =>$Statistics::TimeToResolveWeekends
+$sMonth=>undef
+$sDay=>undef
+$sYear=>undef
+$eMonth=>undef
+$eDay=>undef
+$eYear=>undef
+$days=>undef
+$currentMonth=>undef
+
+$AllowSorting => undef
+$Order => undef
+$OrderBy => undef
+$ShowNavigation => 1
+$ShowHeader => 1
+$Rows => 50
+$Page => 1
+$BaseURL => undef
+</%ARGS>
+
+<%INIT>
+use RTx::Statistics;
+use Time::Local;
+my $n = 0;
+my @data = ([]);
+my @dates;
+my @msgs;
+my $size;
+my $selected;
+my $grandtotal = 0; 
+my $diff;
+my $sEpoch=0;
+my $eEpoch=0;
+my $QueryString;
+
+my $maxitems = 4;
+my %record;
+my %values;
+my $record = \%record;
+
+$record{values} = \%values;
+
+
+# If debugging, set things up and display all the args
+Statistics::DebugClear();
+Statistics::DebugLog("CallsQueueDay/index.html ARGS:\n");
+for my $key (keys %ARGS) {
+  Statistics::DebugLog("ARG{ $key }=" . $ARGS{$key} . "\n");
+}
+
+my $Format = qq{ Statistics_Date, 
+                 '__Statistics_Created_Count__/STYLE:text-align:right;', 
+                 '__Statistics_Resolved_Count__/STYLE:text-align:right;', 
+                '__Statistics_Dynamic__/KEY:Duration/TITLE:Time To Resolve/STYLE:text-align:right;' };
+my $BoldFormat = qq{ '<B>__Statistics_Date__</B>', 
+                     '<B>__Statistics_Created_Count__</B>/STYLE:text-align:right;',
+                     '<B>__Statistics_Resolved_Count__</B>/STYLE:text-align:right;',
+                    '<B>__Statistics_Dynamic__</B>/KEY:Duration/TITLE:Time To Resolve/STYLE:text-align:right;' };
+
+# TODO need way to make this cell do colspan
+my $OneCellFormat = qq{ '<B>__Statistics_Dynamic__</B>/KEY:text/STYLE:text-align:left;','','','' };
+
+my (@Format) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $Format);
+my (@BoldFormat) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $BoldFormat);
+my (@OneCellFormat) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $OneCellFormat);
+
+Statistics::DebugLog("CallsQueueDay/index.html Format array=" . join(',', @Format) . "\n");
+
+if ($sDay > $Statistics::monthsMaxDay{$sMonth}) {
+  $sDay = $Statistics::monthsMaxDay{$sMonth};
+}
+
+if ($eDay > $Statistics::monthsMaxDay{$eMonth}) {
+  $eDay = $Statistics::monthsMaxDay{$eMonth};
+}
+
+if ($sYear){
+       $sEpoch = timelocal(0, 0, 0, $sDay, $sMonth, $sYear-1900);
+}
+if ($eYear){
+Statistics::DebugLog("eMonth = " . $eMonth . "\n");
+       $eEpoch = timelocal(0, 0, 0, $eDay, $eMonth, $eYear-1900);
+} else {
+        # This case happens when the page is first loaded
+       my @local = localtime(time);
+       ($eDay, $eMonth, $eYear) = ($local[3], $local[4], $local[5]);
+       $eYear += 1900; 
+       $eEpoch = timelocal(0, 0, 0, $local[3], $local[4], $local[5], $local[6], $local[7], $local[8]);
+Statistics::DebugLog("Setting eEpoch=$eEpoch from current time.\n");
+}
+
+if (($eEpoch < $sEpoch) || ($sEpoch == 0)) {
+    # We have an end, but not a start, or, overlapping.
+    
+    # if $currentMonth is set, just set the day to 1
+    if($currentMonth) {
+      # set start vars from end, but with day set to 1
+      (undef, undef, undef, $sDay, $sMonth, $sYear) = localtime($eEpoch);
+      $sDay=1;
+      $sEpoch = timelocal(0, 0, 0, $sDay, $sMonth, $sYear);
+    } else {
+      # If the user has specified how many days back to go, use that,
+      # If not, set start to configured default period before end
+      if(defined $days) {
+        $sEpoch = $eEpoch - ($days * $Statistics::secsPerDay);
+      } else {
+        $sEpoch = $eEpoch - ($Statistics::PerDayPeriod * $Statistics::secsPerDay);
+      }
+      (undef, undef, undef, $sDay, $sMonth, $sYear) = localtime($sEpoch);
+    }
+    $sYear += 1900;
+}
+
+# Compute days to chart.
+# The +1 is because we need to generate one more date. If the user
+# selected a 10 day range, we need to generate 11 days.
+$diff = int(($eEpoch - $sEpoch + $Statistics::secsPerDay - 1) / $Statistics::secsPerDay)+1;
+Statistics::DebugLog("Setting diff=$diff\n");
+
+Statistics::DebugLog("sEpoch=$sEpoch, components=" . join(',', localtime($sEpoch)) . "\n");
+Statistics::DebugLog("eEpoch=$eEpoch, components=" . join(',', localtime($eEpoch)) . "\n");
+
+my $QueueObj = new RT::Queue($session{'CurrentUser'});
+if (!defined $Queue) {
+  $QueueObj->Load($Statistics::TimeToResolveQueue);
+  $Queue = $QueueObj->Id();
+}
+
+# Set up the string for the current query for bookmarkable link
+$QueryString = "sDay=$sDay&sMonth=$sMonth&sYear=$sYear&eDay=$eDay&eMonth=$eMonth&eYear=$eYear&weekends=$weekends&Queue=$Queue";
+
+# Set up the end date to be midnight(morning) of the date after the one the user wanted.
+my $endRange = $eEpoch + $Statistics::secsPerDay;
+$QueueObj->Load($Queue);
+# NOTE: list loop starts at the end of the date range, unshifting dates onto 
+# the arrays, so that they end up in start to finish order.
+$eEpoch += $Statistics::secsPerDay;
+$n = 0;
+until ($#dates == $diff ) {    
+    my $date = new RT::Date($session{CurrentUser});
+    $date->Set(Value=>$endRange - $n, Format => 'unix');
+    # Note: we used to adjust the time to local midnight, but
+    # none of the other date entry fields in RT seem to adjust, so we've stopped.
+    #Statistics::DebugLog("Before adjust to midnight date " . Statistics::FormatDate("%c", $date) . "\n");
+    $n+= $Statistics::secsPerDay;
+    # If we aren't showing weekends and this is one, decrement the number
+    # of days to show and skip to the next date.
+    if(!$weekends and Statistics::RTDateIsWeekend($date)) {$diff--; next;}
+    unshift @dates, $date;
+Statistics::DebugLog("pushing date " . Statistics::FormatDate("%c", $date) . "\n");
+    unshift @{ $data[0] }, Statistics::FormatDate($Statistics::PerDayLabelDateFormat, $date);
+}
+</%INIT>
diff --git a/rt/html/RTx/Statistics/TimeToResolve/Elements/Chart b/rt/html/RTx/Statistics/TimeToResolve/Elements/Chart
new file mode 100755 (executable)
index 0000000..a069a7b
--- /dev/null
@@ -0,0 +1,23 @@
+<%perl>
+print $graph->plot(\@data)->$format();
+$m->abort();
+</%perl>
+<%INIT>
+use GD::Graph::points;
+
+my @data;
+my $graph = GD::Graph::points->new(400,300);
+$graph->set(export_format => "png",
+            marker_size   => $ARGS{marker_size},
+            x_label       => 'Average time to resolve (Days)',
+            y_label       => 'Number of tickets resolved' );
+#$r->content_type("image/$format");
+my $format = $graph->export_format; 
+push @data, [split /,/ , $ARGS{x_labels}];
+for (1..((scalar keys %ARGS)-2)) {
+  push @data, [split /,/  , $ARGS{"data".$_}];
+}
+
+</%INIT>
+<%ARGS>
+</%ARGS>
diff --git a/rt/html/RTx/Statistics/TimeToResolve/index.html b/rt/html/RTx/Statistics/TimeToResolve/index.html
new file mode 100755 (executable)
index 0000000..2124b53
--- /dev/null
@@ -0,0 +1,75 @@
+<& /Elements/Header, Title => 'Time to Resolve in Queue' &>
+<& /RTx/Statistics/Elements/Tabs,  Title => 'Time to Resolve, by ticket in Queue:' . $QueueObj->Name() &>
+
+<h3>Description</h3>
+<p>This page displays the same information as the Time to Resolve chart, but in a scattergraph format and only for the previous 7 calendar
+days. It only displays data for tickets which have been resolved. Each division on the Days axis is one day and the granularity of this chart
+is 30 minutes.</p>
+<form method="POST">
+
+<table>
+  <tr>
+  <td>Show Queue:</td>
+  <td COLSPAN=3><& /Elements/SelectQueue, Name=>"queue", Default=>$queue ,ShowNullOption=>0, 
+            CheckQueueRight=>'SeeQueue' &></td>
+  </tr>
+</table>
+<INPUT TYPE="submit" VALUE="Update Page"</INPUT>
+</form>
+
+<BR>
+% my $url = 'Elements/Chart?x_labels=';
+% my $i;
+% $url .= join ",", (map {(int($_/2) == $_/2 && (++$i)%2) ? $_/2 : ""} grep {$counts[$_]} 0..($#counts-1)), "longer";
+% $url .= '&';
+% $url .= "marker_size=1&";
+% $url .= "data1=".(join ",", map { $_ || () } @counts)."&";
+% chop $url;
+<IMG SRC="<% $url %>">
+
+<BR>
+
+%Statistics::DebugInit($m);
+
+<%ARGS>
+$queue => undef
+</%ARGS>
+
+<%INIT>
+use RTx::Statistics;
+
+my @days = qw(Sun Mon Tue Wed Thu Fri Sat);
+my $n = 0;
+my @data = ([]);
+my @msgs;
+my @counts;
+
+Statistics::DebugClear();
+Statistics::DebugLog("TimeToResolve/index.html ARGS:\n");
+for my $key (keys %ARGS) {
+  Statistics::DebugLog("ARG{ $key }=" . $ARGS{$key} . "\n");
+}
+
+my $QueueObj = new RT::Queue($session{'CurrentUser'});
+if (!defined $queue) {
+  $QueueObj->Load($Statistics::TimeToResolveGraphQueue);
+  $queue = $QueueObj->Id();
+} else {
+  $QueueObj->Load($queue);
+}
+
+
+my $tix = new RT::Tickets($session{'CurrentUser'});
+$tix->LimitQueue (VALUE => $queue) if $queue;
+$tix->LimitStatus(VALUE => "resolved");
+$tix->UnLimit;
+if ($tix->Count) {
+    while (my $t = $tix->RT::SearchBuilder::Next) {  # BLOODY HACK
+        my $when = $t->ResolvedObj->Unix - $t->CreatedObj->Unix;
+        next unless $when > 0; # Doubly bloody hack
+        my $max = (60*60*24*2) / 1800;
+        my $x = int($when / 1800);
+        $counts[$x > $max ? $max : $x]++;
+    }
+}
+</%INIT>
diff --git a/rt/html/RTx/Statistics/UserTest/Elements/Chart b/rt/html/RTx/Statistics/UserTest/Elements/Chart
new file mode 100755 (executable)
index 0000000..99eb2a2
--- /dev/null
@@ -0,0 +1,28 @@
+<%perl>
+print $graph->plot(\@data)->$format();
+$m->abort();
+print $#data+1 . " Elements:<p>";
+for (0..$#data) {
+print $data[$_];
+print "<p>";
+}
+</%perl>
+<%INIT>
+use GD::Graph::lines;
+
+my @data;
+my $graph = GD::Graph::lines->new(640,480);
+$graph->set(export_format => "png",
+            x_label           => 'Days',
+            y_label           => 'Average time in Days');
+
+push @data, [split /,/ , $ARGS{x_labels}];
+push @data, [split /,/ , $ARGS{data1}];
+push @data, [split /,/ , $ARGS{data2}];
+push @data, [split /,/ , $ARGS{data3}];
+
+my $format = $graph->export_format;
+#$r->content_type("image/$format");
+</%INIT>
+<%ARGS>
+</%ARGS>
diff --git a/rt/html/RTx/Statistics/UserTest/index.html b/rt/html/RTx/Statistics/UserTest/index.html
new file mode 100755 (executable)
index 0000000..7bc25da
--- /dev/null
@@ -0,0 +1,54 @@
+<& /Elements/Header, Title => 'Time to Resolve in Queue' &>
+<& /RTx/Statistics/Elements/Tabs,  Title => 'Time to Resolve, by ticket in Queue:' . $QueueObj->Name() &>
+
+
+<form method="POST">
+
+See Queue:<BR>
+<& /Elements/SelectQueue, Name=>"queue", Default => "$queue" &>
+<BR>
+<INPUT TYPE="submit" VALUE="Go!"</INPUT>
+</form>
+
+<BR>
+% my $url = 'Elements/Chart?x_labels=';
+% my $i;
+% $url .= join ",", (map {(int($_/2) == $_/2 && (++$i)%2) ? $_/2 : ""} grep {$counts[$_]} 0..($#counts-1)), "longer";
+% $url .= '&';
+% $url .= "marker_size=1&";
+% $url .= "data1=".(join ",", map { $_ || () } @counts)."&";
+% chop $url;
+<IMG SRC="<% $url %>">
+
+<BR>
+
+<%ARGS>
+$queue => $Statistics::TimeToResolveGraphQueue;
+</%ARGS>
+
+<%INIT>
+use RTx::Statistics;
+
+my @days = qw(Sun Mon Tue Wed Thu Fri Sat);
+my $n = 0;
+my @data = ([]);
+my @msgs;
+my @counts;
+
+my $QueueObj = new RT::Queue($session{'CurrentUser'});
+$QueueObj->Load($queue);
+
+my $tix = new RT::Tickets($session{'CurrentUser'});
+$tix->LimitQueue (VALUE => $queue) if $queue;
+$tix->LimitStatus(VALUE => "resolved");
+$tix->UnLimit;
+if ($tix->Count) {
+    while (my $t = $tix->RT::SearchBuilder::Next) {  # BLOODY HACK
+        my $when = $t->ResolvedObj->Unix - $t->CreatedObj->Unix;
+        next unless $when > 0; # Doubly bloody hack
+        my $max = (60*60*24*2) / 1800;
+        my $x = int($when / 1800);
+        $counts[$x > $max ? $max : $x]++;
+    }
+}
+</%INIT>
diff --git a/rt/html/RTx/Statistics/index.html b/rt/html/RTx/Statistics/index.html
new file mode 100755 (executable)
index 0000000..41490de
--- /dev/null
@@ -0,0 +1,59 @@
+%# BEGIN LICENSE BLOCK
+%# 
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%# 
+%# (Except where explictly superceded by other copyright notices)
+%# 
+%# Copyright this file (c) 2003 Harald Wagener <hwagener@hamburg.fcb.com>
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%# 
+%# 
+%# END LICENSE BLOCK
+<& /Elements/Header, Title => loc('RT Statistics') &>
+<& /RTx/Statistics/Elements/Tabs,  Title => loc('RT Statistics')  &>
+
+<&|/l&><h2>Description</h2>
+<p>These 6 options below enable you to display management data from the RT Database in table and graphical forms, enabling trends, bottlenecks, load problems etc to be identified.
+Each contains a description of how the data is displayed and describes the options available to you.</p></&>
+<ul>
+<li><strong><a href="CallsQueueDay/index.html">
+<&|/l&>Tickets per day per Queue</&></a></strong><br />
+<&|/l&>View the number of tickets created, resolved or deleted in a<br /> specific Queue, over the requested period of days</&>
+</li>
+<li><strong><a href="OpenStalled/index.html">
+<&|/l&>Tickets status by Queue</&></a></strong><br>
+<&|/l&>View numbers of new, open and stalled tickets in a selected Queue</&>
+</li>
+<li><strong><a href="CallsMultiQueue/index.html">
+<&|/l&>Tickets per Day in Multiple Queues</&>
+</a></strong><br>
+<&|/l&>View tickets created, resolved or deleted on in one or more Queues<br /> over a specified time period</&>
+</li>
+<li><strong><a href="DayOfWeek/index.html">
+<&|/l&>Tickets per Day of Week (absolute)</&></a></strong><br>
+<&|/l&>View trends showing when tickets are created, resolved or deleted</&>
+</li>
+<li><strong><a href="Resolution/index.html">
+<&|/l&>Time to Resolve</&></a></strong><br>
+<&|/l&>View how long tickets take to be resolved by Queue</&>
+</li>
+</li>
+<li><strong><a href="TimeToResolve/index.html">
+<&|/l&>Time to Resolve (scatter graph)</&></a></strong><br>
+<&|/l&>View a detailed scatter graph of time to resolve tickets by Queue</&>
+</li>
+</ul>
diff --git a/rt/html/Reports/Activity/ActivityDetail.html b/rt/html/Reports/Activity/ActivityDetail.html
new file mode 100644 (file)
index 0000000..ef0d830
--- /dev/null
@@ -0,0 +1,83 @@
+<&|Elements/Wrapper, %ARGS, title => loc("Activity detail"),
+    path => "Reports/Activity/ActivityDetail.html",
+    &>
+
+<& Elements/MiniPlot, data => \%counts &>
+
+<table style="width: 100%">
+<tr class="titlerow">
+<th>Queue</th><th>Activity</th><th>Date</th><th>Time</th><th>Ticket #</th><th>User</th><th>Short description</th>
+</tr>
+% for my $item (@items) {
+<tr>
+<td><% $item->{queue} %></td>
+<td><% $item->{status} %></td>
+<td><% $item->{date} %></td>
+<td><% $item->{time} %></td>
+<td><% $item->{id} %></td>
+<td><% $item->{actor} %></td>
+<td><% $item->{notes} %></td>
+</tr>
+% }
+</table>
+
+</&>
+<%args>
+$query => 'id > 0'
+$start => "2005/01/01"
+$end   => "2006/01/01"
+</%args>
+<%init>
+
+
+my $summary_tickets = RT::Tickets->new($session{'CurrentUser'});
+$summary_tickets->FromSQL($query . " AND ( Updated >= '$start' AND Updated <= '$end')");
+my %counts;
+while (my $ticket = $summary_tickets->Next) {
+    my $txns = $ticket->Transactions;
+    $txns->Limit(FIELD => 'Created', OPERATOR => '>=', VALUE => $start);
+    $txns->Limit(FIELD => 'Created', OPERATOR => '<=', VALUE => $end);
+    # I think they really don't just want status changes
+    $txns->Limit(FIELD => 'Type', VALUE => 'Status', ENTRYAGGREGATOR => 'OR');
+    $txns->Limit(FIELD => 'Type', VALUE => 'Create');
+
+    while (my $txn = $txns->Next){
+        my $date = substr($txn->Created, 0, 10);
+        # we don't have data on the status of a new ticket, default to 'new'
+        $counts{$date}{$txn->NewValue || 'new'}++;
+    }
+}
+
+
+my $tickets = RT::Tickets->new($session{'CurrentUser'});
+$tickets->FromSQL($query);
+my @items;
+while (my $ticket = $tickets->Next) {
+    my $txns = $ticket->Transactions;
+    $txns->Limit(FIELD => 'Created', OPERATOR => '>=', VALUE => $start);
+    $txns->Limit(FIELD => 'Created', OPERATOR => '<=', VALUE => $end);
+    # I think they really don't just want status changes
+    $txns->Limit(FIELD => 'Type', VALUE => 'Status', ENTRYAGGREGATOR => 'OR');
+    $txns->Limit(FIELD => 'Type', VALUE => 'Create');
+
+    while (my $txn = $txns->Next) {
+        push @items, { queue => $txn->TicketObj->QueueObj->Name,
+                       id => $txn->TicketObj->id,
+                       date => (split ' ', $txn->CreatedObj->ISO)[0],
+                       time => (split ' ', $txn->CreatedObj->ISO)[1],
+                       status => $txn->NewValue || 'new',
+                       actor => $txn->CreatorObj->Name,
+                       notes => ($txn->Content ne 'This transaction appears to have no content' ? substr($txn->Content, 0, 60) :  $txn->BriefDescription)
+                     };
+    }
+}
+
+@items = sort {
+           $a->{queue}    cmp $b->{'queue'}
+        || $a->{'status'} cmp $b->{'status'}
+        || $a->{'id'}     <=> $b->{'id'}
+        || $a->{'actor'}  cmp $b->{'actor'}
+        || $a->{'notes'}  <=> $b->{'notes'}
+} @items;
+
+</%init>
diff --git a/rt/html/Reports/Activity/ActivitySummary.html b/rt/html/Reports/Activity/ActivitySummary.html
new file mode 100644 (file)
index 0000000..7bb756f
--- /dev/null
@@ -0,0 +1,61 @@
+<&|Elements/Wrapper, %ARGS, title => loc("Activity summary"),
+    path => "Reports/Activity/ActivitySummary.html",
+    &>
+
+<& Elements/MiniPlot, data => \%queues &>
+
+<table style="width: 100%">
+<tr class="titlerow">
+<th>Queue</th>
+% for my $status (sort keys %status) {
+<th><% $status %></th>
+% }
+<th>Total</th>
+</tr>
+% for my $queue (sort keys %queues) {
+<th class="label"><% $queue %></th>
+% for my $status (sort keys %status) {
+<td><% $queues{$queue}{$status} || 0 %>
+% }
+<td><% $total{$queue} %></td>
+</tr>
+% }
+<tr class="grandtotal">
+<th class="label" >Grand Total</th>
+% for my $status (sort keys %status) {
+<td><% $status{$status} %></td>
+% }
+<td><% $total %></td>
+</table>
+</&>
+<%args>
+$query => 'id > 0'
+$start => "2005/01/01"
+$end   => "2006/01/01"
+</%args>
+<%init>
+
+my $tickets = RT::Tickets->new($session{'CurrentUser'});
+$tickets->FromSQL($query . " AND ( Updated >= '$start' AND Updated <= '$end')");
+
+my %queues;
+my %status;
+my %total;
+my $total;
+while (my $ticket = $tickets->Next) {
+    my $txns = $ticket->Transactions;
+    $txns->Limit(FIELD => 'Created', OPERATOR => '>=', VALUE => $start);
+    $txns->Limit(FIELD => 'Created', OPERATOR => '<=', VALUE => $end);
+    $txns->Limit(FIELD => 'Type', VALUE => 'Status', ENTRYAGGREGATOR => 'OR');
+    $txns->Limit(FIELD => 'Type', VALUE => 'Create');
+
+    while (my $txn = $txns->Next) {
+        $queues{$txn->TicketObj->QueueObj->Name}{$txn->NewValue || 'new'}++;   
+        $status{$txn->NewValue || 'new'}++;
+        $total{$txn->TicketObj->QueueObj->Name}++;
+        $total++;
+    }
+}
+
+
+</%init>
diff --git a/rt/html/Reports/Activity/Elements/LimitReport b/rt/html/Reports/Activity/Elements/LimitReport
new file mode 100644 (file)
index 0000000..7c4aac7
--- /dev/null
@@ -0,0 +1,23 @@
+<form action="index.html" method="POST" enctype="multipart/form-data">
+Query:
+<textarea name="query" rows="5" cols="80"><% $query %></textarea><br />
+
+Report type: <select name="type">
+<option value="ActivityDetail" <% $ARGS{path} =~ /ActivityDetail/ ? 'selected' : '' %>>Activity detail</option>
+<option value="ActivitySummary" <% $ARGS{path} =~ /ActivitySummary/ ? 'selected' : '' %>>Activity summary</option>
+<option value="ResolutionComments" <% $ARGS{path} =~ /ResolutionComments/ ? 'selected' : '' %>>Resolution comments</option>
+<option value="ResolutionStatistics" <% $ARGS{path} =~ /ResolutionStatistics/ ? 'selected' : '' %>>Resolution statistics</option>
+</select><br />
+
+Start date: <input type="text" name="start" value="<% $start %>" /><br />
+End date:   <input type="text" name="end"   value="<% $end   %>" /><br />
+<& /Elements/Submit, Label => loc('Report') &>
+</form>
+<%args>
+$type  => undef
+$start => undef
+$end   => undef
+$query => undef
+</%args>
+<%init>
+</%init>
diff --git a/rt/html/Reports/Activity/Elements/MiniPlot b/rt/html/Reports/Activity/Elements/MiniPlot
new file mode 100644 (file)
index 0000000..f920328
--- /dev/null
@@ -0,0 +1,57 @@
+<table class="miniplot"><tr>
+% for my $major (@major) {
+<td><div class="graph">
+    <ul>
+% my $i = 0;
+% for my $minor (@minor) {
+%   my $percent = int( 100 * ($data->{$major}{$minor} || 0) / $max );
+        <li class="c<% ($i % 6) + 1%>" style="width: <% $barwidth %>%; 
+                                              left: <% $baroffset + $each * $i %>%;
+                                              height: <% $percent %>%;"><div class="data"><% $minor %>: <% $percent %>%</div></li>
+% $i++;
+% }
+    </ul>
+</div></td>
+% }
+</tr><tr>
+% for my $major (@major) {
+<th class="legend"><% $major %></th>
+% }
+</tr>
+</table>
+
+<table class="miniplot"><tr>
+% my $i = 0;
+% for my $minor (@minor) {
+<th><span class="demoblock c<% ($i++ % 6) + 1 %>"></span> <% $minor %></th>
+% }
+</tr>
+</table>
+
+<%args>
+$data
+$major => undef
+$minor => undef
+</%args>
+<%init>
+
+my $max = 1;
+
+my %minor;
+for my $major (keys %{$data}) {
+    for (keys %{$data->{$major}}) {
+        $minor{$_}++;
+        $max = $data->{$major}{$_} if $data->{$major}{$_} > $max;
+    }
+}
+
+my @major = $major ? @{$major} : sort keys %{$data};
+my @minor = $minor ? @{$minor} : sort keys %minor;
+
+return unless @minor and @major;
+
+my $each      = int( (100 / @minor) );
+my $barwidth  = int( (100 / @minor) * (3/4) );
+my $baroffset = int( (100 / @minor) * (1/8) );
+
+</%init>
diff --git a/rt/html/Reports/Activity/Elements/PrintFooter b/rt/html/Reports/Activity/Elements/PrintFooter
new file mode 100644 (file)
index 0000000..fa9f475
--- /dev/null
@@ -0,0 +1,7 @@
+<hr/>
+<div style="text-align: center;">
+<%$RT::ReportFooterMessage || 'Proprietary and Confidential' %>
+</div>
+</body>
+</html>
+%$m->abort();
diff --git a/rt/html/Reports/Activity/Elements/PrintHeader b/rt/html/Reports/Activity/Elements/PrintHeader
new file mode 100644 (file)
index 0000000..b7c4b34
--- /dev/null
@@ -0,0 +1,32 @@
+<%args>
+$title => undef
+$path => undef
+$query => undef
+</%args>
+<HTML>
+<HEAD>
+<TITLE><%$title%></TITLE>
+<link rel="shortcut icon" href="<%$RT::WebImagesURL%>/favicon.png" type="image/png" />
+<link media="all" rel="stylesheet" href="<%$RT::WebPath%>/NoAuth/webrt.css" type="text/css" />
+<link media="print" rel="stylesheet" href="<%$RT::WebPath%>/NoAuth/printrt.css" type="text/css" />
+%# XXX TODO THIS SHOULD NOT BE A TABLE
+<body>
+<table width="100%">
+<tr>
+<td align="left">
+<div id="username">User: <%$session{'CurrentUser'}->Name%></div>
+<div id="reportdate">
+%my $d= RT::Date->new($session{'CurrentUser'}); $d->SetToNow; 
+<%$d->AsString%></div>
+</td>
+<td align="center">
+<h1><%$title%></h1>
+</td>
+<td align="right">
+<img src="<%$RT::LogoURL%>" alt="RT Logo"/>
+</td>
+</tr>
+</table>
+<hr/>
+<&|/l&>Report criteria:</&> <%$query%>
+<hr />
diff --git a/rt/html/Reports/Activity/Elements/ScreenFooter b/rt/html/Reports/Activity/Elements/ScreenFooter
new file mode 100644 (file)
index 0000000..235b7b3
--- /dev/null
@@ -0,0 +1,13 @@
+<& LimitReport, %ARGS &>
+% if ($show_print_link) {
+<div align="right">
+% my %printable_args = %ARGS;
+% delete $printable_args{$_} for (qw/path title mode/);
+% $printable_args{'mode'} = 'print';
+% my $url = $ARGS{'path'} .'?'. join(';', map { $_."=".$printable_args{$_} } keys %printable_args);
+<a href="<%$RT::WebPath|n%>/<%$url|n%>"><&|/l&>Printable version</&></a>
+</div>
+% }
+<%args>
+$show_print_link => 1
+</%args>
diff --git a/rt/html/Reports/Activity/Elements/ScreenHeader b/rt/html/Reports/Activity/Elements/ScreenHeader
new file mode 100644 (file)
index 0000000..080efc0
--- /dev/null
@@ -0,0 +1,8 @@
+<%args>
+$title => undef
+$path => undef
+</%args>
+<& /Elements/Header, Title => $title &>
+<& Tabs,
+    current_subtab => $path,
+    Title => $title &> 
diff --git a/rt/html/Reports/Activity/Elements/Tabs b/rt/html/Reports/Activity/Elements/Tabs
new file mode 100644 (file)
index 0000000..a949820
--- /dev/null
@@ -0,0 +1,52 @@
+<& /Elements/Tabs, 
+    tabs => $tabs,
+    subtabs => $subtabs,
+    current_toptab => 'Tools/Offline.html', 
+    current_tab => 'Reports/Activity/index.html'.$args, 
+    Title => $Title &>
+
+<%INIT>
+my $subtabs = {};
+
+my $top = $m->caller_args(-1);
+my $args = "?" . $m->comp( '/Elements/QueryString',
+                           query => $top->{query},
+                           start => $top->{start},
+                           end   => $top->{end});
+if ($m->caller_args(-1)->{'query'}) {
+    $current_subtab .= $args;
+    $subtabs = {
+                a => { title => 'Activity detail',
+                       path  => 'Reports/Activity/ActivityDetail.html'.$args,
+                     },
+                b => { title => 'Activity summary',
+                       path  => 'Reports/Activity/ActivitySummary.html'.$args,
+                     },
+                c => { title => 'Resolution comments',
+                       path  => 'Reports/Activity/ResolutionComments.html'.$args,
+                     },
+                d => { title => 'Resolution statistics',
+                       path  => 'Reports/Activity/ResolutionStatistics.html'.$args,
+                     },
+               };
+}
+
+my $tabs = {
+            a => { title => loc('Offline'),
+                   path  => 'Tools/Offline.html',
+                 },
+            r => { title => loc('Reports'),
+                   path  => 'Reports/Activity/index.html'.$args,
+                   subtabs => $subtabs,
+                   current_subtab => $current_subtab,
+                 }
+             };
+
+</%INIT>
+
+
+<%ARGS>
+$current_tab => undef
+$current_subtab => undef
+$Title => undef
+</%ARGS>
diff --git a/rt/html/Reports/Activity/Elements/Wrapper b/rt/html/Reports/Activity/Elements/Wrapper
new file mode 100644 (file)
index 0000000..6f81f5f
--- /dev/null
@@ -0,0 +1,16 @@
+<%args>
+$mode => 'screen'
+</%args>
+
+% if ($mode eq 'print') {
+<& PrintHeader, %ARGS &>
+%} else {
+<& ScreenHeader, %ARGS &>
+% }
+<%$m->content |n%>
+% if ($mode eq 'print') {
+<& PrintFooter, %ARGS &>
+%} else {
+<& ScreenFooter, %ARGS &>
+% }
+
diff --git a/rt/html/Reports/Activity/ResolutionComments.html b/rt/html/Reports/Activity/ResolutionComments.html
new file mode 100644 (file)
index 0000000..81ca301
--- /dev/null
@@ -0,0 +1,62 @@
+<&|Elements/Wrapper, %ARGS, title => loc("Resolution Comments"), 
+    path => "Reports/Activity/ResolutionComments.html",
+    &>
+
+<table style="width: 100%">
+<tr>
+<th>Queue</th><th>Ticket #</th><th>Created</th><th>Resolved</th><th>Time to resolve</th>
+</tr>
+<tr>
+<th colspan="5">Resolution comments</th>
+</tr>
+% for my $item (@items) {
+<tr class="titlerow">
+<td><% $item->{queue} %></td>
+<td><% $item->{id} %></td>
+<td><% $item->{created} %></td>
+<td><% $item->{resolved} %></td>
+<td><% $item->{duration} %></td>
+</tr>
+<tr>
+<td colspan="5"><% $item->{whiteboard} %></td>
+</tr>
+% }
+</table>
+</&>
+
+<%args>
+$query => 'id > 0'
+$start => "2005/01/01"
+$end   => "2006/01/01"
+</%args>
+<%init>
+
+use Time::Duration;
+
+my $summary_tickets = RT::Tickets->new( $session{'CurrentUser'} );
+$summary_tickets->FromSQL(
+    $query . " AND (Status = 'resolved') AND ( Updated >= '$start' AND Updated <= '$end')" );
+
+my @items;
+while ( my $ticket = $summary_tickets->Next ) {
+    push @items, {
+        queue    => $ticket->QueueObj->Name,
+        id       => $ticket->id,
+        created  => $ticket->CreatedObj->AsString,
+        resolved => $ticket->ResolvedObj->AsString,
+        duration => Time::Duration::concise(
+            Time::Duration::duration(
+                $ticket->ResolvedObj->Unix - $ticket->CreatedObj->Unix
+            )
+        ),
+        whiteboard => $ticket->FirstCustomFieldValue('Whiteboard')
+    };
+}
+
+@items = sort { $a->{queue} cmp $b->{queue} || $a->{id} <=> $b->{id} } @items;
+
+
+
+
+
+</%init>
diff --git a/rt/html/Reports/Activity/ResolutionStatistics.html b/rt/html/Reports/Activity/ResolutionStatistics.html
new file mode 100644 (file)
index 0000000..4ecde2c
--- /dev/null
@@ -0,0 +1,95 @@
+<&|Elements/Wrapper, %ARGS, title => loc("Resolution statistics"),
+    path => "Reports/Activity/ResolutionStatistics.html",
+    &>
+
+<& Elements/MiniPlot,
+   data => \%plot,
+   major => ['Date range','Last 30 days','Last 60 days','Last 90 days','Ever'],
+   minor => [(sort keys %queues), "Average"]
+ &>
+
+<table style="width: 100%">
+<tr>
+<td></td><th colspan="4">Number of tickets closed / Average resolution time per ticket</th>
+</tr>
+<tr class="titlerow">
+<th>Queue</th>
+<th>Date range</th>
+<th>Last 30 days</th>
+<th>Last 60 days</th>
+<th>Last 90 days</th>
+<th>Ever</th>
+</tr>
+% for my $queue (sort keys %queues) {
+<tr>
+<th><% $queue %></th>
+% for my $period ('Date range','Last 30 days','Last 60 days','Last 90 days','Ever') {
+<td><% scalar @{$closed{$period}{$queue}} %> / <% $average_resolve_times{$period}{$queue} %></td>
+% }
+</tr>
+% }
+<tr class="grandtotal">
+<th>Ticket average</th>
+% for my $period ('Date range','Last 30 days','Last 60 days','Last 90 days','Ever') {
+<td><% $average_resolve_times{$period}{_all_count} %> / <% $average_resolve_times{$period}{_all} %></td>
+% }
+</tr>
+</table>
+
+</&>
+<%args>
+$query => 'id > 0'
+$start => "2005/01/01"
+$end   => "2006/01/01"
+</%args>
+<%init>
+
+my $in_30_days = RT::Date->new($session{'CurrentUser'});
+$in_30_days->Set(Format => 'Unix', Value => ( time - (86400*30)));
+my $in_60_days = RT::Date->new($session{'CurrentUser'});
+$in_60_days->Set(Format => 'Unix', Value => ( time - (86400*60)));
+my $in_90_days = RT::Date->new($session{'CurrentUser'});
+$in_90_days->Set(Format => 'Unix', Value => ( time - (86400*90)));
+
+my %queries;
+$queries{'Date range'}   = "(Resolved >= '$start' AND Resolved <= '$end')";
+$queries{'Last 30 days'} = "(Resolved >= '".$in_30_days->ISO."')";
+$queries{'Last 60 days'} = "(Resolved >= '".$in_60_days->ISO."')";
+$queries{'Last 90 days'} = "(Resolved >= '".$in_90_days->ISO."')";
+$queries{'Ever'}         = "(Status = 'resolved' OR Status = 'rejected')";
+
+
+my %closed;
+my %queues;
+foreach my $period (keys %queries) {
+    my $tix = RT::Tickets->new($session{'CurrentUser'});
+    $tix->FromSQL($query . " AND " .$queries{$period});
+
+    while (my $ticket = $tix->Next) {
+        push @{ $closed{$period}{$ticket->QueueObj->Name}}, $ticket;
+        $queues{$ticket->QueueObj->Name}++;
+    }
+}
+
+my %restimes;
+my %average_resolve_times;
+my %plot;
+use Time::Duration;
+foreach my $period ( keys %closed ) {
+    foreach my $queue ( keys %{$closed{$period}} ) {
+        foreach my $ticket (@{$closed{$period}{$queue}} ) {
+            push @{$restimes{$period}{$queue}}, ( $ticket->ResolvedObj->Unix - $ticket->CreatedObj->Unix);
+        }
+
+        my $total_time = 0;
+        $total_time+= $_ for @{$restimes{$period}{$queue}};
+        $average_resolve_times{$period}{'_all_time'} += $total_time;
+        $average_resolve_times{$period}{'_all_count'} += @{$restimes{$period}{$queue}};
+        $plot{$period}{$queue} = $total_time / @{$restimes{$period}{$queue}};
+        $average_resolve_times{$period}{$queue} = Time::Duration::concise(Time::Duration::duration($plot{$period}{$queue}));
+    }
+    $plot{$period}{Average} = $average_resolve_times{$period}{'_all_time'} / $average_resolve_times{$period}{'_all_count'};
+    $average_resolve_times{$period}{'_all'}  = Time::Duration::concise(Time::Duration::duration($plot{$period}{Average}));
+}
+
+</%init>
diff --git a/rt/html/Reports/Activity/index.html b/rt/html/Reports/Activity/index.html
new file mode 100644 (file)
index 0000000..1f6ddb0
--- /dev/null
@@ -0,0 +1,29 @@
+<&| Elements/Wrapper, %ARGS, title => loc("Activity reports"), show_print_link => 0 &>
+
+
+</&>
+
+<%args>
+$type  => undef
+$start => undef
+$end   => undef
+$query => "Status = 'resolved'"
+</%args>
+<%init>
+
+unless ($start) {
+    my $then = RT::Date->new($session{'CurrentUser'});
+    $then->Set(Format => 'Unix', Value => time - (86400*7));
+    $ARGS{start} = substr($then->ISO,0,10);
+}
+
+unless ($end) {
+    my $now = RT::Date->new($session{'CurrentUser'});
+    $now->SetToNow();
+    $ARGS{end} = substr($now->ISO,0,10);
+}
+
+if ($type) {
+    $m->redirect($type . ".html?" . $m->comp('/Elements/QueryString', query => $query, start => $start, end => $end));
+}
+</%init>
diff --git a/rt/lib/RT/Extension/ActivityReports.pm b/rt/lib/RT/Extension/ActivityReports.pm
new file mode 100644 (file)
index 0000000..52d8ba6
--- /dev/null
@@ -0,0 +1,3 @@
+package RT::Extension::ActivityReports;
+
+our $VERSION = '0.2';
diff --git a/rt/lib/RTx/Statistics.pm b/rt/lib/RTx/Statistics.pm
new file mode 100755 (executable)
index 0000000..8b9d6e4
--- /dev/null
@@ -0,0 +1,239 @@
+package Statistics;
+
+use vars qw(
+$MultiQueueStatus $MultiQueueDateFormat @MultiQueueQueueList $MultiQueueMaxRows $MultiQueueWeekends $MultiQueueLabelDateFormat
+$PerDayStatus $PerDayDateFormat $PerDayQueue $PerDayMaxRows $PerDayWeekends $PerDayLabelDateFormat $PerDayPeriod
+$DayOfWeekQueue
+@OpenStalledQueueList $OpenStalledWeekends
+$TimeToResolveDateFormat $TimeToResolveQueue $TimeToResolveMaxRows $TimeToResolveWeekends $TimeToResolveLabelDateFormat
+$TimeToResolveGraphQueue
+@years @months %monthsMaxDay
+$secsPerDay
+$RestrictAccess
+$GraphWidth $GraphHeight
+);
+
+use Time::Local;
+
+# I couldn't figure out a way to override these in RT_SiteConfig, which would be
+# preferable.
+
+# Width and Height of all graphics
+$GraphWidth=500;
+$GraphHeight=400;
+
+# Initial settings for the CallsMultiQueue stat page
+$MultiQueueStatus = "resolved";
+$MultiQueueDateFormat = "%a %b %d %Y";  # format for dates on Multi Queue report, see "man strftime" for options
+@MultiQueueQueueList = ("General"); # list of queues to start Multi Queue per day reports
+$MultiQueueMaxRows = 10;
+$MultiQueueWeekends = 1;
+$MultiQueueLabelDateFormat = "%a";
+
+# Initial settings for the CallsQueueDay stat page
+$PerDayStatus = "resolved";
+$PerDayDateFormat = "%a %b %d %Y";
+$PerDayQueue = "General";
+$PerDayMaxRows = 10;
+$PerDayWeekends = 1;
+$PerDayLabelDateFormat = "%a";
+$PerDayPeriod = 10;
+
+# Initial settings for the DayOfWeek stat page
+$DayOfWeekQueue = "General";
+
+# Initial settings for the OpenStalled stat page
+@OpenStalledQueueList = ("General");
+$OpenStalledWeekends = 1;
+
+# Initial settings for the TimeToResolve stat page
+$TimeToResolveDateFormat = "%a %b %d"; 
+$TimeToResolveQueue = "General";
+$TimeToResolveMaxRows = 10;
+$TimeToResolveWeekends = 1;
+$TimeToResolveLabelDateFormat = "%a";
+
+# Initial settings for the TimeToResolve Graph page
+$TimeToResolveGraphQueue = "General";
+
+$secsPerDay = 86400;
+
+# List of years and months to populate drop down lists
+@years =('2010', '2009', '2008', '2007', '2006', '2005', '2004', '2003' ,'2003' ,'2002');
+@months=qw/Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec/;     
+%monthsMaxDay = (
+                0 => 31,  # January
+                1 => 29,  # February, allow for leap year
+                2 => 31,  # March
+                3 => 30,  # April
+                4 => 31,  # May
+                5 => 30,  # June
+                6 => 31,  # July
+                7 => 31,  # August
+                8 => 30,  # September
+                9 => 31,  # October
+                10=> 30,  # November
+                11=> 31   # December
+                );
+
+# Set to one to prevent users without the ShowConfigTab right from seeing Statistics
+$RestrictAccess = 0;
+
+# Variables to control debugging
+my $debugging=0;  # set to 1 to enable debugging
+my $debugtext="";
+
+=head2 FormatDate 
+
+Returns a string representing the specified date formatted by the specified string
+
+=cut
+sub FormatDate {
+    my $fmt = shift;
+    my $self = shift;
+    return POSIX::strftime($fmt, localtime($self->Unix));
+}
+
+
+=head2 RTDateSetToLocalMidnight
+
+Sets the date to midnight (at the beginning of the day) local time
+Returns the unixtime at midnight.
+
+=cut
+sub RTDateSetToLocalMidnight {
+    my $self = shift;
+    
+    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = localtime($self->Unix);
+    $self->Unix(timelocal (0,0,0,$mday,$mon,$year,$wday,$yday));
+    
+    return ($self->Unix);
+}
+
+=head2 RTDateIsWeekend
+
+Returns 1 if the date is on saturday or sunday
+
+=cut
+sub RTDateIsWeekend {
+    my $self = shift;
+    
+    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = localtime($self->Unix);
+    return 1 if (($wday==6) || ($wday==0));
+    0;
+}
+
+=head2 RTDateGetDateWeekday
+
+Returns the localized name of the day specified by date
+
+=cut
+sub RTDateGetDateWeekday {
+    my $self = shift;
+    
+    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = localtime($self->Unix);
+    return $self->GetWeekday($wday);
+}
+
+=head2 RTDateSubDay
+
+Subtracts 24 hours from the current time
+
+=cut
+
+sub RTDateSubDay {
+    my $self = shift;
+    $self->AddSeconds(0 - $DAY);
+}
+
+=head2 RTDateSubDays $DAYS
+
+Subtracts 24 hours * $DAYS from the current time
+
+=cut
+
+sub RTDateSubDays {
+    my $self = shift;
+    my $days = shift;
+    $self->AddSeconds(0 - ($days * $DAY));
+}
+
+=head2 DebugInit
+
+Creates a text area on the page if debugging is on.
+
+=cut
+
+sub DebugInit {
+    if($debugging) {
+       my $m = shift;
+       $m->print("<TEXTAREA NAME=debugarea COLS=120 ROWS=50>$debugtext</TEXTAREA>\n");
+    }
+}
+
+=head2 DebugLog $logmsg
+
+Adds a message to the debug area
+
+=cut
+
+sub DebugLog {
+    if($debugging) {
+       my $line = shift;
+       $debugtext .= $line;
+       $RT::Logger->debug($line);
+    }
+}
+
+=head2 DebugClear
+
+Clears the current debug string, otherwise it builds from page to page
+
+=cut
+
+sub DebugClear {
+    if($debugging) {
+       $debugtext = undef;
+    }
+}
+
+=head2 DurationAsString 
+
+Returns a string representing the specified duration
+
+=cut
+
+sub DurationAsString {
+  my $Duration = shift;
+  my $MINUTE = 60;
+  my $HOUR =  $MINUTE*60;
+  my $DAY = $HOUR * 24;
+  my $WEEK = $DAY * 7;
+  my $days = int($Duration / $DAY);
+  $Duration = $Duration % $DAY;
+  my $hours = int($Duration / $HOUR);
+  $hours = sprintf("%02d", $hours);
+  $Duration = $Duration % $HOUR;
+  my $minutes = int($Duration/$MINUTE);
+  $minutes = sprintf("%02d", $minutes);
+  $Duration = $Duration % $MINUTE;
+  my $secs = sprintf("%02d", $Duration);
+
+  if(!$days) {
+      $days = "00";
+  }
+  if(!$hours) {
+      $hours = "00";
+  }
+  if(!$minutes) {
+      $minutes = "00";
+  }
+  if(!$secs) {
+      $secs = "00";
+  }
+  return "$days days $hours:$minutes:$secs";
+}
+
+1;
+
+