TT#43654 datatable rowcount clipping

to prevent long-running count query in case of mio.
of items, it is done on a limited set, ie.

select count(1) from (select id from cdr where .. limit 1000)

the method determines if there are more than eg. 1000
rows, in which case the UI datatable will simply render a "+"

"Showing 1 to 5 of 1000+ records"

it is only enabled for the topical call history datatable
for now.

Change-Id: I1ca6d22c69784f20ec39c74e3db989c43f1a6918
changes/60/23160/7
Rene Krenn 8 years ago
parent d02a4416b0
commit 29a8ca6918

@ -91,7 +91,7 @@ __PACKAGE__->config(
'View::JSON' => {
#Set the stash keys to be exposed to a JSON response
#(sEcho iTotalRecords iTotalDisplayRecords aaData) for datatables
expose_stash => [ qw(sEcho iTotalRecords iTotalDisplayRecords aaData dt_custom_footer widget_data timeline_data) ],
expose_stash => [ qw(sEcho iTotalRecords iTotalDisplayRecords iTotalRecordCountClipped iTotalDisplayRecordCountClipped aaData dt_custom_footer widget_data timeline_data) ],
},
'View::TT' => {
INCLUDE_PATH => [
@ -266,13 +266,13 @@ __PACKAGE__->log(Log::Log4perl::Catalyst->new($logger_config));
my ($self, $root, $property_type, $relation, $property) = @_;
my $embedded = $root->embedded ? $root->embedded->[0] : undef;
if ($embedded
&& ( ( $property_type eq 'links' && $panel_config->{appearance}{api_links_forcearray} )
&& ( ( $property_type eq 'links' && $panel_config->{appearance}{api_links_forcearray} )
|| ( $property_type eq 'embedded' && $panel_config->{appearance}{api_embedded_forcearray}) )
&& $relation =~/^ngcp:[a-z0-9]+$/
) {
return 1;
}
if (!$embedded
if (!$embedded
&& ( ( $property_type eq 'links' && $panel_config->{appearance}{api_links_forcearray} ) )
&& $relation =~/^ngcp:[a-z0-9]+$/
) {

@ -372,6 +372,8 @@ sub countries_ajax :Chained('/') :PathPart('contact/country/ajax') :Args(0) :Doe
$c->stash(aaData => \@aaData,
iTotalRecords => $count,
iTotalDisplayRecords => $count,
iTotalRecordCountClipped => \0,
iTotalDisplayRecordCountClipped => \0,
sEcho => int($c->request->params->{sEcho} // 1),
);

@ -46,14 +46,14 @@ sub base :Chained('/') :PathPart('device') :CaptureArgs(0) {
}
],
});
$reseller_id and $devmod_rs = $devmod_rs->search({ reseller_id => $reseller_id });
$c->stash->{devmod_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, [
{ name => 'id', search => 1, title => $c->loc('#') },
{ name => 'type', search => 1, title => $c->loc('Type') },
{ name => 'reseller.name', search => 1, title => $c->loc('Reseller') },
{ name => 'vendor', search => 1, title => $c->loc('Vendor') },
{ name => 'model', search => 1, title => $c->loc('Model') },
{ name => 'model', search => 1, title => $c->loc('Model') },
]);
my $devfw_rs = $c->model('DB')->resultset('autoprov_firmwares')->search_rs(undef,{'columns' => [qw/id device_id version filename tag/],
@ -154,7 +154,7 @@ sub devmod_ajax :Chained('base') :PathPart('model/ajax') :Args(0) :Does(ACL) :AC
NGCP::Panel::Utils::Datatables::process($c, $resultset, $c->stash->{devmod_dt_columns},
sub {
my ($result) = @_;
my %data = (
my %data = (
mac_image_exists => $result->get_column('mac_image_exists'),
front_image_exists => $result->get_column('front_image_exists'),
);
@ -173,7 +173,7 @@ sub extensionmodel_ajax :Chained('base') :PathPart('extensionmodel/ajax') :Args(
NGCP::Panel::Utils::Datatables::process($c, $resultset, $c->stash->{devmod_dt_columns},
sub {
my ($result) = @_;
my %data = (
my %data = (
mac_image_exists => $result->get_column('mac_image_exists'),
front_image_exists => $result->get_column('front_image_exists'),
);
@ -663,7 +663,7 @@ sub devfw_download :Chained('devfw_base') :PathPart('download') :Args(0) {
$c->response->content_type('application/octet-stream');
$c->response->body(
NGCP::Panel::Utils::DeviceFirmware::get_firmware_data(
c => $c,
c => $c,
fw_id => $fw->id
));
}
@ -952,6 +952,8 @@ sub devprof_extensions :Chained('devprof_base') :PathPart('extensions') :Args(0)
aaData => $data,
iTotalRecords => 1,
iTotalDisplayRecords => 1,
iTotalRecordCountClipped => \0,
iTotalDisplayRecordCountClipped => \0,
sEcho => int($c->request->params->{sEcho} // 1),
);
@ -1020,6 +1022,8 @@ sub get_annotated_info :Private {
aaData => $data,
iTotalRecords => 1,
iTotalDisplayRecords => 1,
iTotalRecordCountClipped => \0,
iTotalDisplayRecordCountClipped => \0,
sEcho => int($c->request->params->{sEcho} // 1),
);
@ -1194,7 +1198,7 @@ sub dev_field_config :Chained('/') :PathPart('device/autoprov/config') :Args() {
$c->response->status(403);
return;
}
}
}
my $dev = $c->model('DB')->resultset('autoprov_field_devices')->find({
identifier => $id
@ -1364,16 +1368,16 @@ sub dev_field_config :Chained('/') :PathPart('device/autoprov/config') :Args() {
'+select' => [\'replace(attribute.attribute,"__","")' ],
'+as' => ['attribute_normalized'],
});
$preferences_device_dynamic->{$type} = get_inflated_columns_all($pref_rs_dynamic,
'hash' => 'attribute_normalized',
'column' => 'value'
$preferences_device_dynamic->{$type} = get_inflated_columns_all($pref_rs_dynamic,
'hash' => 'attribute_normalized',
'column' => 'value'
);
my $pref_rs_static = $pref_rs->search_rs({
'attribute.dynamic' => 0,
});
$preferences_device->{$type} = get_inflated_columns_all($pref_rs_static,
'hash' => 'attribute',
'column' => 'value'
$preferences_device->{$type} = get_inflated_columns_all($pref_rs_static,
'hash' => 'attribute',
'column' => 'value'
);
}
my %preferences_device = (
@ -1888,7 +1892,7 @@ sub dev_field_firmware_download :Chained('dev_field_firmware_base') :PathPart('v
$c->response->content_type('application/octet-stream');
$c->response->body(
NGCP::Panel::Utils::DeviceFirmware::get_firmware_data(
c => $c,
c => $c,
fw_id => $fw->id
));
}
@ -1939,7 +1943,7 @@ sub dev_field_firmware_next :Chained('dev_field_firmware_version_base') :PathPar
$c->response->content_type('application/octet-stream');
$c->response->body(
NGCP::Panel::Utils::DeviceFirmware::get_firmware_data(
c => $c,
c => $c,
fw_id => $fw->id
));
}
@ -1976,7 +1980,7 @@ sub dev_field_firmware_latest :Chained('dev_field_firmware_version_base') :PathP
$c->response->content_type('application/octet-stream');
$c->response->body(
NGCP::Panel::Utils::DeviceFirmware::get_firmware_data(
c => $c,
c => $c,
fw_id => $fw->id
));
}
@ -1995,7 +1999,7 @@ sub devices_preferences_list :Chained('devmod_base') :PathPart('preferences') :C
NGCP::Panel::Utils::Preferences::load_preference_list(
c => $c,
pref_values => $pref_values,
#we don't need fielddev_pref flag, because it always will be just more narrow than dev_pref.
#we don't need fielddev_pref flag, because it always will be just more narrow than dev_pref.
dev_pref => 1,
search_conditions => [{
'attribute' =>
@ -2003,7 +2007,7 @@ sub devices_preferences_list :Chained('devmod_base') :PathPart('preferences') :C
{ 'like' => 'vnd_'.lc($c->stash->{devmod}->vendor).'%' },
{'-not_like' => 'vnd_%' },
],
#relation type is defined by preference flag dev_pref,
#relation type is defined by preference flag dev_pref,
#so here we select only linked to the current model, or not linked to any model at all
'-or' => [
'voip_preference_relations.autoprov_device_id' => $c->stash->{devmod}->id,
@ -2069,7 +2073,7 @@ sub devices_preferences_create :Chained('devices_preferences_list') :PathPart('c
$resource->{autoprov_device_id} = $c->stash->{devmod}->id;
my $preference = NGCP::Panel::Utils::Preferences::create_dynamic_preference(
$c, $resource,
$c, $resource,
group_name => 'CPBX Device Administration',
);

@ -386,6 +386,8 @@ sub emptyajax :Chained('/') :PathPart('emptyajax') :Args(0) {
aaData => [],
iTotalDisplayRecords => 0,
iTotalRecords => 0,
iTotalRecordCountClipped => \0,
iTotalDisplayRecordCountClipped => \0,
sEcho => $c->request->params->{sEcho} // 1,
);
$c->detach( $c->view("JSON") );

@ -3863,6 +3863,7 @@ sub _process_calls_rows {
$data{total_customer_cost} = (defined $result->{total_customer_cost} ? sprintf("%.2f", $result->{total_customer_cost} / 100.0) : "");
return %data;
},
'count_limit' => 1000,
}
);
}

@ -17,6 +17,8 @@ sub process {
my $aaData = [];
my $totalRecords = 0;
my $displayRecords = 0;
my $totalRecordCountClipped = 0;
my $displayRecordCountClipped = 0;
my $aggregate_cols = [];
my $aggregations = {};
@ -56,16 +58,16 @@ sub process {
#all joins already implemented, and filters aren't applied. But count we will take only if there are search and no other aggregations
my $totalRecords_rs = $rs;
#= $use_rs_cb ? 0 : $rs->count;
### Search processing section
# generic searching
my @searchColumns = ();
my %conjunctSearchColumns = ();
#processing single search input - group1 from groups to be joined by 'AND'
my $searchString = $c->request->params->{sSearch} // "";
if($searchString && ! $use_rs_cb) {
#for search string from one search input we need to check all columns which contain the 'search' spec (now: qw/search search_lower_column search_upper_column/). so, for example user entered into search input ip address - we don't know that it is ip address, so we check that name like search OR id like search OR search is between network_lower_value and network upper value
#for search string from one search input we need to check all columns which contain the 'search' spec (now: qw/search search_lower_column search_upper_column/). so, for example user entered into search input ip address - we don't know that it is ip address, so we check that name like search OR id like search OR search is between network_lower_value and network upper value
foreach my $col(@{ $cols }) {
my ($name,$search_value,$op,$convert);
# avoid amigious column names if we have the same column in different joined tables
@ -171,21 +173,23 @@ sub process {
$aggregations = {%{$aggregations}, $total_row_func->($aggregations) };
}
if(!$use_rs_cb){
if(@searchColumns){
$totalRecords = $totalRecords_rs->count;
if(!@$aggregate_cols){
$displayRecords = $rs->count;
if (!$use_rs_cb) {
if (@searchColumns) {
($totalRecords, $totalRecordCountClipped) = get_count_safe($c,$totalRecords_rs,$params);
if (!@$aggregate_cols) {
($displayRecords, $displayRecordCountClipped) = get_count_safe($c,$rs,$params);
}
}else{
if(@$aggregate_cols){
} else {
if (@$aggregate_cols) {
$totalRecords = $displayRecords;
}elsif(!@$aggregate_cols){
$totalRecords = $displayRecords = $totalRecords_rs->count;
} elsif (!@$aggregate_cols) {
($totalRecords, $totalRecordCountClipped) = get_count_safe($c,$totalRecords_rs,$params);
$displayRecords = $totalRecords;
$displayRecordCountClipped = $totalRecordCountClipped;
}
}
}
# show specific row on top (e.g. if we come back from a newly created entry)
my $topId = $c->request->params->{iIdOnTop};
if(defined $topId) {
@ -257,10 +261,37 @@ sub process {
add_arbitrary_data($c, $aaData, $params->{topData}, $cols, $row_func, $params);
expose_data($c, $aaData, $totalRecords, $displayRecords);
expose_data($c, $aaData, $totalRecords, $totalRecordCountClipped, $displayRecords, $displayRecordCountClipped);
}
sub get_count_safe {
my ($c,$rs,$params) = @_;
my $count_limit = $params->{count_limit};
#$count_limit = 12;
if ($c and defined $count_limit and $count_limit > 0) {
my ($count_clipped) = $c->model('DB')->storage->dbh_do(sub {
my ($storage, $dbh, $stmt, @bind_vals) = @_;
@bind_vals = map { $_->[1]; } @bind_vals;
$c->log->debug("bind: " . join(",",@bind_vals));
return $dbh->selectrow_array("select count(1) from ($stmt) as query_clipped",undef,@bind_vals);
},@{${$rs->search_rs(undef,{
page => 1,
rows => $count_limit + 1,
#below is required if fields with identical name are selected by $rs:
'select' => (defined $params->{count_projection_column} ? $params->{count_projection_column} : "id"),
})->as_query}});
if ($count_clipped > $count_limit) {
$c->log->debug("result count clipped");
return ($count_limit,1);
} else {
return ($count_clipped,0);
}
} else {
return ($rs->count,0);
}
}
sub add_arbitrary_data {
my ($c, $aaData, $topData, $cols, $row_func, $params) = @_;
# show any arbitrary data rows on top, just like a union would do
@ -283,11 +314,13 @@ sub add_arbitrary_data {
}
sub expose_data {
my($c, $aaData, $totalRecords, $displayRecords) = @_;
my($c, $aaData, $totalRecords, $totalRecordCountClipped, $displayRecords, $displayRecordCountClipped) = @_;
$c->stash(
aaData => $aaData,
iTotalRecords => $totalRecords,
iTotalDisplayRecords => $displayRecords,
iTotalRecordCountClipped => ($totalRecordCountClipped ? \1 : \0),
iTotalDisplayRecordCountClipped => ($displayRecordCountClipped ? \1 : \0),
sEcho => int($c->request->params->{sEcho} // 1),
);
}
@ -304,7 +337,7 @@ sub process_static_data {
add_arbitrary_data($c, $aaData, $data, $cols, $row_func, $params);
my $totalRecords = scalar @$aaData;
my $displayRecords = $totalRecords;
expose_data($c, $aaData, $totalRecords, $displayRecords);
expose_data($c, $aaData, $totalRecords, 0, $displayRecords, 0);
}
sub set_columns {

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save