diff --git a/webtools/testopia/Bugzilla/Testopia/Report.pm b/webtools/testopia/Bugzilla/Testopia/Report.pm new file mode 100644 index 000000000000..9959b55adfef --- /dev/null +++ b/webtools/testopia/Bugzilla/Testopia/Report.pm @@ -0,0 +1,351 @@ +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Bugzilla Testopia System. +# +# The Initial Developer of the Original Code is Greg Hendricks. +# Portions created by Greg Hendricks are Copyright (C) 2006 +# Greg Hendricks. All Rights Reserved. +# +# Large portions lifted from bugzilla's report.cgi written by +# Gervase Markham +# +# Contributor(s): Greg Hendricks + +=head1 NAME + +Bugzilla::Testopia::Report - Generates report data. + +=head1 DESCRIPTION + +Reports + +=over + +=back + +=head1 SYNOPSIS + + +=cut + +package Bugzilla::Testopia::Report; + +use strict; + +use Bugzilla; +use Bugzilla::Util; +use Bugzilla::Error; +use Bugzilla::Testopia::Util; +use Bugzilla::Testopia::Search; + + +############################### +#### Initialization #### +############################### + +=head1 METHODS + +=head2 new + +Instantiates a new report object + +=cut + +sub new { + my $invocant = shift; + my $class = ref($invocant) || $invocant; + + my $self = {}; + bless($self, $class); + + $self->init(@_); + + return $self; +} + +=head2 init + +Private constructor for this class + +=cut + +sub init { + my $self = shift; + my ($type, $url, $cgi) = @_; + $self->{'type'} = $type || ThrowCodeError('bad_arg', + {argument => 'type', + function => 'Testopia::Table::_init'}); + $self->{'url_loc'} = $url; + $self->{'cgi'} = $cgi; + my $debug = $cgi->param('debug') if $cgi; + + my $col_field = $cgi->param('x_axis_field') || ''; + my $row_field = $cgi->param('y_axis_field') || ''; + my $tbl_field = $cgi->param('z_axis_field') || ''; + + if (!($col_field || $row_field || $tbl_field)) { + ThrowUserError("no_axes_defined"); + } + + my $width = $cgi->param('width'); + my $height = $cgi->param('height'); + + if (defined($width)) { + (detaint_natural($width) && $width > 0) + || ThrowCodeError("invalid_dimensions"); + $width <= 2000 || ThrowUserError("chart_too_large"); + } + + if (defined($height)) { + (detaint_natural($height) && $height > 0) + || ThrowCodeError("invalid_dimensions"); + $height <= 2000 || ThrowUserError("chart_too_large"); + } + + # These shenanigans are necessary to make sure that both vertical and + # horizontal 1D tables convert to the correct dimension when you ask to + # display them as some sort of chart. + if (defined $cgi->param('format') && $cgi->param('format') eq "table") { + if ($col_field && !$row_field) { + # 1D *tables* should be displayed vertically (with a row_field only) + $row_field = $col_field; + $col_field = ''; + } + } + else { + if ($row_field && !$col_field) { + # 1D *charts* should be displayed horizontally (with an col_field only) + $col_field = $row_field; + $row_field = ''; + } + } + + my %columns; + if ($type eq 'case'){ + $columns{'case_status'} = "map_case_status.name"; + $columns{'priority'} = "map_priority.value"; + $columns{'product'} = "map_case_product.name"; + $columns{'component'} = "map_case_components.name"; + $columns{'category'} = "map_categories.name"; + $columns{'isautomated'} = "test_cases.isautomated"; + $columns{'tags'} = "map_case_tags.name"; + $columns{'requirement'} = "test_cases.requirement"; + $columns{'author'} = "map_case_author.login_name"; + $columns{'default_tester'} = "map_default_tester.login_name"; + } + elsif ($type eq 'run'){ + $columns{'run_status'} = "test_runs.close_date"; + $columns{'product'} = "map_run_product.name"; + $columns{'build'} = "map_run_build.name"; + $columns{'milestone'} = "map_run_milestone.value"; + $columns{'environment'} = "map_run_environment.name"; + $columns{'tags'} = "map_run_tags.name"; + $columns{'manager'} = "map_run_manager.login_name"; + $columns{'default_product_version'} = "test_runs.product_version"; + } + elsif ($type eq 'plan'){ + $columns{'plan_type'} = "map_plan_type.name"; + $columns{'product'} = "map_plan_product.name"; + $columns{'archived'} = "test_plans.isactive"; + $columns{'tags'} = "map_plan_tags.name"; + $columns{'author'} = "map_plan_author.login_name"; + $columns{'default_product_version'} = "test_plans.default_product_version"; + } + # One which means "nothing". Any number would do, really. It just gets SELECTed + # so that we always select 3 items in the query. + $columns{''} = "42217354"; + + # Validate the values in the axis fields or throw an error. + !$row_field + || ($columns{$row_field} && trick_taint($row_field)) + || ThrowCodeError("report_axis_invalid", {fld => "x", val => $row_field}); + !$col_field + || ($columns{$col_field} && trick_taint($col_field)) + || ThrowCodeError("report_axis_invalid", {fld => "y", val => $col_field}); + !$tbl_field + || ($columns{$tbl_field} && trick_taint($tbl_field)) + || ThrowCodeError("report_axis_invalid", {fld => "z", val => $tbl_field}); + + my @axis_fields = ($row_field, $col_field, $tbl_field); + my @selectnames = map($columns{$_}, @axis_fields); + $self->{'axis_fields'} = \@axis_fields; + $self->{'selectnames'} = \@selectnames; + $cgi->param('viewall', 1); + + my $dbh = Bugzilla->switch_to_shadow_db; + my $search = Bugzilla::Testopia::Search->new($cgi, \@selectnames); + my $results = $dbh->selectall_arrayref($search->query); + Bugzilla->switch_to_main_db; + + # We have a hash of hashes for the data itself, and a hash to hold the + # row/col/table names. + my %data; + my %names; + + # Read the bug data and count the bugs for each possible value of row, column + # and table. + # + # We detect a numerical field, and sort appropriately, if all the values are + # numeric. + my $col_isnumeric = 1; + my $row_isnumeric = 1; + my $tbl_isnumeric = 1; + + foreach my $result (@$results) { + my ($row, $col, $tbl) = @$result; + + # handle empty dimension member names + $row = ' ' if ($row eq ''); + $col = ' ' if ($col eq ''); + $tbl = ' ' if ($tbl eq ''); + + $row = "" if ($row eq $columns{''}); + $col = "" if ($col eq $columns{''}); + $tbl = "" if ($tbl eq $columns{''}); + + # account for the fact that names may start with '_' or '.'. Change this + # so the template doesn't hide hash elements with those keys + $row =~ s/^([._])/ $1/; + $col =~ s/^([._])/ $1/; + $tbl =~ s/^([._])/ $1/; + + $data{$tbl}{$col}{$row}++; + $names{"col"}{$col}++; + $names{"row"}{$row}++; + $names{"tbl"}{$tbl}++; + + $col_isnumeric &&= ($col =~ /^-?\d+(\.\d+)?$/o); + $row_isnumeric &&= ($row =~ /^-?\d+(\.\d+)?$/o); + $tbl_isnumeric &&= ($tbl =~ /^-?\d+(\.\d+)?$/o); + } + + my @col_names = @{get_names($names{"col"}, $col_isnumeric, $col_field)}; + my @row_names = @{get_names($names{"row"}, $row_isnumeric, $row_field)}; + my @tbl_names = @{get_names($names{"tbl"}, $tbl_isnumeric, $tbl_field)}; + + # The GD::Graph package requires a particular format of data, so once we've + # gathered everything into the hashes and made sure we know the size of the + # data, we reformat it into an array of arrays of arrays of data. + push(@tbl_names, "-total-") if (scalar(@tbl_names) > 1); + + my @image_data; + foreach my $tbl (@tbl_names) { + my @tbl_data; + push(@tbl_data, \@col_names); + foreach my $row (@row_names) { + my @col_data; + foreach my $col (@col_names) { + $data{$tbl}{$col}{$row} = $data{$tbl}{$col}{$row} || 0; + push(@col_data, $data{$tbl}{$col}{$row}); + if ($tbl ne "-total-") { + # This is a bit sneaky. We spend every loop except the last + # building up the -total- data, and then last time round, + # we process it as another tbl, and push() the total values + # into the image_data array. + $data{"-total-"}{$col}{$row} += $data{$tbl}{$col}{$row}; + } + } + + push(@tbl_data, \@col_data); + } + + unshift(@image_data, \@tbl_data); + } + $self->{'col_field'} = $col_field; + $self->{'row_field'} = $row_field; + $self->{'tbl_field'} = $tbl_field; + + my @time = localtime(time()); + my $date = sprintf "%04d-%02d-%02d", 1900+$time[5],$time[4]+1,$time[3]; + $self->{'date'} = $date; + $self->{'format'} = $cgi->param('format'); + + $self->{'col_names'} = \@col_names; + $self->{'row_names'} = \@row_names; + $self->{'tbl_names'} = \@tbl_names; + + # Below a certain width, we don't see any bars, so there needs to be a minimum. + if ($width && $cgi->param('format') eq "bar") { + my $min_width = (scalar(@col_names) || 1) * 20; + + if (!$cgi->param('cumulate')) { + $min_width *= (scalar(@row_names) || 1); + } + + $self->{'min_width'} = $min_width; + } + + $self->{'width'} = $width if $width; + $self->{'height'} = $height if $height; + + $self->{'query'} = $search->query; + $self->{'debug'} = $cgi->param('debug'); + + $self->{'data'} = \%data; + $self->{'image_data'} = \@image_data; + $self->{'report_loc'} = "tr_" . $type . "_reports.cgi"; + if ($cgi->param('debug')) { + print $cgi->header; + require Data::Dumper; + print "
data hash:\n";
+        print Data::Dumper::Dumper(%data) . "\n\n";
+        print "data array:\n";
+        print Data::Dumper::Dumper(@image_data) . "\n\n
"; + } + + return $self; +} + +sub get_names { + my ($names, $isnumeric, $field) = @_; + + my @sorted; + + if ($isnumeric) { + # It's not a field we are preserving the order of, so sort it + # numerically... + sub numerically { $a <=> $b } + @sorted = sort numerically keys(%{$names}); + } else { + # ...or alphabetically, as appropriate. + @sorted = sort(keys(%{$names})); + } + + return \@sorted; +} +sub listbase{ + my $self = shift; + my $cgi = $self->{'cgi'}; + $self->{'listbase'} = $cgi->canonicalise_query( + "x_axis_field", "y_axis_field", "z_axis_field", + "ctype", "format", "query_format", "report_action", @{$self->{'axis_fields'}}); + return $self->{'listbase'}; +} + +sub imagebase { + my $self = shift; + my $cgi = $self->{'cgi'}; + $self->{'imagebase'} = $cgi->canonicalise_query( + $self->{'tbl_field'}, "report_action", "ctype", "format", "width", "height"); + return $self->{'imagebase'}; +} + +sub switchbase { + my $self = shift; + my $cgi = $self->{'cgi'}; + $self->{'switchbase'} = $cgi->canonicalise_query( + "query_format", "report_action", "ctype", "format", "width", "height"); + return $self->{'switchbase'}; +} + +1; \ No newline at end of file diff --git a/webtools/testopia/Bugzilla/Testopia/Search.pm b/webtools/testopia/Bugzilla/Testopia/Search.pm index 3853c3ee8fae..2627fa4568e2 100644 --- a/webtools/testopia/Bugzilla/Testopia/Search.pm +++ b/webtools/testopia/Bugzilla/Testopia/Search.pm @@ -75,15 +75,16 @@ sub new { sub init { my $self = shift; my $cgi = shift; + my $fields = shift; my $user = $self->{'user'} || Bugzilla->user; $self->{'cgi'} = $cgi; + $self->{'fields'} = $fields if $fields; my $debug = $cgi->param('debug') || 0; my $dbh = Bugzilla->dbh; - - if ($debug){ + print $cgi->header if $debug; + if ($debug && !$cgi->{'final_separator'}){ use Data::Dumper; print Dumper($cgi); - print "

