From 8edfa5e111091a7f8d48664e4ba1ca9e63e992bf Mon Sep 17 00:00:00 2001 From: Andreas Granig Date: Tue, 30 Aug 2016 17:21:06 +0200 Subject: [PATCH] TT#2757 Rework emergemcy mapping CSV up/download Auto-detect CSV separator (requested by customer). Move reseller id out of CSV into form/api-param. Fix empty prefix to store as NULL instead of empty string. Change-Id: Id6776d3cddb6bc1745a23a79e9f590ef934608aa --- .../Panel/Controller/API/EmergencyMappings.pm | 28 +- lib/NGCP/Panel/Controller/EmergencyMapping.pm | 73 ++- .../Panel/Form/EmergencyMapping/Download.pm | 44 ++ .../Panel/Form/EmergencyMapping/Upload.pm | 1 - .../Form/EmergencyMapping/UploadAdmin.pm | 25 + lib/NGCP/Panel/Utils/CSVSeparator.pm | 537 ++++++++++++++++++ lib/NGCP/Panel/Utils/EmergencyMapping.pm | 41 +- 7 files changed, 711 insertions(+), 38 deletions(-) create mode 100644 lib/NGCP/Panel/Form/EmergencyMapping/Download.pm create mode 100644 lib/NGCP/Panel/Form/EmergencyMapping/UploadAdmin.pm create mode 100644 lib/NGCP/Panel/Utils/CSVSeparator.pm diff --git a/lib/NGCP/Panel/Controller/API/EmergencyMappings.pm b/lib/NGCP/Panel/Controller/API/EmergencyMappings.pm index a8611f1333..f826849a47 100644 --- a/lib/NGCP/Panel/Controller/API/EmergencyMappings.pm +++ b/lib/NGCP/Panel/Controller/API/EmergencyMappings.pm @@ -27,7 +27,7 @@ sub allowed_methods{ sub api_description { - return 'Defines emergency mappings for an Emergency Mapping Container. You can POST mappings individually one-by-one using json. To bulk-upload mappings, specify the Content-Type as "text/csv" and POST the CSV in the request body to the collection with an optional parameter "purge_existing=true", like "/api/emergencymappings/?purge_existing=true"'; + return 'Defines emergency mappings for an Emergency Mapping Container. You can POST mappings individually one-by-one using json. To bulk-upload mappings, specify the Content-Type as "text/csv", pass a reseller_id URL parameter and POST the CSV in the request body to the collection with an optional parameter "purge_existing=true", like "/api/emergencymappings/?reseller_id=123&purge_existing=true"'; }; sub query_params { @@ -181,16 +181,27 @@ sub POST :Allow { if ($data) { my($mappings, $fails, $text_success); try { + if($c->user->roles eq "reseller") { + $resource->{reseller_id} = $c->user->reseller_id; + } else { + unless(defined $resource->{reseller_id}) { + $c->log->error("Missing reseller_id"); + $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "missing reseller_id parameter"); + last; + } + unless($schema->resultset('resellers')->find($resource->{reseller_id})) { + $c->log->error("Invalid reseller_id '$$resource{reseller_id}'"); + $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "invalid reseller_id '$$resource{reseller_id}'"); + last; + } + } if ($resource->{purge_existing} eq 'true') { my ($start, $end); $start = time; - NGCP::Panel::Utils::MySQL::truncate_table( - c => $c, - schema => $schema, - do_transaction => 0, - table => 'provisioning.emergency_mappings', - ); - $schema->resultset('emergency_containers')->delete; + my $rs = $schema->resultset('emergency_containers')->search({ + reseller_id => $resource->{reseller_id}, + }); + $rs->delete; $end = time; $c->log->debug("API Purging emergency mappings entries took " . ($end - $start) . "s"); } @@ -199,6 +210,7 @@ sub POST :Allow { c => $c, data => \$data, schema => $schema, + reseller_id => $resource->{reseller_id}, ); $c->log->info( $$text_success ); diff --git a/lib/NGCP/Panel/Controller/EmergencyMapping.pm b/lib/NGCP/Panel/Controller/EmergencyMapping.pm index 783a602fa1..71b8eb03b7 100644 --- a/lib/NGCP/Panel/Controller/EmergencyMapping.pm +++ b/lib/NGCP/Panel/Controller/EmergencyMapping.pm @@ -14,6 +14,8 @@ use NGCP::Panel::Form::EmergencyMapping::Container; use NGCP::Panel::Form::EmergencyMapping::ContainerAdmin; use NGCP::Panel::Form::EmergencyMapping::Mapping; use NGCP::Panel::Form::EmergencyMapping::Upload; +use NGCP::Panel::Form::EmergencyMapping::UploadAdmin; +use NGCP::Panel::Form::EmergencyMapping::Download; sub auto :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) { my ($self, $c) = @_; @@ -481,14 +483,20 @@ sub emergency_mapping_delete :Chained('emergency_mapping_base') :PathPart('delet sub emergency_mappings_upload :Chained('list') :PathPart('upload') :Args(0) { my ($self, $c) = @_; - my $form = NGCP::Panel::Form::EmergencyMapping::Upload->new(ctx => $c); + my $form; + if($c->user->roles eq "reseller") { + $form = NGCP::Panel::Form::EmergencyMapping::Upload->new(ctx => $c); + } else { + $form = NGCP::Panel::Form::EmergencyMapping::UploadAdmin->new(ctx => $c); + } my $upload = $c->req->upload('upload_mapping'); my $posted = $c->req->method eq 'POST'; - my @params = ( upload_mapping => $posted ? $upload : undef, ); + $c->request->params->{upload_mapping} = $posted ? $upload : undef; + my $params = {}; $form->process( posted => $posted, - params => { @params }, - action => $c->uri_for('/emergencymapping/upload'), + params => $c->request->params, + item => $params, ); if($form->validated) { @@ -510,13 +518,10 @@ sub emergency_mappings_upload :Chained('list') :PathPart('upload') :Args(0) { if($c->req->params->{purge_existing}) { my ($start, $end); $start = time; - NGCP::Panel::Utils::MySQL::truncate_table( - c => $c, - schema => $schema, - do_transaction => 0, - table => 'provisioning.emergency_mappings', - ); - $c->stash->{emergency_container_rs}->delete; + my $rs = $c->stash->{emergency_container_rs}->search({ + reseller_id => $form->params->{reseller}->{id} + }); + $rs->delete; $end = time; $c->log->debug("Purging emergency mappings took " . ($end - $start) . "s"); } @@ -524,6 +529,7 @@ sub emergency_mappings_upload :Chained('list') :PathPart('upload') :Args(0) { c => $c, data => \$data, schema => $schema, + reseller_id => $form->params->{reseller}->{id}, ); }); @@ -551,13 +557,44 @@ sub emergency_mappings_upload :Chained('list') :PathPart('upload') :Args(0) { sub emergency_mappings_download :Chained('list') :PathPart('download') :Args(0) { my ($self, $c) = @_; - my $schema = $c->model('DB'); - $c->response->header ('Content-Disposition' => 'attachment; filename="emergency_mapping_list.csv"'); - $c->response->content_type('text/csv'); - $c->response->status(200); - NGCP::Panel::Utils::EmergencyMapping::create_csv( - c => $c, - ); + + my $form; + my $reseller_id; + if($c->user->roles eq "reseller") { + $reseller_id = $c->user->reseller_id; + } else { + $form = NGCP::Panel::Form::EmergencyMapping::Download->new(ctx => $c); + } + my $posted = $c->req->method eq 'POST'; + my $params = {}; + + if(defined $form) { + $form->process( + posted => $posted, + params => $c->request->params, + item => $params, + ); + if($form->validated) { + $reseller_id = $form->params->{reseller}{id}; + } + } + + if(!$posted || !defined $reseller_id) { + $c->stash(emergency_container_create_flag => 1); + $c->stash(emergency_container_form => $form); + } else { + my $schema = $c->model('DB'); + $c->response->header ('Content-Disposition' => "attachment; filename=\"emergency_mapping_list_reseller_$reseller_id.csv\""); + $c->response->content_type('text/csv'); + $c->response->status(200); + NGCP::Panel::Utils::EmergencyMapping::create_csv( + c => $c, + reseller_id => $reseller_id, + ); + } + + + return; } diff --git a/lib/NGCP/Panel/Form/EmergencyMapping/Download.pm b/lib/NGCP/Panel/Form/EmergencyMapping/Download.pm new file mode 100644 index 0000000000..254074b6e6 --- /dev/null +++ b/lib/NGCP/Panel/Form/EmergencyMapping/Download.pm @@ -0,0 +1,44 @@ +package NGCP::Panel::Form::EmergencyMapping::Download; +use Sipwise::Base; +use HTML::FormHandler::Moose; +extends 'HTML::FormHandler'; + +use HTML::FormHandler::Widget::Block::Bootstrap; + +has '+widget_wrapper' => ( default => 'Bootstrap' ); +has '+enctype' => ( default => 'multipart/form-data'); +has_field 'submitid' => ( type => 'Hidden' ); +sub build_render_list {[qw/submitid fields actions/]} +sub build_form_element_class { [qw/form-horizontal/] } + +has_field 'reseller' => ( + type => '+NGCP::Panel::Field::Reseller', + validate_when_empty => 1, + element_attr => { + rel => ['tooltip'], + title => ['The reseller id for which to download the CSV.'] + }, +); + +has_field 'save' => ( + type => 'Submit', + value => 'Download', + element_class => [qw/btn btn-primary/], + do_label => 0, +); + +has_block 'fields' => ( + tag => 'div', + class => [qw/modal-body/], + render_list => [qw/reseller/], +); + +has_block 'actions' => ( + tag => 'div', + class => [qw/modal-footer/], + render_list => [qw/save/], +); + +1; + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Form/EmergencyMapping/Upload.pm b/lib/NGCP/Panel/Form/EmergencyMapping/Upload.pm index 44d2251a24..acddbd4484 100644 --- a/lib/NGCP/Panel/Form/EmergencyMapping/Upload.pm +++ b/lib/NGCP/Panel/Form/EmergencyMapping/Upload.pm @@ -4,7 +4,6 @@ use HTML::FormHandler::Moose; extends 'HTML::FormHandler'; use HTML::FormHandler::Widget::Block::Bootstrap; -use NGCP::Panel::Field::BillingZone; has '+widget_wrapper' => ( default => 'Bootstrap' ); has '+enctype' => ( default => 'multipart/form-data'); diff --git a/lib/NGCP/Panel/Form/EmergencyMapping/UploadAdmin.pm b/lib/NGCP/Panel/Form/EmergencyMapping/UploadAdmin.pm new file mode 100644 index 0000000000..40d4e16b1f --- /dev/null +++ b/lib/NGCP/Panel/Form/EmergencyMapping/UploadAdmin.pm @@ -0,0 +1,25 @@ +package NGCP::Panel::Form::EmergencyMapping::UploadAdmin; +use Sipwise::Base; +use HTML::FormHandler::Moose; +extends 'NGCP::Panel::Form::EmergencyMapping::Upload'; + +use HTML::FormHandler::Widget::Block::Bootstrap; + +has_field 'reseller' => ( + type => '+NGCP::Panel::Field::Reseller', + validate_when_empty => 1, + element_attr => { + rel => ['tooltip'], + title => ['The reseller id this CSV upload belongs to.'] + }, +); + +has_block 'fields' => ( + tag => 'div', + class => [qw/modal-body/], + render_list => [qw/upload_mapping reseller purge_existing/], +); + +1; + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Utils/CSVSeparator.pm b/lib/NGCP/Panel/Utils/CSVSeparator.pm new file mode 100644 index 0000000000..3b109d2bfd --- /dev/null +++ b/lib/NGCP/Panel/Utils/CSVSeparator.pm @@ -0,0 +1,537 @@ +package NGCP::Panel::Utils::CSVSeparator; + +use 5.008; +use strict; +use warnings; +use Carp qw(carp croak); + +our $VERSION = '0.20'; + +use Exporter; +use base 'Exporter'; +our @EXPORT_OK = qw(get_separator); + + +sub get_separator { + + my %options = @_; + + my $file_path = $options{path}; + + # check options + my $echo; + if ($options{echo}) { + $echo = 1; + print "\nDetecting field separator of $file_path\n"; + } + + my (@excluded, @included); + if (exists $options{exclude}) { + @excluded = @{$options{exclude}}; + } + + if (exists $options{include}) { + @included = @{$options{include}}; + } + + my ($lucky, $colon_timecol, $comma_decsep, $comma_groupsep); + if (exists $options{lucky} && $options{lucky} == 1) { + $lucky = 1; + print "Scalar context...\n\n" if $echo; + } else { + $colon_timecol = $comma_decsep = $comma_groupsep = 1; + print "List context...\n\n" if $echo; + } + + # options checked + + # Default set of candidates + my @candidates = (',', ';', ':', '|', "\t"); + + my %survivors; + $survivors{$_} = [] foreach (@candidates); + + if (@excluded > 0) { + foreach (@excluded) { + delete $survivors{$_}; + _message('deleted', $_) if $echo; + } + } + + if (@included > 0) { + foreach (@included) { + if (length($_) == 1) { + $survivors{$_} = []; + } + _message('added', $_) if $echo; + } + } + + if (keys %survivors == 0) { + carp "No candidates left!"; + return; + } + + my $csv; + open ($csv, "<:crlf", $file_path) || croak "Couldn't open $file_path: $!"; + + my $record_count = 0; # if $echo + while (<$csv>) { + my $record = $_; + chomp $record; + + if ($echo) { + $record_count++; + print "\nRecord #$record_count\n"; + } + + foreach my $candidate (keys %survivors) { + _message('candidate', $candidate) if $echo; + + my $rex = qr/\Q$candidate\E/; + + my $count = 0; + $count++ while ($record =~ /$rex/g); + + print "Count: $count\n" if $echo; + + if ($count > 0 && !$lucky) { + push @{$survivors{$candidate}}, $count; + } elsif ($count == 0) { + delete $survivors{$candidate}; + } + + } + + if (!$lucky) { + $colon_timecol = _regularity($record, 'timecol') if $colon_timecol; + $comma_decsep = _regularity($record, 'decsep') if $comma_decsep; + $comma_groupsep = _regularity($record, 'groupsep') if $comma_groupsep; + } + + + my @alive = keys %survivors; + my $survivors_count = @alive; + if ($survivors_count == 1) { + if ($echo) { + _message('detected', $alive[0]); + print "Returning control to caller...\n\n"; + } + close $csv; + if (!$lucky) { + return @alive; + } else { + return $alive[0]; + } + } elsif ($survivors_count == 0) { + carp "\nNo candidates left!\n"; + return; + } + } + + # More than 1 survivor. 2nd pass to determine count variability + if ($lucky) { + print "\nSeveral candidates left\n" if $echo; + carp "\nBad luck. Couldn't determine the separator of $file_path.\n"; + return; + } else { + print "\nVariability:\n\n" if $echo; + my %std_dev; + foreach my $candidate (keys %survivors) { + my $mean = _mean(@{$survivors{$candidate}}); + $std_dev{$candidate} = _std_dev($mean, @{$survivors{$candidate}}); + if ($echo) { + _message('candidate', $candidate); + print "Mean: $mean\tStd Dev: $std_dev{$candidate}\n\n"; + } + } + + print "Couldn't determine the separator\n" if $echo; + + close $csv; + + my @penalized; + if ($colon_timecol) { + print "Detected time column\n" if $echo; + delete $survivors{':'}; + push @penalized, ':'; + } + + if ($comma_decsep || $comma_groupsep) { + delete $survivors{','}; + push @penalized, ','; + if ($echo && $comma_decsep) { + print "\nDetected comma-separated decimal numbers column\n"; + } + if ($echo && $comma_groupsep) { + print "\nDetected comma-grouped numbers column\n"; + } + } + + my @alive = sort {$std_dev{$a} <=> $std_dev{$b}} keys %survivors; + push @alive, sort {$std_dev{$a} <=> $std_dev{$b}} @penalized; + if ($echo) { + print "Remaining candidates: "; + foreach my $left (@alive) { + _message('left', $left); + } + print "\n\nReturning control to caller...\n\n"; + } + return @alive; + } +} + +sub _mean { + my @array = @_; + + my $sum = 0; + $sum += $_ foreach (@array); + + my $mean = $sum / scalar(@array); + + return $mean; +} + +sub _std_dev { + my ($mean, @array) = @_; + + my $sum = 0; + $sum += ($_ - $mean)**2 foreach (@array); + + my $std_dev = sqrt( $sum / scalar(@array) ); + + return $std_dev; +} + +sub _regularity { + my ($string, $kind) = @_; + + my $time_rx = qr/ + (?:^|(?<=\s|[T,;|\t])) + (?:[01]?[0-9]|2[0-3]) # hours + : + (?:[0-5][0-9]) # minutes + (?::[0-5][0-9])? # seconds + (?: + Z + | + \.\d+ + | + (?:\+|-) + (?:[01]?[0-9]|2[0-3]) + : + (?:[0-5][0-9]) + )? + (?=$|\s|[,;|\t]) + /x; + + my $commadecsep_rx = qr/ + (?:^|(?<=[^\d,.])) + (?: + [-+]? + (?: + \d{0,3}?(?:\.\d{3})* + | + \d+ + ) + ,\d+ + ) + (?=$|[^\d,.]) + /x; + + my $commagroupsep_rx = qr/ + (?:^|(?<=[^\d,.])) + (?: + [-+]?\d{0,3}? + (?:,\d{3})+ + (?:\.\d+)? + ) + (?=$|[^\d,.]) + /x; + + + return 0 if ($kind eq 'timecol' && $string !~ /$time_rx/); + return 0 if ($kind eq 'decsep' && $string !~ /$commadecsep_rx/); + return 0 if ($kind eq 'groupsep' && $string !~ /$commagroupsep_rx/); + + return 1; +} + +sub _message { + my ($type, $candidate) = @_; + + my $char; + if (ord $candidate == 9) { # tab character + $char = "\\t"; + } else { + $char = $candidate; + } + + my %message = ( + deleted => "Deleted $char from candidates list\n", + added => "Added $char to candidates list\n", + candidate => "Candidate: $char\t", + detected => "\nSeparator detected: $char\n", + left => " $char ", + ); + + print $message{$type}; +} + +1; + +__END__ + + +=head1 NAME + +Text::CSV::Separator - Determine the field separator of a CSV file + +=head1 VERSION + +Version 0.20 - November 2, 2008 + +=head1 SYNOPSIS + + use Text::CSV::Separator qw(get_separator); + + my @char_list = get_separator( + path => $csv_path, + exclude => $array1_ref, # optional + include => $array2_ref, # optional + echo => 1, # optional + ); + + my $separator; + if (@char_list) { + if (@char_list == 1) { # successful detection + $separator = $char_list[0]; + } else { # several candidates passed the tests + # Some code here + } else { # no candidate passed the tests + # Some code here + } + + + # "I'm Feeling Lucky" alternative interface + # Don't forget to include the 'lucky' parameter + + my $separator = get_separator( + path => $csv_path, + lucky => 1, + exclude => $array1_ref, # optional + include => $array2_ref, # optional + echo => 1, # optional + ); + + + +=head1 DESCRIPTION + +This module provides a fast detection of the field separator character (also +called field delimiter) of a CSV file, or more generally, of a character +separated text file (also called delimited text file), and returns it ready +to use in a CSV parser (e.g., Text::CSV_XS, Tie::CSV_File, or +Text::CSV::Simple). +This may be useful to the vulnerable -and often ignored- population of +programmers who need to process automatically CSV files from different sources. + +The default set of candidates contains the following characters: +',' ';' ':' '|' '\t' + +The only required parameter is the CSV file path. Optionally, the user can +specify characters to be excluded or included in the list of candidates. + +The routine returns an array containing the list of candidates that passed +the tests. If it succeeds, this array will contain only one value: the field +separator we are looking for. On the other hand, if no candidate survives +the tests, it will return an empty list. + +The technique used is based on the following principle: + +=over 8 + +=item * + +For every line in the file, the number of instances of the separator +character acting as separators must be an integer constant > 0 , although +a line may also contain some instances of that character as +literal characters. + +=item * + +Most of the other candidates won't appear in a typical CSV line. + +=back + +As soon as a candidate misses a line, it will be removed from the candidates +list. + +This is the first test done to the CSV file. In most cases, it will detect the +separator after processing the first few lines. In particular, if the file +contains a header line, one line will probably be enough to get the job done. +Processing will stop and return control to the caller as soon as the program +reaches a status of 1 single candidate (or 0 candidates left). + +If the routine cannot determine the separator in the first pass, it will do +a second pass based on several heuristic techniques. It checks whether the +file has columns consisting of time values, comma-separated decimal numbers, +or numbers containing a comma as the group separator, which can lead to false +positives in files that don't have a header row. It also measures the +variability of the remaining candidates. +Of course, you can always create a CSV file capable of resisting the siege, +but this approach will work correctly in many cases. The possibility of +excluding some of the default candidates may help to resolve cases with +several possible winners. +The resulting array contains the list of possible separators sorted by their +likelihood, being the first array item the most probable separator. + +The module also provides an alternative interface with a simpler syntax, +which can be handy if you think that the files your program will have +to deal with aren't too exotic. To use it you only have to add the +B 1> key-value pair to the parameters hash and the routine +will return a single value, so you can assign it directly to a scalar variable. +If no candidate survives the first pass, it will return C. +The code skips the 2nd pass, which is usually unnecessary, so the program +won't store counts and won't check any existing regularities. Hence, it will +run faster and will require less memory. This approach should be enough in +most cases. + +=head1 FUNCTIONS + +=over 4 + +=item C + +Returns an array containing the field separator character (or characters, if +more than one candidate passed the tests) of a CSV file. In case no candidate +passes the tests, it returns an empty list. + +The available parameters are: + +=over 8 + +=item * C + +Required. The path to the CSV file. + +=item * C + +Optional. Array containing characters to be excluded from the candidates list. + +=item * C + +Optional. Array containing characters to be included in the candidates list. + +=item * C + +Optional. If selected, get_separator will return one single character, +or C in case no separator is detected. Off by default. + +=item * C + +Optional. Writes to the standard output messages describing the actions +performed. Off by default. +This is useful to keep track of what's going on, especially for debugging +purposes. + +=back + +=back + +=head1 EXPORT + +None by default. + +=head1 EXAMPLE + +Consider the following scenario: Your program must process a batch of csv files, +and you know that the separator could be a comma, a semicolon or a tab. +You also know that one of the fields contains time values. This field will +provide a fixed number of colons that could mislead the detection code. +In this case, you should exclude the colon (and you can also exclude the other +default candidate not considered, the pipe character): + + my @char_list = get_separator( + path => $csv_path, + exclude => [':', '|'], + ); + + if (@char_list) { + my $separator; + if (@char_list == 1) { + $separator = $char_list[0]; + } else { + # Some code here + } + } + + + # Using the "I'm Feeling Lucky" interface: + + my $separator = get_separator( + path => $csv_path, + lucky => 1, + exclude => [':', '|'], + ); + + +=head1 MOTIVATION + +Despite the popularity of XML, the CSV file format is still widely used +for data exchange between applications, because of its much lower overhead: +It requires much less bandwidth and storage space than XML, and it also has +a better performance under compression (see the References below). + +Unfortunately, there is no formal specification of the CSV format. +The Microsoft Excel implementation is the most widely used and it has become +a I standard, but the variations are almost endless. + +One of the biggest annoyances of this format is that in most cases you don't +know a priori what is the field separator character used in a file. +CSV stands for "comma-separated values", but most of the spreadsheet +applications let the user select the field delimiter from a list of several +different characters when saving or exporting data to a CSV file. +Furthermore, in a Windows system, when you save a spreadsheet in Excel as a +CSV file, Excel will use as the field delimiter the default list separator of +your system's locale, which happens to be a semicolon for several European +languages. You can even customize this setting and use the list separator you +like. For these and other reasons, automating the processing of CSV files is a +risky task. + +This module can be used to determine the separator character of a delimited +text file of any kind, but since the aforementioned ambiguity problems occur +mainly in CSV files, I decided to use the Text::CSV:: namespace. + +=head1 REFERENCES + +L + +L + +=head1 SEE ALSO + +There's another module in CPAN for this task, Text::CSV::DetectSeparator, +which follows a different approach. + +=head1 ACKNOWLEDGEMENTS + +Many thanks to Xavier Noria for wise suggestions. +The author is also grateful to Thomas Zahreddin, Benjamin Erhart, Ferdinand Gassauer, +and Mario Krauss for valuable comments and bug reports. + +=head1 AUTHOR + +Enrique Nell, Eperl_nell@telefonica.netE + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2006 by Enrique Nell. + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + + +=cut + + diff --git a/lib/NGCP/Panel/Utils/EmergencyMapping.pm b/lib/NGCP/Panel/Utils/EmergencyMapping.pm index 5d2d17fe88..5ff228dbcc 100644 --- a/lib/NGCP/Panel/Utils/EmergencyMapping.pm +++ b/lib/NGCP/Panel/Utils/EmergencyMapping.pm @@ -4,6 +4,7 @@ use warnings; use Text::CSV_XS; use NGCP::Panel::Utils::MySQL; +use NGCP::Panel::Utils::CSVSeparator; sub _insert_batch { my ($c, $schema, $mappings, $chunk_size) = @_; @@ -19,13 +20,28 @@ sub _insert_batch { sub upload_csv { my(%params) = @_; - my ($c,$data,$schema) = @params{qw/c data schema/}; + my ($c,$data,$schema, $reseller_id) = @params{qw/c data schema reseller_id/}; my ($start, $end); + my $separator = NGCP::Panel::Utils::CSVSeparator::get_separator( + path => $data, + lucky => 1, + ); + unless(defined $separator) { + my $text = $c->loc("Failed to detect CSV separator"); + return ([], [], \$text ); + } + # csv bulk upload - my $csv = Text::CSV_XS->new({ allow_whitespace => 1, binary => 1, keep_meta_info => 1 }); + my $csv = Text::CSV_XS->new({ + allow_whitespace => 1, + binary => 1, + keep_meta_info => 1, + sep_char => $separator, + }); + #my @cols = @{ $c->config->{lnp_csv}->{element_order} }; - my @cols = qw/name reseller_id code prefix/; + my @cols = qw/name code prefix/; my @fields ; my @fails = (); @@ -44,14 +60,16 @@ sub upload_csv { my $row = {}; @{$row}{@cols} = @{ $line }; my $k = $row->{name}; - my $r = $row->{reseller_id}; unless(exists $containers{$k}) { my $container = $schema->resultset('emergency_containers')->find_or_create({ name => $k, - reseller_id => $r, + reseller_id => $reseller_id, }); $containers{$k} = $container->id; } + unless(length $row->{prefix}) { + $row->{prefix} = undef; + } push @mappings, [$containers{$k}, $row->{code}, $row->{prefix}]; if($linenum % $chunk_size == 0) { @@ -76,16 +94,17 @@ sub upload_csv { sub create_csv { my(%params) = @_; - my($c) = @params{qw/c/}; + my($c, $reseller_id) = @params{qw/c reseller_id/}; #my @cols = @{ $c->config->{emergency_mapping_csv}->{element_order} }; - my @cols = qw/name reseller_id code prefix/; + my @cols = qw/name code prefix/; - my $mapping_rs = $c->stash->{emergency_mapping_rs}->search_rs( - undef, + my $mapping_rs = $c->stash->{emergency_mapping_rs}->search_rs({ + 'emergency_container.reseller_id' => $reseller_id, + }, { - '+select' => ['emergency_container.name', 'emergency_container.reseller_id'], - '+as' => ['name', 'reseller_id'], + '+select' => ['emergency_container.name'], + '+as' => ['name'], 'join' => 'emergency_container', } );