From: mark Date: Sat, 16 Apr 2011 00:01:37 +0000 (+0000) Subject: XLS and CSV download for 2.1, #10855 X-Git-Tag: freeside_2_1_3~279 X-Git-Url: http://git.freeside.biz/gitweb/?a=commitdiff_plain;h=ac2a516d126ab23b46138a6b930545fecc633943;p=freeside.git XLS and CSV download for 2.1, #10855 --- diff --git a/rt/FREESIDE_MODIFIED b/rt/FREESIDE_MODIFIED index 6bef539be..00e101147 100644 --- a/rt/FREESIDE_MODIFIED +++ b/rt/FREESIDE_MODIFIED @@ -126,3 +126,10 @@ share/html/Callbacks/SearchCustomerFields/* share/html/Callbacks/RTx-Statistics/* share/html/RTx/Statistics/* +share/html/Callbacks/Results-XLS/* +share/html/Search/Results.xls +lib/RT/Extension/SearchResults/XLS.pm + +share/html/Search/Results.csv +share/html/Search/Elements/ResultViews + diff --git a/rt/lib/RT/Extension/SearchResults/XLS.pm b/rt/lib/RT/Extension/SearchResults/XLS.pm new file mode 100644 index 000000000..b5d242b97 --- /dev/null +++ b/rt/lib/RT/Extension/SearchResults/XLS.pm @@ -0,0 +1,82 @@ +package RT::Extension::SearchResults::XLS; + +use warnings; +use strict; + +=head1 NAME + +RT::Extension::SearchResults::XLS - Add Excel format export to RT search results + +=head1 VERSION + +Version 0.06 + +=cut + +our $VERSION = '0.06'; + + +=head1 SYNOPSIS + +This RT Extension allow users to download search results in Microsoft Excel +binary format. This typically fix encoding problems for non-ascii chars with +the standard TSV export included in RT. + +=head1 AUTHOR + +Emmanuel Lacour, C<< >> + +=head1 BUGS + +Please report any bugs or feature requests to C, or through +the web interface at L. I will be notified, and then you'll +automatically be notified of progress on your bug as I make changes. + + + + +=head1 SUPPORT + +You can find documentation for this module with the perldoc command. + + perldoc RT::Extension::SearchResults::XLS + + +You can also look for information at: + +=over 4 + +=item * RT: CPAN's request tracker + +L + +=item * AnnoCPAN: Annotated CPAN documentation + +L + +=item * CPAN Ratings + +L + +=item * Search CPAN + +L + +=back + + +=head1 ACKNOWLEDGEMENTS + + +=head1 COPYRIGHT & LICENSE + +Copyright 2008-2010 Emmanuel Lacour, all rights reserved. + +This program is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +Request Tracker (RT) is Copyright Best Practical Solutions, LLC. + +=cut + +1; # End of RT::Extension::SearchResults::XLS diff --git a/rt/share/html/Callbacks/Results-XLS/Search/Elements/ResultViews/AfterTools b/rt/share/html/Callbacks/Results-XLS/Search/Elements/ResultViews/AfterTools new file mode 100644 index 000000000..c84e6602d --- /dev/null +++ b/rt/share/html/Callbacks/Results-XLS/Search/Elements/ResultViews/AfterTools @@ -0,0 +1,4 @@ +
  • <&|/l&>XLS
  • +<%ARGS> +$QueryString => undef + diff --git a/rt/share/html/Search/Elements/ResultViews b/rt/share/html/Search/Elements/ResultViews index b438b56a0..7f6d7de86 100644 --- a/rt/share/html/Search/Elements/ResultViews +++ b/rt/share/html/Search/Elements/ResultViews @@ -1,40 +1,40 @@ %# BEGIN BPS TAGGED BLOCK {{{ -%# +%# %# COPYRIGHT: -%# -%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC -%# -%# +%# +%# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +%# +%# %# (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., 51 Franklin Street, Fifth Floor, Boston, MA %# 02110-1301 or visit their web page on the internet at %# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -%# -%# +%# +%# %# 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 @@ -43,7 +43,7 @@ %# 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> $QueryString => undef @@ -57,10 +57,11 @@ $ShortQueryString => undef diff --git a/rt/share/html/Search/Results.csv b/rt/share/html/Search/Results.csv new file mode 100644 index 000000000..8551c9b80 --- /dev/null +++ b/rt/share/html/Search/Results.csv @@ -0,0 +1,172 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +%# +%# +%# (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., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# 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> +$Query => '' +$OrderBy => 'id' +$Order => 'ASC' + +<%INIT> + +eval "use Text::CSV_XS"; +if ( $@ ) { + $m->comp('/Error', Why => "Error loading Text::CSV_XS.\n$@"); + $m->abort; + return; +} + +my $csv = Text::CSV_XS->new( { eol => "\n" } ); + +my $Tickets = RT::Tickets->new( $session{'CurrentUser'} ); +$Tickets->FromSQL( $Query ); +if ( $OrderBy =~ /\|/ ) { + # Multiple Sorts + my @OrderBy = split /\|/, $OrderBy; + my @Order = split /\|/, $Order; + $Tickets->OrderByCols( + map { { FIELD => $OrderBy[$_], ORDER => $Order[$_] } } + ( 0 .. $#OrderBy ) + ); +} +else { + $Tickets->OrderBy( FIELD => $OrderBy, ORDER => $Order ); +} + +my %cf_id_to_name; +my %cf_name_to_pos; +{ + my $cfs = RT::SQL::PossibleCustomFields( + Query => $Query, CurrentUser => $session{'CurrentUser'}, + ); + while ( my $cf = $cfs->Next ) { + my $name = $cf->Name; + $cf_id_to_name{ $cf->id } = $name; + next if $cf_name_to_pos{ $name }; + + $cf_name_to_pos{ $name } = + (sort { $b <=> $a } values %cf_name_to_pos)[0] + 1; + } +} + +my @attrs = qw( + id QueueObj->Name Subject Status + TimeEstimated TimeWorked TimeLeft + Priority FinalPriority + OwnerObj->Name + Requestors->MemberEmailAddressesAsString + Cc->MemberEmailAddressesAsString + AdminCc->MemberEmailAddressesAsString + DueObj->ISO ToldObj->ISO CreatedObj->ISO + ResolvedObj->ISO LastUpdatedObj->ISO LastUpdatedByObj->Name +); + +$r->content_type('text/csv'); +$r->header_out('Content-Disposition' => 'attachment;filename="Results.csv"'); +{ + my @header; + foreach my $attr (@attrs) { + my $label = $attr; + $label =~ s'Obj-.(?:AsString|Name|ISO)''g; + $label =~ s'-\>MemberEmailAddressesAsString''g; + push @header, $label; + } + + $_ += @header - 1 foreach values %cf_name_to_pos; + + foreach my $name ( sort { $cf_name_to_pos{$a} <=> $cf_name_to_pos{$b} } keys %cf_name_to_pos ) { + push @header, "CF-". $name; + } + $csv->combine(@header); + $m->out($csv->string()); + $m->flush_buffer; +} + +my $i = 0; +while ( my $Ticket = $Tickets->Next()) { + my @row; + foreach my $attr (@attrs) { + my $value; + if ($attr =~ /(.*)->ISO$/ and $Ticket->$1->Unix <= 0) { + $value = ''; + } else { + my $method = '$Ticket->'.$attr.'()'; + $method =~ s/->ISO\(\)$/->ISO( Timezone => 'user' )/; + $value = eval $method; + if ($@) {die "Failed to find $attr - ". $@}; + } + push @row, $value; + } + + my $values = $Ticket->CustomFieldValues; + $values->OrderByCols; # don't sort them + while (my $value = $values->Next) { + my $pos = $cf_name_to_pos{ $cf_id_to_name{ $value->CustomField } }; + next unless $pos; + + $row[$pos] = '' unless defined $row[$pos]; + $row[$pos] .= ', ' if $row[$pos]; + $row[$pos] .= $value->Content; + } + + # remove tabs from all field values, they screw up the tsv + for (@row) { + $_ = '' unless defined; + $_ =~ s/(?:\n|\r)//g; + $_ =~ s{\t}{ }g; + } + + $csv->combine(@row); + $m->out($csv->string()); + + unless (++$i%10) { + $i = 0; + $m->flush_buffer; + } +} + +$m->abort(); + diff --git a/rt/share/html/Search/Results.tsv b/rt/share/html/Search/Results.tsv index 2a0de14e6..cdf1ebad0 100644 --- a/rt/share/html/Search/Results.tsv +++ b/rt/share/html/Search/Results.tsv @@ -95,8 +95,8 @@ my @attrs = qw( ResolvedObj->ISO LastUpdatedObj->ISO LastUpdatedByObj->Name ); -$r->content_type('text/csv'); -$r->header_out('Content-Disposition' => 'attachment;filename="Results.csv"'); +$r->content_type('text/tab-separated-values'); +$r->header_out('Content-Disposition' => 'attachment;filename="Results.tsv"'); { my @header; foreach my $attr (@attrs) { diff --git a/rt/share/html/Search/Results.xls b/rt/share/html/Search/Results.xls new file mode 100644 index 000000000..8b3f11b64 --- /dev/null +++ b/rt/share/html/Search/Results.xls @@ -0,0 +1,173 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +%# +%# +%# (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., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# 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> +$Query => '' +$OrderBy => 'id' +$Order => 'ASC' + +<%INIT> + +use Spreadsheet::WriteExcel; +my $xls; +my $fh; +open ($fh, ">", \$xls) or die "$!"; +my $workbook = Spreadsheet::WriteExcel->new($fh) or die $!; +my $worksheet = $workbook->add_worksheet(); + +my $Tickets = RT::Tickets->new( $session{'CurrentUser'} ); +$Tickets->FromSQL( $Query ); +if ( $OrderBy =~ /\|/ ) { + # Multiple Sorts + my @OrderBy = split /\|/, $OrderBy; + my @Order = split /\|/, $Order; + $Tickets->OrderByCols( + map { { FIELD => $OrderBy[$_], ORDER => $Order[$_] } } + ( 0 .. $#OrderBy ) + ); +} +else { + $Tickets->OrderBy( FIELD => $OrderBy, ORDER => $Order ); +} + +my %cf_id_to_name; +my %cf_name_to_pos; +{ + my $cfs = RT::SQL::PossibleCustomFields( + Query => $Query, CurrentUser => $session{'CurrentUser'}, + ); + while ( my $cf = $cfs->Next ) { + my $name = $cf->Name; + $cf_id_to_name{ $cf->id } = $name; + next if $cf_name_to_pos{ $name }; + + $cf_name_to_pos{ $name } = + (sort { $b <=> $a } values %cf_name_to_pos)[0] + 1; + } +} + +my @attrs = qw( + id QueueObj->Name Subject Status + TimeEstimated TimeWorked TimeLeft + Priority FinalPriority + OwnerObj->Name + Requestors->MemberEmailAddressesAsString + Cc->MemberEmailAddressesAsString + AdminCc->MemberEmailAddressesAsString + DueObj->ISO ToldObj->ISO CreatedObj->ISO + ResolvedObj->ISO LastUpdatedObj->ISO +); + +$r->content_type('application/vnd.ms-excel'); +$r->header_out('Content-Disposition' => 'attachment;filename="Results.xls"'); +{ + my @header; + foreach my $attr (@attrs) { + my $label = $attr; + $label =~ s'Obj-.(?:AsString|Name|ISO)''g; + $label =~ s'-\>MemberEmailAddressesAsString''g; + push @header, $label; + } + + $_ += @header - 1 foreach values %cf_name_to_pos; + + foreach my $name ( sort { $cf_name_to_pos{$a} <=> $cf_name_to_pos{$b} } keys %cf_name_to_pos ) { + push @header, "CF-". $name; + } + my $ws_col = 0; + foreach my $ws_val ( @header ) { + $worksheet->write(0, $ws_col, $ws_val); + $ws_col++; + } +} + +my $i = 0; +my $ws_row = 1; +while ( my $Ticket = $Tickets->Next()) { + my @row; + foreach my $attr (@attrs) { + my $value; + if ($attr =~ /(.*)->ISO$/ and $Ticket->$1->Unix <= 0) { + $value = ''; + } else { + my $method = '$Ticket->'.$attr.'()'; + $method =~ s/->ISO\(\)$/->ISO( Timezone => 'user' )/; + $value = eval $method; + if ($@) {die "Failed to find $attr - ". $@}; + } + push @row, $value; + } + + my $values = $Ticket->CustomFieldValues; + $values->OrderByCols; # don't sort them + while (my $value = $values->Next) { + my $pos = $cf_name_to_pos{ $cf_id_to_name{ $value->CustomField } }; + next unless $pos; + + $row[$pos] = '' unless defined $row[$pos]; + $row[$pos] .= ', ' if $row[$pos]; + $row[$pos] .= $value->Content; + } + + my $ws_col = 0; + foreach my $ws_val ( @row ) { + $worksheet->write($ws_row, $ws_col, $ws_val); + $ws_col++; + } + $ws_row++; + + unless (++$i%10) { + $i = 0; + $m->flush_buffer; + } +} + +$workbook->close; +close($fh); +$m->print($xls); +$m->abort(); +