"; } my $page = $cgi->param('page') || 0; detaint_natural($page) if $page; @@ -137,7 +138,103 @@ sub init { my $obj = trim($cgi->param('current_tab')) || ThrowUserError('testopia-missing-parameter', {'param' => 'current_tab'}); ThrowUserError('unknown-tab') if $obj !~ '^(case|plan|run|case_run|environment)$'; trick_taint($obj); + + # If what we intend to do is generate a report, we need some tables + # to map names to ids + if ($fields){ + ## Cases ## + if (grep(/map_categories/, @$fields)) { + push @supptables, "INNER JOIN test_case_categories AS map_categories " . + "ON test_cases.category_id = map_categories.category_id"; + } + if (grep(/map_priority/, @$fields)) { + push @supptables, "INNER JOIN priority AS map_priority " . + "ON test_cases.priority_id = map_priority.id"; + } + if (grep(/map_case_status/, @$fields)) { + push @supptables, "INNER JOIN test_case_status AS map_case_status " . + "ON test_cases.case_status_id = map_case_status.case_status_id"; + } + if (grep(/map_case_components/, @$fields)) { + push @supptables, "INNER JOIN test_case_components AS tccomps " . + "ON test_cases.case_id = tccomps.case_id"; + push @supptables, "INNER JOIN components AS map_case_components " . + "ON tccomps.component_id = map_case_components.id"; + } + if (grep(/map_case_product/, @$fields)) { + push(@supptables, "INNER JOIN test_case_plans AS map_case_plans " . + "ON test_cases.case_id = map_case_plans.case_id"); + push(@supptables, "INNER JOIN test_plans AS map_product_plans " . + "ON map_case_plans.plan_id = map_product_plans.plan_id"); + push(@supptables, "INNER JOIN products AS map_case_product " . + "ON map_product_plans.product_id = map_case_product.id"); + } + if (grep(/map_case_tags/, @$fields)) { + push @supptables, "INNER JOIN test_case_tags AS tctags " . + "ON test_cases.case_id = tctags.case_id"; + push @supptables, "INNER JOIN test_tags AS map_case_tags " . + "ON tctags.tag_id = map_case_tags.tag_id"; + } + if (grep(/map_case_author/, @$fields)) { + push @supptables, "INNER JOIN profiles AS map_case_author " . + "ON test_cases.author_id = profiles.userid"; + } + if (grep(/map_default_tester/, @$fields)) { + push @supptables, "INNER JOIN profiles AS map_default_tester " . + "ON test_cases.default_tester_id = map_default_tester.userid"; + } + ## Runs ## + + if (grep(/map_run_product/, @$fields)) { + push @supptables, "INNER JOIN test_plans " . + "ON test_runs.plan_id = test_plans.plan_id"; + push @supptables, "INNER JOIN products AS map_run_product " . + "ON test_plans.product_id = map_run_product.id"; + } + if (grep(/map_run_build/, @$fields)) { + push @supptables, "INNER JOIN test_builds AS map_run_build " . + "ON test_runs.build_id = map_run_build.build_id"; + } + if (grep(/map_run_milestone/, @$fields)) { + push @supptables, "INNER JOIN test_builds AS map_run_milestone " . + "ON test_runs.build_id = map_run_build.build_id"; + } + if (grep(/map_run_environment/, @$fields)) { + push @supptables, "INNER JOIN test_environments AS map_run_environment " . + "ON test_runs.environment_id = map_run_environment.environment_id"; + } + if (grep(/map_run_tags/, @$fields)) { + push @supptables, "INNER JOIN test_run_tags " . + "ON test_runs.run_id = test_run_tags.run_id"; + push @supptables, "INNER JOIN test_tags AS map_run_tags " . + "ON test_run_tags.tag_id = map_run_tags.tag_id"; + } + if (grep(/map_run_manager/, @$fields)) { + push @supptables, "INNER JOIN profiles AS map_run_manager " . + "ON test_runs.manager_id = map_run_manager.userid"; + } + ## Plans ## + if (grep(/map_plan_type/, @$fields)) { + push @supptables, "INNER JOIN test_plan_types AS map_plan_type " . + "ON test_plans.type_id = map_plan_type.type_id"; + } + if (grep(/map_plan_product/, @$fields)) { + push @supptables, "INNER JOIN products AS map_plan_product " . + "ON test_plans.product_id = map_plan_product.id"; + } + if (grep(/map_plan_tags/, @$fields)) { + push @supptables, "INNER JOIN test_plan_tags " . + "ON test_plans.plan_id = test_plan_tags.plan_id"; + push @supptables, "INNER JOIN test_tags AS map_plan_tags " . + "ON test_plan_tags.tag_id = map_plan_tags.tag_id"; + } + if (grep(/map_plan_author/, @$fields)) { + push @supptables, "INNER JOIN profiles AS map_plan_author " . + "ON test_plans.author_id = map_plan_author.userid"; + } + } + # Set up tables for field sort order my $order = $cgi->param('order') || ''; if ($order eq 'author') { @@ -262,19 +359,43 @@ sub init { "ON test_plans.plan_id = plan_texts.plan_id"); $f = "plan_texts.plan_text"; }, + "^prod_name," => sub { + push(@supptables, + "INNER JOIN products ". + "ON test_". $obj ."s.product_id = products.id"); + $f = 'products.name'; + }, + "^case_status," => sub { + push(@supptables, + "INNER JOIN test_case_status AS case_status " . + "ON test_cases.case_status_id = case_status.case_status_id"); + $f = 'case_status.name'; + }, + "^priority," => sub { + push(@supptables, + "INNER JOIN priority ". + "ON test_". $obj ."s.priority_id = priority.id"); + $f = 'priority.value'; + }, "^environment," => sub { - if ($obj eq 'case_run'){ - $f = "environment_id"; - } - else{ - push(@supptables, - "LEFT JOIN test_environments AS env " . - "ON test_runs.environment_id = env.environment_id"); - $f = "env.xml"; - } + push(@supptables, + "INNER JOIN test_environments ". + "ON test_". $obj ."s.environment_id = test_environments.environment_id"); + $f = 'test_environments.name'; + }, + "^plan_type," => sub { + push(@supptables, + "INNER JOIN test_plan_types ". + "ON test_plans.type_id = test_plan_types.type_id"); + $f = 'test_plan_types.name'; + }, + "^case_run_status," => sub { + push(@supptables, + "INNER JOIN test_case_run_status AS tcrs ". + "ON test_case_runs.case_run_status_id = tcrs.case_run_status_id"); + $f = 'tcrs.name'; }, "^env_products," => sub { - print STDERR "THIS IS HERE"; push(@supptables, "INNER JOIN products as env_products ON test_environments.product_id = env_products.id"); @@ -448,7 +569,13 @@ sub init { push(@supptables, "INNER JOIN products " . "ON test_plans.product_id = products.id"); - $f = "test_plans.product_id"; + if ($cgi->param('product_id')){ + $f = "test_plans.product_id"; + } + else { + $f = "products.name"; + } + }, "^run_prod," => sub { push(@supptables, @@ -457,7 +584,12 @@ sub init { push(@supptables, "INNER JOIN products " . "ON test_plans.product_id = products.id"); - $f = "test_plans.product_id"; + if ($cgi->param('product_id')){ + $f = "test_plans.product_id"; + } + else { + $f = "products.name"; + } }, "^(author|manager|default_tester)," => sub { push(@supptables, @@ -633,19 +765,25 @@ sub init { } push(@specialchart, ["bug", $type, join(',', $cgi->param('bug_id'))]); } - if ($cgi->param("product_id")){ + if ($cgi->param("product_id") || $cgi->param("product")){ + my $attribute = $cgi->param("product_id") ? "product_id" : "product"; my $type = "anyexact"; if ($cgi->param('prodidtype') && $cgi->param('prodidtype') eq 'exclude') { $type = "nowords"; } if ($obj eq 'run'){ - push(@specialchart, ["run_prod", $type, join(',', $cgi->param('product_id'))]); + push(@specialchart, ["run_prod", $type, join(',', $cgi->param($attribute))]); } elsif ($obj eq 'case'){ - push(@specialchart, ["case_prod", $type, join(',', $cgi->param('product_id'))]); + push(@specialchart, ["case_prod", $type, join(',', $cgi->param($attribute))]); } else{ - push(@specialchart, ["product_id", $type, join(',', $cgi->param('product_id'))]); + if ($cgi->param("product")){ + push(@specialchart, ["prod_name", $type, join(',', $cgi->param($attribute))]); + } + else{ + push(@specialchart, ["product_id", $type, join(',', $cgi->param($attribute))]); + } } } # Check the Multi select fields and add them to the chart @@ -653,7 +791,9 @@ sub init { "component", "isautomated", "case_run_status_id", "default_product_version", "type_id", "build", "environment_id", "milestone", "env_products", - "env_categories", "env_elements", "env_properties", "env_expressions"); + "env_categories", "env_elements", "env_properties", + "env_expressions", "case_status", "priority", "environment", + "plan_type", "case_run_status"); foreach my $field ($cgi->param()) { if (lsearch(\@legal_fields, $field) != -1) { @@ -890,7 +1030,11 @@ sub init { push(@supptables, $specialorderjoin{$splitfield[0]}); } } - + if ($debug){ + print "
";
+        print join("\n", @supptables);
+        print "
"; + } my %suppseen = ("test_". $obj ."s" => 1); my $suppstring = "test_". $obj ."s"; my @supplist = (" "); @@ -919,9 +1063,14 @@ sub init { # Make sure we create a legal SQL query. @andlist = ("1 = 1") if !@andlist; - my $query = "SELECT test_". $obj ."s.". $obj. "_id" . - " FROM $suppstring"; - + my $query; + if ($self->{'fields'}){ + $query = "SELECT ". join(",", @{$self->{'fields'}}); + } + else { + $query = "SELECT test_". $obj ."s.". $obj. "_id"; + } + $query .= " FROM $suppstring"; $query .= " WHERE " . join(' AND ', (@wherepart, @andlist)); @@ -950,7 +1099,6 @@ sub init { } if ($debug) { print "

" . value_quote($query) . "

\n"; - exit; } $self->{'sql'} = $query; diff --git a/webtools/testopia/Bugzilla/Testopia/TestCase.pm b/webtools/testopia/Bugzilla/Testopia/TestCase.pm index 123a62e70f1c..229cbd4e50d9 100644 --- a/webtools/testopia/Bugzilla/Testopia/TestCase.pm +++ b/webtools/testopia/Bugzilla/Testopia/TestCase.pm @@ -121,6 +121,27 @@ $self->{'display_columns'} = \@columns; return $self->{'display_columns'}; } +sub report_columns { + my $self = shift; + my %columns; + # Changes here need to match Report.pm + $columns{'Status'} = "case_status"; + $columns{'Priority'} = "priority"; + $columns{'Product'} = "product_id"; + $columns{'Component'} = "component"; + $columns{'Category'} = "category"; + $columns{'Automated'} = "isautomated"; + $columns{'Tags'} = "tags"; + $columns{'Requirement'} = "requirement"; + $columns{'Author'} = "author"; + $columns{'Default tester'} = "default_tester"; + $columns{''} = ''; + my @result; + push @result, {'name' => $_, 'id' => $columns{$_}} foreach (keys %columns); + return \@result; + +} + =head1 METHODS =cut diff --git a/webtools/testopia/Bugzilla/Testopia/TestPlan.pm b/webtools/testopia/Bugzilla/Testopia/TestPlan.pm index f6cf90cf5032..9e67666645da 100644 --- a/webtools/testopia/Bugzilla/Testopia/TestPlan.pm +++ b/webtools/testopia/Bugzilla/Testopia/TestPlan.pm @@ -87,6 +87,22 @@ use constant DB_COLUMNS => qw( our $columns = join(", ", DB_COLUMNS); +sub report_columns { + my $self = shift; + my %columns; + # Changes here need to match Report.pm + $columns{'Type'} = "plan_type"; + $columns{'Version'} = "default_product_version"; + $columns{'Product'} = "product"; + $columns{'Archived'} = "archived"; + $columns{'Tags'} = "tags"; + $columns{'Author'} = "author"; + $columns{''} = ''; + my @result; + push @result, {'name' => $_, 'id' => $columns{$_}} foreach (keys %columns); + return \@result; + +} ############################### #### Methods #### diff --git a/webtools/testopia/Bugzilla/Testopia/TestRun.pm b/webtools/testopia/Bugzilla/Testopia/TestRun.pm index 1d57f84a3c1b..e17aef694173 100644 --- a/webtools/testopia/Bugzilla/Testopia/TestRun.pm +++ b/webtools/testopia/Bugzilla/Testopia/TestRun.pm @@ -88,6 +88,24 @@ use constant DB_COLUMNS => qw( our $columns = join(", ", DB_COLUMNS); +sub report_columns { + my $self = shift; + my %columns; + # Changes here need to match Report.pm + $columns{'Status'} = "run_status"; + $columns{'Version'} = "default_product_version"; + $columns{'Product'} = "product"; + $columns{'Build'} = "build"; + $columns{'Milestone'} = "milestone"; + $columns{'Environment'} = "environment"; + $columns{'Tags'} = "tags"; + $columns{'Manager'} = "manager"; + $columns{''} = ''; + my @result; + push @result, {'name' => $_, 'id' => $columns{$_}} foreach (keys %columns); + return \@result; + +} ############################### #### Methods #### diff --git a/webtools/testopia/template/en/default/hook/global/useful-links.html.tmpl/end/tr.html.tmpl b/webtools/testopia/template/en/default/hook/global/useful-links.html.tmpl/end/tr.html.tmpl index b3e91e973d3e..f22b024edb2a 100644 --- a/webtools/testopia/template/en/default/hook/global/useful-links.html.tmpl/end/tr.html.tmpl +++ b/webtools/testopia/template/en/default/hook/global/useful-links.html.tmpl/end/tr.html.tmpl @@ -1,5 +1,23 @@ [%# 1.0@bugzilla.org %] - +[%# The contents of this file are subject to the Mozilla Public + # License Version 1.1 (the "License"); you may not use this file + # except in compliance with the License. You may obtain a copy of + # the License at http://www.mozilla.org/MPL/ + # + # Software distributed under the License is distributed on an "AS + # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + # implied. See the License for the specific language governing + # rights and limitations under the License. + # + # The Original Code is the Bugzilla Testopia System. + # + # The Initial Developer of the Original Code is Greg Hendricks. + # Portions created by Greg Hendricks are Copyright (C) 2001 + # Greg Hendricks. All Rights Reserved. + # + # Contributor(s): Greg Hendricks + #%] + [% IF plan_id %] [% plan_id = plan_id %] [% ELSIF plan %] @@ -17,6 +35,7 @@