From d0971aeeff4715e19209f4736f71324d1aaa97a8 Mon Sep 17 00:00:00 2001
From: "ghendricks%novell.com"
Date: Thu, 5 Oct 2006 21:11:04 +0000
Subject: [PATCH] Comitting Testopia 1.1 beta
git-svn-id: svn://10.0.0.236/trunk@213182 18797224-902f-48f8-a5cc-f745e15eee43
---
.../testopia/Bugzilla/Testopia/Attachment.pm | 2 +-
.../Bugzilla/Testopia/Classification.pm | 85 +++
.../testopia/Bugzilla/Testopia/Environment.pm | 528 ++++++++++++----
.../Bugzilla/Testopia/Environment/Category.pm | 543 ++++++++++++++++
.../Bugzilla/Testopia/Environment/Element.pm | 471 ++++++++++++++
.../Bugzilla/Testopia/Environment/Property.pm | 372 +++++++++++
.../Bugzilla/Testopia/Environment/Xml.pm | 585 +++++++++++++++++
.../testopia/Bugzilla/Testopia/Product.pm | 54 +-
.../testopia/Bugzilla/Testopia/Schema.pm | 489 --------------
.../testopia/Bugzilla/Testopia/Search.pm | 193 +++++-
.../testopia/Bugzilla/Testopia/Table.pm | 24 +-
.../testopia/Bugzilla/Testopia/TestCase.pm | 398 +++++++++---
.../testopia/Bugzilla/Testopia/TestCaseRun.pm | 186 +++++-
.../testopia/Bugzilla/Testopia/TestPlan.pm | 149 ++++-
.../testopia/Bugzilla/Testopia/TestRun.pm | 156 ++++-
.../testopia/Bugzilla/Testopia/TestTag.pm | 3 +-
.../testopia/Bugzilla/Testopia/Xml.pm | 389 ++++++++----
.../Bugzilla/Testopia/XmlReferences.pm | 101 +++
.../testopia/Bugzilla/Testopia/XmlTestCase.pm | 304 ++++++---
.../testopia/Bugzilla/WebService/Constants.pm | 36 ++
.../Bugzilla/WebService/Testopia/TestCase.pm | 257 ++++++++
.../WebService/Testopia/TestCaseRun.pm | 186 ++++++
.../Bugzilla/WebService/Testopia/TestPlan.pm | 190 ++++++
.../Bugzilla/WebService/Testopia/TestRun.pm | 177 ++++++
.../useful-links.html.tmpl/end/tr.html.tmpl | 28 +-
.../errors/tr-user-error.html.tmpl | 2 +-
.../hook/index.html.tmpl/links/tr.html.tmpl | 2 +-
.../testopia/admin/plantypes/add.html.tmpl | 32 +
.../testopia/admin/plantypes/delete.html.tmpl | 23 +
.../testopia/admin/plantypes/edit.html.tmpl | 33 +
.../testopia/admin/plantypes/show.html.tmpl | 41 ++
.../en/default/testopia/admin/show.html.tmpl | 44 ++
.../en/default/testopia/blocks.html.tmpl | 2 +-
.../en/default/testopia/case/add.html.tmpl | 8 +-
.../en/default/testopia/case/bugs.html.tmpl | 44 ++
.../en/default/testopia/case/filter.html.tmpl | 85 +++
.../en/default/testopia/case/form.html.tmpl | 67 +-
.../en/default/testopia/case/list.html.tmpl | 12 +
.../en/default/testopia/case/show.html.tmpl | 49 +-
.../en/default/testopia/case/table.html.tmpl | 16 +-
.../testopia/caserun/case-history.html.tmpl | 12 +-
.../default/testopia/caserun/filter.html.tmpl | 144 +++--
.../default/testopia/caserun/form.html.tmpl | 14 +-
.../default/testopia/caserun/list.html.tmpl | 3 +-
.../caserun/short-form-header.html.tmpl | 6 +-
.../testopia/caserun/short-form.html.tmpl | 84 ++-
.../default/testopia/caserun/show.html.tmpl | 14 +-
.../default/testopia/caserun/table.html.tmpl | 5 +
.../testopia/environment/add.html.tmpl | 44 +-
.../environment/admin/category.html.tmpl | 65 ++
.../environment/admin/element.html.tmpl | 128 ++++
.../environment/admin/property.html.tmpl | 58 ++
.../testopia/environment/admin/show.html.tmpl | 75 +++
.../testopia/environment/admin/tree.html.tmpl | 460 ++++++++++++++
.../environment/admin/valid_exp.html.tmpl | 70 +++
.../testopia/environment/choose.html.tmpl | 38 ++
.../testopia/environment/delete.html.tmpl | 2 +-
.../testopia/environment/export.xml.tmpl | 45 ++
.../testopia/environment/form.html.tmpl | 81 ++-
.../testopia/environment/import.xml.tmpl | 57 ++
.../testopia/environment/list.html.tmpl | 5 -
.../testopia/environment/show.html.tmpl | 202 +++++-
.../testopia/environment/table.html.tmpl | 40 +-
.../en/default/testopia/percent_bar.html.tmpl | 18 +-
.../en/default/testopia/plan/add.html.tmpl | 9 +-
.../en/default/testopia/plan/form.html.tmpl | 31 +-
.../en/default/testopia/plan/list.html.tmpl | 15 +-
.../testopia/plan/show-document.html.tmpl | 4 -
.../en/default/testopia/plan/show.html.tmpl | 21 +-
.../en/default/testopia/run/add.html.tmpl | 4 +
.../en/default/testopia/run/clone.html.tmpl | 5 +-
.../en/default/testopia/run/form.html.tmpl | 15 +-
.../en/default/testopia/run/list.html.tmpl | 12 +-
.../en/default/testopia/run/show.html.tmpl | 84 ++-
.../en/default/testopia/run/table.html.tmpl | 6 +-
.../testopia/search/advanced.html.tmpl | 46 +-
.../en/default/testopia/search/case.html.tmpl | 2 +-
.../testopia/search/environment.html.tmpl | 411 ++++++++++++
.../en/default/testopia/search/plan.html.tmpl | 10 -
.../testopia/search/variables.none.tmpl | 41 ++
.../en/default/testopia/tag/table.html.tmpl | 4 +-
mozilla/webtools/testopia/testopia.dtd | 92 +++
mozilla/webtools/testopia/testopia/README | 85 +++
.../testopia/testopia/css/default.css | 30 +-
.../webtools/testopia/testopia/dojo/README | 12 +
.../testopia/testopia/environment.dtd | 58 +-
.../webtools/testopia/testopia/img/1x1.gif | Bin 0 -> 123 bytes
.../webtools/testopia/testopia/img/circle.gif | Bin 0 -> 342 bytes
.../webtools/testopia/testopia/img/circle.png | Bin 0 -> 29717 bytes
.../testopia/testopia/img/confirm.png | Bin 0 -> 353 bytes
.../testopia/testopia/img/confirm.svg | 69 ++
.../testopia/testopia/img/env_lookup.png | Bin 0 -> 598 bytes
.../testopia/testopia/img/env_lookup.svg | 86 +++
.../webtools/testopia/testopia/img/error.gif | Bin 0 -> 177 bytes
.../webtools/testopia/testopia/img/folder.gif | Bin 0 -> 165 bytes
.../testopia/testopia/img/folder_blue.gif | Bin 0 -> 163 bytes
.../testopia/testopia/img/folder_red.gif | Bin 0 -> 163 bytes
.../testopia/testopia/img/green_bar.gif | Bin 0 -> 118 bytes
.../testopia/testopia/img/orange_bar.gif | Bin 0 -> 118 bytes
.../testopia/testopia/img/red_bar.gif | Bin 0 -> 118 bytes
.../testopia/testopia/img/selected_value.png | Bin 0 -> 1104 bytes
.../webtools/testopia/testopia/img/square.gif | Bin 0 -> 301 bytes
.../webtools/testopia/testopia/img/square.png | Bin 0 -> 27882 bytes
.../testopia/testopia/img/triangle.gif | Bin 0 -> 315 bytes
.../testopia/testopia/img/triangle.png | Bin 0 -> 29306 bytes
.../webtools/testopia/testopia/js/caserun.js | 29 +
mozilla/webtools/testopia/testopia/js/util.js | 11 +-
mozilla/webtools/testopia/testopia/patch-2.22 | 595 +++++++++++++++++-
.../testopia/testopia/testopia.create.sql | 365 -----------
.../testopia/testopia/testopia.drop.sql | 46 +-
.../testopia/testopia/testopia.insert.sql | 41 ++
mozilla/webtools/testopia/tr_admin.cgi | 102 +++
.../testopia/tr_admin_environment.cgi | 557 ++++++++++++++++
.../testopia/tr_case_dependencies.cgi | 43 --
mozilla/webtools/testopia/tr_defparams.pl | 178 ------
mozilla/webtools/testopia/tr_environment.cgi | 161 -----
.../testopia/tr_export_environment.cgi | 68 ++
.../testopia/tr_import_environment.cgi | 347 ++++++++++
mozilla/webtools/testopia/tr_install.pl | 546 ++++++++--------
.../webtools/testopia/tr_list_caseruns.cgi | 17 +-
mozilla/webtools/testopia/tr_list_cases.cgi | 79 ++-
.../testopia/tr_list_environments.cgi | 56 ++
mozilla/webtools/testopia/tr_list_plans.cgi | 2 +-
mozilla/webtools/testopia/tr_list_runs.cgi | 12 +-
mozilla/webtools/testopia/tr_new_case.cgi | 46 +-
.../webtools/testopia/tr_new_environment.cgi | 92 +++
mozilla/webtools/testopia/tr_new_plan.cgi | 1 -
mozilla/webtools/testopia/tr_new_run.cgi | 19 +-
mozilla/webtools/testopia/tr_query.cgi | 109 +++-
mozilla/webtools/testopia/tr_show_case.cgi | 67 +-
mozilla/webtools/testopia/tr_show_caserun.cgi | 102 ++-
.../webtools/testopia/tr_show_environment.cgi | 300 +++++++++
mozilla/webtools/testopia/tr_show_plan.cgi | 19 +-
mozilla/webtools/testopia/tr_show_run.cgi | 43 +-
mozilla/webtools/testopia/tr_xmlrpc.cgi | 48 ++
135 files changed, 10808 insertions(+), 2481 deletions(-)
create mode 100644 mozilla/webtools/testopia/Bugzilla/Testopia/Classification.pm
create mode 100644 mozilla/webtools/testopia/Bugzilla/Testopia/Environment/Category.pm
create mode 100644 mozilla/webtools/testopia/Bugzilla/Testopia/Environment/Element.pm
create mode 100644 mozilla/webtools/testopia/Bugzilla/Testopia/Environment/Property.pm
create mode 100644 mozilla/webtools/testopia/Bugzilla/Testopia/Environment/Xml.pm
delete mode 100644 mozilla/webtools/testopia/Bugzilla/Testopia/Schema.pm
create mode 100644 mozilla/webtools/testopia/Bugzilla/Testopia/XmlReferences.pm
create mode 100644 mozilla/webtools/testopia/Bugzilla/WebService/Constants.pm
create mode 100644 mozilla/webtools/testopia/Bugzilla/WebService/Testopia/TestCase.pm
create mode 100644 mozilla/webtools/testopia/Bugzilla/WebService/Testopia/TestCaseRun.pm
create mode 100644 mozilla/webtools/testopia/Bugzilla/WebService/Testopia/TestPlan.pm
create mode 100644 mozilla/webtools/testopia/Bugzilla/WebService/Testopia/TestRun.pm
create mode 100644 mozilla/webtools/testopia/template/en/default/testopia/admin/plantypes/add.html.tmpl
create mode 100644 mozilla/webtools/testopia/template/en/default/testopia/admin/plantypes/delete.html.tmpl
create mode 100644 mozilla/webtools/testopia/template/en/default/testopia/admin/plantypes/edit.html.tmpl
create mode 100644 mozilla/webtools/testopia/template/en/default/testopia/admin/plantypes/show.html.tmpl
create mode 100644 mozilla/webtools/testopia/template/en/default/testopia/admin/show.html.tmpl
create mode 100644 mozilla/webtools/testopia/template/en/default/testopia/case/bugs.html.tmpl
create mode 100644 mozilla/webtools/testopia/template/en/default/testopia/case/filter.html.tmpl
create mode 100644 mozilla/webtools/testopia/template/en/default/testopia/environment/admin/category.html.tmpl
create mode 100644 mozilla/webtools/testopia/template/en/default/testopia/environment/admin/element.html.tmpl
create mode 100644 mozilla/webtools/testopia/template/en/default/testopia/environment/admin/property.html.tmpl
create mode 100644 mozilla/webtools/testopia/template/en/default/testopia/environment/admin/show.html.tmpl
create mode 100644 mozilla/webtools/testopia/template/en/default/testopia/environment/admin/tree.html.tmpl
create mode 100644 mozilla/webtools/testopia/template/en/default/testopia/environment/admin/valid_exp.html.tmpl
create mode 100644 mozilla/webtools/testopia/template/en/default/testopia/environment/choose.html.tmpl
create mode 100644 mozilla/webtools/testopia/template/en/default/testopia/environment/export.xml.tmpl
create mode 100644 mozilla/webtools/testopia/template/en/default/testopia/environment/import.xml.tmpl
create mode 100644 mozilla/webtools/testopia/template/en/default/testopia/search/environment.html.tmpl
create mode 100644 mozilla/webtools/testopia/template/en/default/testopia/search/variables.none.tmpl
create mode 100644 mozilla/webtools/testopia/testopia.dtd
create mode 100644 mozilla/webtools/testopia/testopia/README
create mode 100644 mozilla/webtools/testopia/testopia/dojo/README
create mode 100644 mozilla/webtools/testopia/testopia/img/1x1.gif
create mode 100644 mozilla/webtools/testopia/testopia/img/circle.gif
create mode 100644 mozilla/webtools/testopia/testopia/img/circle.png
create mode 100644 mozilla/webtools/testopia/testopia/img/confirm.png
create mode 100644 mozilla/webtools/testopia/testopia/img/confirm.svg
create mode 100644 mozilla/webtools/testopia/testopia/img/env_lookup.png
create mode 100644 mozilla/webtools/testopia/testopia/img/env_lookup.svg
create mode 100644 mozilla/webtools/testopia/testopia/img/error.gif
create mode 100644 mozilla/webtools/testopia/testopia/img/folder.gif
create mode 100644 mozilla/webtools/testopia/testopia/img/folder_blue.gif
create mode 100644 mozilla/webtools/testopia/testopia/img/folder_red.gif
create mode 100644 mozilla/webtools/testopia/testopia/img/green_bar.gif
create mode 100644 mozilla/webtools/testopia/testopia/img/orange_bar.gif
create mode 100644 mozilla/webtools/testopia/testopia/img/red_bar.gif
create mode 100644 mozilla/webtools/testopia/testopia/img/selected_value.png
create mode 100644 mozilla/webtools/testopia/testopia/img/square.gif
create mode 100644 mozilla/webtools/testopia/testopia/img/square.png
create mode 100644 mozilla/webtools/testopia/testopia/img/triangle.gif
create mode 100644 mozilla/webtools/testopia/testopia/img/triangle.png
delete mode 100644 mozilla/webtools/testopia/testopia/testopia.create.sql
create mode 100644 mozilla/webtools/testopia/testopia/testopia.insert.sql
create mode 100755 mozilla/webtools/testopia/tr_admin.cgi
create mode 100755 mozilla/webtools/testopia/tr_admin_environment.cgi
delete mode 100644 mozilla/webtools/testopia/tr_defparams.pl
delete mode 100755 mozilla/webtools/testopia/tr_environment.cgi
create mode 100755 mozilla/webtools/testopia/tr_export_environment.cgi
create mode 100755 mozilla/webtools/testopia/tr_import_environment.cgi
create mode 100755 mozilla/webtools/testopia/tr_list_environments.cgi
create mode 100755 mozilla/webtools/testopia/tr_new_environment.cgi
create mode 100755 mozilla/webtools/testopia/tr_show_environment.cgi
create mode 100755 mozilla/webtools/testopia/tr_xmlrpc.cgi
diff --git a/mozilla/webtools/testopia/Bugzilla/Testopia/Attachment.pm b/mozilla/webtools/testopia/Bugzilla/Testopia/Attachment.pm
index 0482513358e..975563bb0b4 100644
--- a/mozilla/webtools/testopia/Bugzilla/Testopia/Attachment.pm
+++ b/mozilla/webtools/testopia/Bugzilla/Testopia/Attachment.pm
@@ -163,7 +163,7 @@ sub store {
$self->{'filename'}, $timestamp, $self->{'mime_type'});
my $key = $dbh->bz_last_key( 'test_attachments', 'attachment_id' );
- $dbh->do("INSERT INTO test_attachment_data VALUES(?,?)",
+ $dbh->do("INSERT INTO test_attachment_data (attachment_id, contents) VALUES(?,?)",
undef, $key, $self->{'contents'});
return $key;
diff --git a/mozilla/webtools/testopia/Bugzilla/Testopia/Classification.pm b/mozilla/webtools/testopia/Bugzilla/Testopia/Classification.pm
new file mode 100644
index 00000000000..2053f96be52
--- /dev/null
+++ b/mozilla/webtools/testopia/Bugzilla/Testopia/Classification.pm
@@ -0,0 +1,85 @@
+# -*- 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 Test Runner System.
+#
+# The Initial Developer of the Original Code is Maciej Maczynski.
+# Portions created by Maciej Maczynski are Copyright (C) 2001
+# Maciej Maczynski. All Rights Reserved.
+#
+# Contributor(s): Greg Hendricks
+
+package Bugzilla::Testopia::Classification;
+
+use strict;
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Config;
+
+# Extends Bugzilla::Classification;
+use base "Bugzilla::Classification";
+
+use Bugzilla;
+
+sub user_visible_products {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ if (!$self->{'products'}) {
+ my $query = "SELECT id FROM products " .
+ "LEFT JOIN group_control_map " .
+ "ON group_control_map.product_id = products.id ";
+ if (Param('useentrygroupdefault')) {
+ $query .= "AND group_control_map.entry != 0 ";
+ } else {
+ $query .= "AND group_control_map.membercontrol = " .
+ CONTROLMAPMANDATORY . " ";
+ }
+ if (%{Bugzilla->user->groups}) {
+ $query .= "AND group_id NOT IN(" .
+ join(',', values(%{Bugzilla->user->groups})) . ") ";
+ }
+ $query .= "WHERE group_id IS NULL AND products.classification_id= ? ORDER BY products.name";
+
+ my $product_ids = $dbh->selectcol_arrayref($query, undef, $self->id);
+
+ my @products;
+ foreach my $product_id (@$product_ids) {
+ push (@products, new Bugzilla::Testopia::Product($product_id));
+ }
+ $self->{'user_visible_products'} = \@products;
+ }
+ return $self->{'user_visible_products'};
+}
+
+sub products_to_json {
+ my $self = shift;
+ my ($disable_move) = @_;
+
+ $disable_move ||= '';
+ $disable_move = ',"addChild","move"' if $disable_move;
+ my $products = $self->user_visible_products;
+ my $json = "[";
+ foreach my $obj (@{$products}){
+ $json .= '{title:"' . $obj->name . '",';
+ $json .= 'isFolder:' . (scalar @{$obj->environment_categories} > 0 ? "true" : "false") . ',';
+ $json .= 'objectId:"' . $obj->id . '",';
+ $json .= 'widgetId:"product' . $obj->id . '",';
+ $json .= 'actionsDisabled:["addElement","addProperty","addValue"'. $disable_move .'],';
+ $json .= 'childIconSrc:"testopia/img/folder_red.gif"},';
+ }
+ chop $json;
+ $json .= "]";
+ return $json;
+}
+1;
\ No newline at end of file
diff --git a/mozilla/webtools/testopia/Bugzilla/Testopia/Environment.pm b/mozilla/webtools/testopia/Bugzilla/Testopia/Environment.pm
index 60ccc492411..fd77a9cd113 100644
--- a/mozilla/webtools/testopia/Bugzilla/Testopia/Environment.pm
+++ b/mozilla/webtools/testopia/Bugzilla/Testopia/Environment.pm
@@ -16,7 +16,9 @@
# Portions created by Maciej Maczynski are Copyright (C) 2001
# Maciej Maczynski. All Rights Reserved.
#
-# Contributor(s): Greg Hendricks
+# Contributor(s): Greg Hendricks
+# Michael Hight
+# Garrett Braden
=head1 NAME
@@ -26,11 +28,12 @@ Bugzilla::Testopia::Environment - A test environment
Environments are a set of parameters dictating what conditions
a test was conducted in. Each test run must have an environment.
-Environments can be very simple or very complex. Since there is
-no easy way of defining a limit for an environment, the bulk of
-the information is captured as an XML document. At the very least
-however, and environment consists of the OS and platform from
-Bugzilla.
+Environments can be very simple or very complex.
+
+Environments are comprised of Elemements, Properties, and Values.
+Elements can be nested within other elements. Each element can have
+zero or more properties. Each property can only have one value selected
+of the possible values.
=head1 SYNOPSIS
@@ -54,23 +57,23 @@ use Bugzilla::User;
=head1 FIELDS
environment_id
- op_sys_id
- rep_platform_id
+ product_id
name
- xml
+ isactive
=cut
use constant DB_COLUMNS => qw(
- test_environments.environment_id
- test_environments.op_sys_id
- test_environments.rep_platform_id
- test_environments.name
- test_environments.xml
+ environment_id
+ product_id
+ name
+ isactive
);
our $columns = join(", ", DB_COLUMNS);
+our constant $max_depth = 7;
+
###############################
#### Methods ####
###############################
@@ -101,16 +104,18 @@ sub _init {
my $self = shift;
my ($param) = (@_);
my $dbh = Bugzilla->dbh;
-
- my $id = $param unless (ref $param eq 'HASH');
+
+ my $id = $param unless (ref $param eq 'HASH' || ref $param eq 'Bugzilla::Testopia::Environment::Xml');
my $obj;
-
+
if (defined $id && detaint_natural($id)) {
$obj = $dbh->selectrow_hashref(qq{
- SELECT $columns FROM test_environments
- WHERE environment_id = ?}, undef, $id);
- } elsif (ref $param eq 'HASH'){
+ SELECT $columns
+ FROM test_environments
+ WHERE environment_id = ?}, undef, ($id));
+
+ } elsif (ref $param eq 'HASH' || ref $param eq 'Bugzilla::Testopia::Environment::Xml'){
$obj = $param;
}
@@ -119,41 +124,263 @@ sub _init {
foreach my $field (keys %$obj) {
$self->{$field} = $obj->{$field};
}
+
+ if(! ref $obj eq 'Bugzilla::Testopia::Environment::Xml'){
+
+ $self->get_environment_elements();
+ my @elements = $self->{'elements'};
+
+ foreach my $elem (@elements)
+ {
+ foreach my $element (@$elem)
+ {
+
+ my $elem_id = $element->{'element_id'};
+ my @properties = $element->{'properties'};
+
+ foreach my $prop (@properties)
+ {
+ foreach my $property (@$prop)
+ {
+ my $prop_id = $property->{'property_id'};
+ $property->{'value_selected'} = $self->get_value_selected($self->{'environment_id'},$elem_id,$prop_id);
+ }
+ }
+ }
+ }
+ }
+
return $self;
}
-=head2 get_op_sys_list
-Returns the list of bugzilla op_sys entries
+=head2 get element list for environment
+
+Returns an array of element objects for an environment
=cut
-sub get_op_sys_list{
- my $self = shift;
+sub get_environment_elements{
my $dbh = Bugzilla->dbh;
- my $ref = $dbh->selectall_arrayref(
- "SELECT id, value AS name
- FROM op_sys
- ORDER BY sortkey", {'Slice'=>{}});
+ my $self = shift;
+
+ return $self->{'elements'} if exists $self->{'elements'};
+
+ my $id = $self->{'environment_id'};
+
+ my $ref = $dbh->selectcol_arrayref(qq{
+ SELECT DISTINCT tee.element_id
+ FROM test_environment_map as tem
+ JOIN test_environment_element as tee
+ ON tem.element_id = tee.element_id
+ WHERE tem.environment_id = ?},undef,$id);
+
+ my @elements;
- return $ref;
+ foreach my $val (@$ref){
+ push @elements, Bugzilla::Testopia::Environment::Element->new($val);
+ }
+ $self->{'elements'} = \@elements;
+
+ return \@elements;
}
-=head2 get_rep_platform_list
+sub element_count {
+ my $self = shift;
+
+ return scalar(@{$self->get_environment_elements});
+}
-Returns the list of rep_platforms from bugzilla
+sub elements_to_json {
+ my $self = shift;
+
+ my $elements = $self->get_environment_elements;
+ my $json = '[';
+
+ foreach my $element (@$elements)
+ {
+ $json .= '{title:"'. $element->{'name'} .'",';
+ $json .= 'objectId:"'. $element->{'element_id'}. '",';
+ $json .= 'widgetId:"element'. $element->{'element_id'} .'",';
+ $json .= 'actionsDisabled:["addCategory","addValue","addChild"],';
+ $json .= 'isFolder:true,' if($element->check_for_children || $element->check_for_properties);
+ $json .= 'childIconSrc:"testopia/img/circle.gif"},';
+ }
+ chop $json;
+ $json .= ']';
+
+ return $json;
+}
+
+
+=head2 get_value_selected
+
+Returns a selected value for the specified environment element property instance.
=cut
-sub get_rep_platform_list{
+sub get_value_selected{
+ my $dbh = Bugzilla->dbh;
my $self = shift;
- my $dbh = Bugzilla->dbh;
- my $ref = $dbh->selectall_arrayref(
- "SELECT id, value AS name
- FROM rep_platform
- ORDER BY sortkey", {'Slice'=>{}});
+
+ my ($environment,$element,$property) = (@_);
+
+ my ($var) = $dbh->selectrow_array(
+ "SELECT value_selected
+ FROM test_environment_map
+ WHERE environment_id = ?
+ AND element_id = ?
+ AND property_id = ?",
+ undef,($environment,$element,$property));
+
+ return $var;
+}
- return $ref;
+=head2 get environment names
+
+Returns the list of environment names and ids
+
+=cut
+
+sub get_environments{
+ my $dbh = Bugzilla->dbh;
+ my $self = shift;
+
+ my $ref = $dbh->selectall_arrayref(
+ "SELECT environment_id, name
+ FROM test_environments");
+
+ return $ref;
+}
+
+sub get_all_env_categories {
+ my $self = shift;
+ my ($byid) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $idstr = $byid ? 'env_category_id' : 'DISTINCT name';
+ my $ref = $dbh->selectall_arrayref(
+ "SELECT $idstr AS id, name
+ FROM test_environment_category",
+ {'Slice' => {}});
+
+ return $ref;
+}
+
+sub get_all_visible_elements {
+ my $self = shift;
+ my ($byid) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $idstr = $byid ? 'element_id' : 'DISTINCT name';
+ my $ref = $dbh->selectall_arrayref(
+ "SELECT $idstr AS id, name
+ FROM test_environment_element",
+ {'Slice' => {}});
+
+ return $ref;
+}
+
+sub get_all_element_properties {
+ my $self = shift;
+ my ($byid) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $idstr = $byid ? 'property_id' : 'DISTINCT name';
+ my $ref = $dbh->selectall_arrayref(
+ "SELECT $idstr AS id, name, validexp
+ FROM test_environment_property",
+ {'Slice' => {}});
+
+ return $ref;
+}
+
+sub get_distinct_property_values {
+ my $self = shift;
+ my @exps;
+ foreach my $prop (@{$self->get_all_element_properties}){
+ push @exps, split(/\|/, $prop->{'validexp'})
+ }
+ my %seen;
+ foreach my $v (@exps){
+ $seen{$v} = $v;
+ }
+ my @values;
+ foreach my $v (keys %seen){
+ my %p;
+ $p{'id'} = $v;
+ $p{'name'} = $v;
+ push @values, \%p;
+ }
+ return \@values;
+}
+
+=head2 get all elements
+
+Returns the list of element names, ids and category names
+
+=cut
+
+sub get_all_elements{
+
+ my $dbh = Bugzilla->dbh;
+ my $self = shift;
+
+ my $ref = $dbh->selectcol_arrayref(
+ "SELECT tee.element_id
+ FROM test_environment_map as tem
+ JOIN test_environment_element as tee
+ ON tem.element_id = tee.element_id",
+ undef);
+
+ my @elements;
+
+ foreach my $val (@$ref){
+ push @elements, Bugzilla::Testopia::Environment::Element->new($val);
+ }
+
+ return \@elements;
+}
+
+=head2 check environment name
+
+Returns environment id if environment exists
+
+=cut
+
+sub check_environment{
+ my $self = shift;
+ my ($name, $product_id) = (@_);
+
+ my $dbh = Bugzilla->dbh;
+
+ my ($used) = $dbh->selectrow_array(
+ "SELECT environment_id
+ FROM test_environments
+ WHERE name = ? AND product_id = ?",
+ undef, ($name, $product_id));
+
+ return $used;
+}
+
+
+=head2 Check Environment Element Property Value Selected
+
+Returns environment id if Environment Element Property Value Selected exists
+
+=cut
+
+sub check_value_selected {
+ my $self = shift;
+ my ($prop_id, $elem_id) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my ($used) = $dbh->selectrow_array(
+ "SELECT environment_id
+ FROM test_environment_map
+ WHERE environment_id = ?
+ AND property_id = ?
+ AND element_id = ?",
+ undef, ($self->{'environment_id'}, $prop_id, $elem_id));
+
+ return $used;
}
=head2 store
@@ -165,17 +392,64 @@ Serializes this environment to the database
sub store {
my $self = shift;
my $timestamp = Bugzilla::Testopia::Util::get_time_stamp();
- # Verify name is available
- return 0 if $self->check_name($self->{'name'});
-
+
+ #Verify Environment isn't already in use.
+ return undef if $self->check_environment($self->{'name'}, $self->{'product_id'});
+
my $dbh = Bugzilla->dbh;
$dbh->do("INSERT INTO test_environments ($columns)
- VALUES (?,?,?,?,?)",
- undef, (undef, $self->{'op_sys_id'},
- $self->{'rep_platform_id'}, $self->{'name'},
- $self->{'xml'}));
- my $key = $dbh->bz_last_key( 'test_plans', 'plan_id' );
- return $key;
+ VALUES (?,?,?,?)",
+ undef, (undef, $self->{'product_id'}, $self->{'name'}, $self->{'isactive'}));
+ my $key = $dbh->bz_last_key( 'test_environments', 'environment_id' );
+
+ my $elements = $self->{'elements'};
+
+ foreach my $element (@$elements)
+ {
+ $self->persist_environment_element_and_children(1, $element, "store");
+ }
+
+ return $key;
+}
+
+
+=head2 store property values
+
+Serializes the property values to the database
+
+=cut
+
+sub store_property_value {
+ my $self = shift;
+
+ my ($prop_id,$elem_id,$value_selected) = @_;
+
+ return 0 if ($self->check_value_selected($prop_id, $elem_id, $value_selected));
+
+ my $dbh = Bugzilla->dbh;
+ $dbh->do("INSERT INTO test_environment_map (environment_id,property_id,element_id,value_selected)
+ VALUES (?,?,?,?)",undef, ($self->{'environment_id'}, $prop_id, $elem_id,$value_selected));
+ return 1;
+}
+
+=head2 store environment name
+
+Serializes the environment name to the database
+
+=cut
+
+sub store_environment_name {
+ my $self = shift;
+ my ($name, $product_id) = (@_);
+ my $timestamp = Bugzilla::Testopia::Util::get_time_stamp();
+
+ return undef if $self->check_environment($name, $product_id);
+
+ my $dbh = Bugzilla->dbh;
+ $dbh->do("INSERT INTO test_environments ($columns)
+ VALUES (?,?,?,?)", undef,
+ (undef,$product_id,$name,1));
+ return $dbh->bz_last_key( 'test_environments', 'environment_id' );
}
=head2 update
@@ -190,12 +464,18 @@ sub update {
my $self = shift;
my ($newvalues) = @_;
my $dbh = Bugzilla->dbh;
+ my $product_id;
$dbh->bz_lock_tables('test_environments WRITE');
foreach my $field (keys %{$newvalues}){
if ($self->{$field} ne $newvalues->{$field}){
# If the new name is already in use, return.
- if ($field eq 'name' && $self->check_name($newvalues->{'name'})) {
+ $product_id = $newvalues->{'product_id'} || $self->{'product_id'};
+ if ($product_id eq undef) {
+ $dbh->bz_unlock_tables;
+ return 0;
+ }
+ if ($field eq 'name' && $self->check_environment($newvalues->{'name'}, $product_id)) {
$dbh->bz_unlock_tables;
return 0;
}
@@ -203,15 +483,86 @@ sub update {
$dbh->do("UPDATE test_environments
SET $field = ? WHERE environment_id = ?",
undef, $newvalues->{$field}, $self->{'environment_id'});
- $self->{$field} = $newvalues->{$field};
-
+ $self->{$field} = $newvalues->{$field};
}
}
$dbh->bz_unlock_tables;
+ my $elements = $self->{'elements'};
+
+ foreach my $element (@$elements)
+ {
+ $self->persist_environment_element_and_children(1, $element, "update");
+ }
+
return 1;
}
+
+=head2 persist_environment_element_and_children
+
+Persists Environment Element and Children Recursively.
+
+=cut
+
+sub persist_environment_element_and_children {
+ my $self = shift;
+ my ($depth, $element, $method) = @_;
+ if ($depth > $max_depth) {
+ return;
+ }
+ $depth++;
+ my $elem_id = $element->{'element_id'};
+ my $properties = $element->{'properties'};
+ foreach my $property (@$properties)
+ {
+ my $prop_id = $property->{'property_id'};
+ my $value_selected = $property->{'value_selected'};
+ my ($value_stored) = $self->get_value_selected($self->{'environment_id'},$prop_id,$elem_id);
+ if ($method eq "store" || $value_stored eq undef) {
+ $self->store_property_value($prop_id,$elem_id,$value_selected);
+ }
+ else {
+ $self->update_property_value($prop_id,$elem_id,$value_selected);
+ }
+ }
+ my $children = $element->{'children'};
+ foreach my $child_element (@$children) {
+ $self->persist_environment_element_and_children($depth, $child_element, $method);
+ }
+}
+
+
+=head2 update property value
+
+Updates the property of the element in the database
+
+=cut
+
+sub update_property_value {
+
+ my $timestamp = Bugzilla::Testopia::Util::get_time_stamp();
+ my $self = shift;
+ my ($propID, $elemID, $valueSelected) = (@_);
+
+ my $dbh = Bugzilla->dbh;
+ $dbh->do("UPDATE test_environment_map
+ SET value_selected = ? WHERE environment_id = ? AND property_id = ? AND element_id = ?"
+ ,undef, ($valueSelected,$self->{'environment_id'},$propID,$elemID));
+ return 1;
+}
+
+sub delete_element {
+ my $self = shift;
+ my ($element_id) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->do("DELETE FROM test_environment_map
+ WHERE environment_id = ? AND element_id = ?",
+ undef,($self->id, $element_id));
+
+}
+
=head2 obliterate
Completely removes this environment from the database.
@@ -222,19 +573,23 @@ sub obliterate {
my $self = shift;
my $dbh = Bugzilla->dbh;
- $dbh->bz_lock_tables('test_runs READ', 'test_environments WRITE');
+ $dbh->bz_lock_tables('test_runs READ', 'test_environments WRITE','test_environment_map WRITE');
my $used = $dbh->selectrow_array("SELECT 1 FROM test_runs
- WHERE environment_id = ?",
+ WHERE environment_id = ?",
undef, $self->{'environment_id'});
if ($used) {
$dbh->bz_unlock_tables;
ThrowUserError("testopia-non-zero-run-count", {'object' => 'Environment'});
}
- $dbh->do("DELETE FROM test_environments
+ $dbh->do("UPDATE test_environments SET isactive = 0
WHERE environment_id = ?", undef, $self->{'environment_id'});
+
$dbh->bz_unlock_tables;
+
+ return 1;
}
+
=head2 get_run_list
Returns a list of run ids associated with this environment.
@@ -245,7 +600,7 @@ sub get_run_list {
my $self = shift;
my $dbh = Bugzilla->dbh;
my $ref = $dbh->selectcol_arrayref("SELECT run_id FROM test_runs
- WHERE environment_id = ?",
+ WHERE environment_id = ?",
undef, $self->{'environment_id'});
return join(",", @{$ref});
}
@@ -260,29 +615,11 @@ sub get_run_count {
my $self = shift;
my $dbh = Bugzilla->dbh;
my ($count) = $dbh->selectrow_array("SELECT COUNT(run_id) FROM test_runs
- WHERE environment_id = ?",
+ WHERE environment_id = ?",
undef, $self->{'environment_id'});
return $count;
}
-=head2 check_name
-
-Returns true if an environment of the specified name exists
-in the database.
-
-=cut
-
-sub check_name {
- my $self = shift;
- my ($name) = @_;
- my $dbh = Bugzilla->dbh;
- my ($used) = $dbh->selectrow_array("SELECT 1
- FROM test_environments
- WHERE name = ?",
- undef, $name);
- return $used;
-}
-
=head2 canedit
Returns true if the logged in user has rights to edit this environment.
@@ -333,57 +670,32 @@ Returns the ID of this object
Returns the name of this object
-=head2 xml
-
-Returnst the xml associated with this environment
-
=cut
sub id { return $_[0]->{'environment_id'}; }
+sub product_id { return $_[0]->{'product_id'}; }
+sub isactive { return $_[0]->{'isactive'}; }
sub name { return $_[0]->{'name'}; }
-sub xml { return $_[0]->{'xml'}; }
-=head2 op_sys
+=head2 product
-Returns the bugzilla op_sys value
+Returns the bugzilla product
=cut
-sub op_sys {
+sub product {
my $self = shift;
my $dbh = Bugzilla->dbh;
- return $self->{'op_sys'} if exists $self->{'op_sys'};
- my ($res) = $dbh->selectrow_array("SELECT value
- FROM op_sys
- WHERE id = ?",
- undef, $self->{'op_sys_id'});
- $self->{'op_sys'} = $res;
- return $self->{'op_sys'};
-
-}
-
-=head2 rep_platform
-
-Returns the bugzilla rep_platform value
-
-=cut
-
-sub rep_platform {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
- return $self->{'rep_platform'} if exists $self->{'rep_platform'};
- my ($res) = $dbh->selectrow_array("SELECT value
- FROM rep_platform
- WHERE id = ?",
- undef, $self->{'rep_platform_id'});
- $self->{'rep_platform'} = $res;
- return $self->{'rep_platform'};
+ return $self->{'product'} if exists $self->{'product'};
+
+ $self->{'product'} = Bugzilla::Product->new($self->{'product_id'});
+ return $self->{'product'};
}
=head1 TODO
-Use Bugzilla methods for op_sys and rep_platforms in 2.22
+
=head1 SEE ALSO
diff --git a/mozilla/webtools/testopia/Bugzilla/Testopia/Environment/Category.pm b/mozilla/webtools/testopia/Bugzilla/Testopia/Environment/Category.pm
new file mode 100644
index 00000000000..2263a51ebf0
--- /dev/null
+++ b/mozilla/webtools/testopia/Bugzilla/Testopia/Environment/Category.pm
@@ -0,0 +1,543 @@
+# -*- 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 Test Runner System.
+#
+# The Initial Developer of the Original Code is Maciej Maczynski.
+# Portions created by Maciej Maczynski are Copyright (C) 2001
+# Maciej Maczynski. All Rights Reserved.
+#
+# Contributor(s): Greg Hendricks
+# Michael Hight
+# Garrett Braden
+
+=head1 NAME
+
+Bugzilla::Testopia::Environment::Category - A test element category
+
+=head1 DESCRIPTION
+
+Categories are used to organize environment elements.
+
+=head1 SYNOPSIS
+
+ $prop = Bugzilla::Testopia::Environment::Category->new($env_category_id);
+ $prop = Bugzilla::Testopia::Environment::Category->new(\%cat_hash);
+
+=cut
+
+package Bugzilla::Testopia::Environment::Category;
+
+use strict;
+
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::Config;
+use Bugzilla::User;
+use Bugzilla::Constants;
+
+###############################
+#### Initialization ####
+###############################
+
+=head1 FIELDS
+
+ env_category_id
+ product_id
+ name
+
+=cut
+
+use constant DB_COLUMNS => qw(
+ env_category_id
+ product_id
+ name
+ );
+
+our $columns = join(", ", DB_COLUMNS);
+
+###############################
+#### Methods ####
+###############################
+
+=head1 METHODS
+
+=head2 new
+
+Instantiates a new Category object
+
+=cut
+
+sub new {
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my $self = {};
+ bless($self, $class);
+ return $self->_init(@_);
+}
+
+=head2 _init
+
+Private constructor for the category class
+
+=cut
+
+sub _init {
+ my $self = shift;
+ my ($param) = (@_);
+ my $dbh = Bugzilla->dbh;
+
+ my $id = $param unless (ref $param eq 'HASH');
+ my $obj;
+
+ if (defined $id && detaint_natural($id)) {
+
+ $obj = $dbh->selectrow_hashref(qq{
+ SELECT $columns
+ FROM test_environment_category
+ WHERE env_category_id = ?}, undef, $id);
+ } elsif (ref $param eq 'HASH'){
+ $obj = $param;
+ }
+
+ return undef unless (defined $obj);
+
+ foreach my $field (keys %$obj) {
+ $self->{$field} = $obj->{$field};
+ }
+ return $self;
+}
+
+
+=head2 get element list by category
+
+Returns an array of element objects for a category
+
+=cut
+
+sub get_elements_by_category{
+ my $self = shift;
+
+ my $dbh = Bugzilla->dbh;
+
+ my $ref = $dbh->selectcol_arrayref(qq{
+ SELECT element_id
+ FROM test_environment_element
+ WHERE env_category_id = ?}, undef, $self->{'env_category_id'});
+
+ my @elements;
+
+ foreach my $val (@$ref){
+ push @elements, Bugzilla::Testopia::Environment::Element->new($val);
+ };
+
+ return \@elements;
+}
+
+=head2 get_parent_elements
+
+Returns an array of parent elements by category
+
+=cut
+
+sub get_parent_elements{
+ my $self = shift;
+
+ my $dbh = Bugzilla->dbh;
+
+################## original
+# Matches null for parent id
+#
+# my $ref = $dbh->selectcol_arrayref(qq{
+# SELECT element_id
+# FROM test_environment_element
+# WHERE env_category_id = ? AND parent_id is null }, undef, $self->{'env_category_id'});
+#
+############### end original delete when ready
+
+
+################## edited original
+# Matches 0 for parent id
+#
+# my $ref = $dbh->selectcol_arrayref(qq{
+# SELECT element_id
+# FROM test_environment_element
+# WHERE env_category_id = ? AND parent_id = 0 }, undef, $self->{'env_category_id'});
+#
+############### end edited original keep this one when ready
+
+
+########### temp fix to address issues with 0 or null in test_envrionment_element parent_id column
+##### matching null OR 0
+ my $ref = $dbh->selectcol_arrayref(qq{
+ SELECT element_id
+ FROM test_environment_element
+ WHERE env_category_id = ? AND (parent_id is null or parent_id = 0) }, undef, $self->{'env_category_id'});
+########### end temp fix.... delete when ready
+
+ my @elements;
+
+ foreach my $val (@$ref){
+ push @elements, Bugzilla::Testopia::Environment::Element->new($val);
+ };
+
+ return \@elements;
+}
+
+=head2 check_for_elements
+
+Returns 1 if a category has any elements
+
+=cut
+
+sub check_for_elements{
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ my $ref = $dbh->selectrow_array(qq{
+ SELECT 1
+ FROM test_environment_element
+ WHERE env_category_id = ?}, undef, $self->{'env_category_id'});
+
+ return $ref;
+}
+
+=head2 get_product_list
+
+Returns the product_id, product name, and count of categories
+
+=cut
+
+sub get_env_product_list{
+ my $self = shift;
+ my ($class_id) = @_;
+
+ my $dbh = Bugzilla->dbh;
+ my $query = "SELECT p.id, p.name, COUNT(tec.env_category_id) AS cat_count
+ FROM products p
+ LEFT JOIN group_control_map
+ ON group_control_map.product_id = p.id ";
+
+ if (Param('useentrygroupdefault')) {
+ $query .= "AND group_control_map.entry != 0 ";
+ } else {
+ $query .= "AND group_control_map.membercontrol = " .
+ CONTROLMAPMANDATORY . " ";
+ }
+ if (%{Bugzilla->user->groups}) {
+ $query .= "AND group_id NOT IN(" .
+ join(',', values(%{Bugzilla->user->groups})) . ") ";
+ }
+
+ $query .= "LEFT OUTER JOIN test_environment_category AS tec
+ ON p.id = tec.product_id ";
+ $query .= "WHERE group_id IS NULL ";
+ $query .= "AND classification_id = ? " if $class_id;
+ $query .= "GROUP BY p.id
+ ORDER BY p.name";
+
+
+ my $ref;
+ if($class_id){
+ $ref = $dbh->selectall_arrayref($query, {'Slice' => {}}, $class_id);
+ }
+ else{
+ $ref = $dbh->selectall_arrayref($query, {'Slice' => {}});
+ }
+ unshift @$ref, {'id' => 0, 'name' => '--ALL--', 'cat_count' => $self->get_all_child_count };
+ return $ref;
+
+}
+
+sub get_all_child_count {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ my ($all_count) = $dbh->selectrow_array(
+ "SELECT COUNT(*)
+ FROM test_environment_category
+ WHERE product_id = 0");
+
+ return $all_count;
+}
+
+sub product_categories_to_json {
+ my $self = shift;
+ my ($product_id, $disable_move) = @_;
+ detaint_natural($product_id);
+ $disable_move = ',"addChild","move"' if $disable_move;
+ my $json = "[";
+ foreach my $cat (@{$self->get_element_categories_by_product($product_id)}){
+ $json .= '{title:"' . $cat->name . '",';
+ $json .= 'isFolder:' . ($cat->check_for_elements() ? "true" : "false") . ',';
+ $json .= 'objectId:"' . $cat->id . '",';
+ $json .= 'widgetId:"category' . $cat->id . '",';
+ $json .= 'actionsDisabled:["addCategory","addProperty","addValue"';
+ $json .= $disable_move if $disable_move;
+ $json .= ',"remove"' unless $cat->candelete;
+ $json .= '],';
+ $json .= 'childIconSrc:"testopia/img/square.gif"},';
+ }
+ chop $json;
+ $json .= "]";
+ return $json;
+}
+
+=head2 get_element_categories_by_product
+
+Returns the list of element category names and ids by product id
+
+=cut
+
+sub get_element_categories_by_product{
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ my ($product_id) = (@_);
+
+ my $ref = $dbh->selectcol_arrayref(
+ "SELECT env_category_id
+ FROM test_environment_category
+ WHERE product_id = ?",
+ undef, $product_id);
+ my @objs;
+ foreach my $id (@{$ref}){
+ push @objs, Bugzilla::Testopia::Environment::Category->new($id);
+ }
+ return \@objs;
+}
+
+
+=head2 new_category_count
+
+Returns 1 if element has children
+
+=cut
+
+sub new_category_count{
+ my $self = shift;
+ my ($prod_id) = @_;
+ $prod_id ||= $self->{'product_id'};
+ my $dbh = Bugzilla->dbh;
+
+ my ($used) = $dbh->selectrow_array(
+ "SELECT COUNT(*)
+ FROM test_environment_category
+ WHERE name like 'New category%'
+ AND product_id = ?",
+ undef, $prod_id);
+
+ return $used + 1;
+}
+
+sub elements_to_json {
+ my $self = shift;
+ my ($disable_add) = @_;
+ $disable_add = ',"addChild"' if $disable_add;
+
+ my $elements = $self->get_parent_elements;
+ my $json = '[';
+
+ foreach my $element (@$elements)
+ {
+ $json .= '{title:"'. $element->{'name'} .'",';
+ $json .= 'objectId:"'. $element->{'element_id'}. '",';
+ $json .= 'widgetId:"element'. $element->{'element_id'} .'",';
+ $json .= 'actionsDisabled:["addCategory","addValue"';
+ $json .= $disable_add if $disable_add;
+ $json .= ',"remove"' unless $element->candelete;
+ $json .= '],';
+ $json .= 'isFolder:true,' if($element->check_for_children || $element->check_for_properties);
+ $json .= 'childIconSrc:"testopia/img/circle.gif"},';
+ }
+ chop $json;
+ $json .= ']';
+
+ return $json;
+}
+
+=head2 check_category
+
+Returns category id if a category exists
+
+=cut
+
+sub check_category{
+ my $dbh = Bugzilla->dbh;
+ my $self = shift;
+ my ($name, $prodID) = (@_);
+
+ $prodID ||= $self->product_id;
+
+ my ($used) = $dbh->selectrow_array(
+ "SELECT env_category_id
+ FROM test_environment_category
+ WHERE name = ? AND product_id = ?",
+ undef,($name,$prodID));
+
+ return $used;
+}
+
+=head2 check_category_by_id
+
+Returns category name if a category id exists
+
+=cut
+
+sub check_category_by_id{
+ my $dbh = Bugzilla->dbh;
+ my $self = shift;
+ my ($id) = (@_);
+
+ my ($used) = $dbh->selectrow_arrayref(qq{
+ SELECT name
+ FROM test_environment_category
+ WHERE env_category_id = ?},undef,$id);
+
+ return $used;
+}
+
+
+=head2 store
+
+Serializes this category to the database and returns the key or 0
+
+=cut
+
+sub store {
+ my $self = shift;
+ my $timestamp = Bugzilla::Testopia::Util::get_time_stamp();
+
+ return 0 if $self->check_category($self->{'name'},$self->{'product_id'});
+
+ my $dbh = Bugzilla->dbh;
+ $dbh->do("INSERT INTO test_environment_category ($columns)
+ VALUES (?,?,?)",undef, (undef, $self->{'product_id'},$self->{'name'}));
+ my $key = $dbh->bz_last_key( 'test_environment_category', 'env_category_id' );
+
+ return $key;
+}
+
+=head2 set_name
+
+Updates the category name in the database
+
+=cut
+
+sub set_name {
+ my $self = shift;
+ my ($name) = (@_);
+ my $dbh = Bugzilla->dbh;
+
+ return undef if $self->check_category($name);
+
+ $dbh->do("UPDATE test_environment_category SET name = ?
+ WHERE env_category_id = ? AND product_id = ?",
+ undef, ($name, $self->{'env_category_id'},$self->{'product_id'}));
+ return 1;
+}
+
+=head2 set_product
+
+Updates the category in the database
+
+=cut
+
+sub set_product {
+ my $self = shift;
+ my ($product_id) = (@_);
+
+ return if ($product_id == $self->{'product_id'});
+
+ my $dbh = Bugzilla->dbh;
+ $dbh->do("UPDATE test_environment_category SET product_id = ?
+ WHERE env_category_id = ? AND product_id = ?",
+ undef, ($product_id, $self->{'env_category_id'},$self->{'product_id'}));
+ return 1;
+}
+
+=head2 obliterate
+
+Completely removes the element category entry from the database.
+
+=cut
+
+sub obliterate {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->bz_lock_tables('test_environment_element AS tee READ', 'test_environment_map AS tem READ', 'test_environment_category WRITE');
+
+ if (!$self->candelete) {
+ $dbh->bz_unlock_tables;
+ return 0;
+ }
+ $dbh->do("DELETE FROM test_environment_category
+ WHERE env_category_id = ?", undef, $self->id);
+ $dbh->bz_unlock_tables;
+
+ return 1;
+
+}
+
+sub candelete {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ my $used = $dbh->selectrow_array(
+ "SELECT 1 FROM test_environment_map AS tem
+ JOIN test_environment_element AS tee ON tee.element_id = tem.element_id
+ WHERE tee.env_category_id = ?",
+ undef, $self->id);
+ return !$used;
+
+}
+
+###############################
+#### Accessors ####
+###############################
+=head2 id
+
+Returns the ID of this category
+
+=head2 name
+
+Returns the name of this category
+
+=head2 product_id
+
+Returns the product_id of this category
+
+=cut
+
+sub id { return $_[0]->{'env_category_id'}; }
+sub name { return $_[0]->{'name'}; }
+sub product_id { return $_[0]->{'product_id'}; }
+
+
+=head2 product_name
+
+Returns the name of the product this plan is associated with
+
+=cut
+
+sub product_name {
+ my ($self) = @_;
+ my $dbh = Bugzilla->dbh;
+ return $self->{'product_name'} if exists $self->{'product_name'};
+
+ my ($name) = $dbh->selectrow_array(
+ "SELECT name FROM products
+ WHERE id = ?",
+ undef, $self->id);
+ $self->{'product_name'} = $name;
+ return $self->{'product_name'};
+}
+
+1;
diff --git a/mozilla/webtools/testopia/Bugzilla/Testopia/Environment/Element.pm b/mozilla/webtools/testopia/Bugzilla/Testopia/Environment/Element.pm
new file mode 100644
index 00000000000..6d7c9db6757
--- /dev/null
+++ b/mozilla/webtools/testopia/Bugzilla/Testopia/Environment/Element.pm
@@ -0,0 +1,471 @@
+# -*- 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 Test Runner System.
+#
+# The Initial Developer of the Original Code is Maciej Maczynski.
+# Portions created by Maciej Maczynski are Copyright (C) 2001
+# Maciej Maczynski. All Rights Reserved.
+#
+# Contributor(s): Greg Hendricks
+# Michael Hight
+# Garrett Braden
+
+=head1 NAME
+
+Bugzilla::Testopia::Environment::Element - A test environment element
+
+=head1 DESCRIPTION
+
+Environment elements are a set of environment entities that dictate
+the conditions a test was conducted in. Each environment must have an
+element. Elements can be very simple or very complex by adding properties.
+Elements can have child elements.
+
+=head1 SYNOPSIS
+
+ $elem = Bugzilla::Testopia::Environment::Element->new($elem_id);
+ $elem = Bugzilla::Testopia::Environment::Element->new(\%elem_hash);
+
+=cut
+
+package Bugzilla::Testopia::Environment::Element;
+
+use strict;
+
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::User;
+use JSON;
+
+###############################
+#### Initialization ####
+###############################
+
+=head1 FIELDS
+
+ element_id
+ env_category_id
+ name
+ parent_id
+ isprivate
+
+=cut
+
+use constant DB_COLUMNS => qw(
+ element_id
+ env_category_id
+ name
+ parent_id
+ isprivate
+);
+
+our $columns = join(", ", DB_COLUMNS);
+
+our constant $max_depth = 5;
+
+###############################
+#### Methods ####
+###############################
+
+=head1 METHODS
+
+=head2 new
+
+Instantiates a new Element object
+
+=cut
+
+sub new {
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my $self = {};
+ bless($self, $class);
+ return $self->_init(@_);
+}
+
+=head2 _init
+
+Private constructor for the element class
+
+=cut
+
+sub _init {
+ my $self = shift;
+ my ($param) = (@_);
+ my $dbh = Bugzilla->dbh;
+
+ my $id = $param unless (ref $param eq 'HASH');
+ my $obj;
+
+ if (defined $id && detaint_natural($id)) {
+
+ $obj = $dbh->selectrow_hashref(
+ "SELECT $columns
+ FROM test_environment_element
+ WHERE element_id = ?", undef, $id);
+ } elsif (ref $param eq 'HASH'){
+ $obj = $param;
+ }
+
+ return undef unless (defined $obj);
+
+ foreach my $field (keys %$obj) {
+ $self->{$field} = $obj->{$field};
+ }
+
+ $self->get_properties();
+
+ return $self;
+}
+
+=head2 get_children
+
+Returns an array of the children objects for an element and recursively
+gets all of their children until exhausted.
+
+=cut
+
+sub get_children{
+ my $dbh = Bugzilla->dbh;
+ my $self = shift;
+
+ return $self->{'children'} if exists $self->{'children'};
+
+ my %newvalues = (@_);
+ my $depth = $max_depth;
+
+ if(%newvalues)
+ {
+ $depth = $newvalues{'depth'};
+ }
+
+ $depth--;
+
+ if($depth == 0)
+ {return;}
+
+ my $id = $self->{'element_id'};
+
+ my $ref = $dbh->selectcol_arrayref(qq{
+ SELECT tee.element_id
+ FROM test_environment_element as tee
+ WHERE tee.parent_id = ?},undef,$id);
+
+ my @children;
+
+ foreach my $val (@$ref){
+ my $child = Bugzilla::Testopia::Environment::Element->new($val);
+ $child->get_children('depth'=>$depth);
+ push(@children,$child);
+ }
+
+ $self->{'children'} = \@children;
+
+}
+
+=head2 get_properties
+
+Returns an array of the property objects for an element.
+
+=cut
+
+sub get_properties{
+ my $dbh = Bugzilla->dbh;
+ my $self = shift;
+
+ my $ref = $dbh->selectcol_arrayref(qq{
+ SELECT tep.property_id
+ FROM test_environment_property as tep
+ WHERE tep.element_id = ?},undef,($self->{'element_id'}));
+
+ my @properties;
+
+ foreach my $val (@$ref){
+ my $property = Bugzilla::Testopia::Environment::Property->new($val);
+ push(@properties,$property);
+ }
+
+ $self->{'properties'} = \@properties;
+ return $self->{'properties'};
+
+}
+
+=head2 check_element
+
+Returns element id if element exists
+
+=cut
+
+sub check_element{
+ my $dbh = Bugzilla->dbh;
+ my $self = shift;
+ my ($name, $cat_id) = @_;
+
+ # Since categories are uniquely identified by product_id we don't have to check by join on the product_id.
+ my ($used) = $dbh->selectrow_array(
+ "SELECT element_id
+ FROM test_environment_element
+ WHERE name = ?
+ AND env_category_id = ?",
+ undef,($name,$cat_id));
+
+ return $used;
+}
+
+=head2 check_for_children
+
+Returns 1 if element has children
+
+=cut
+
+sub check_for_children{
+ my $dbh = Bugzilla->dbh;
+ my $self = shift;
+
+ my ($used) = $dbh->selectrow_array(qq{
+ SELECT 1
+ FROM test_environment_element
+ WHERE parent_id = ? },undef,$self->{'element_id'});
+
+ return $used;
+}
+
+sub children_to_json{
+ my $self = shift;
+ my ($disable_move) = @_;
+
+ $disable_move = ',"addChild","move","remove"' if $disable_move;
+ my $disable_add = ',"addChild"' if $disable_move;
+ my $elements = $self->get_children;
+ my $properties = $self->get_properties;
+ my $json = '[';
+
+ foreach my $elem (@$elements)
+ {
+ $json .= '{title:"' . $elem->{'name'} . '",';
+ $json .= 'objectId:"' . $elem->{'element_id'} . '",';
+ $json .= 'widgetId:"element' . $elem->{'element_id'} . '",';
+ $json .= 'actionsDisabled:["addCategory","addValue"';
+ $json .= $disable_add if $disable_add;
+ $json .=',"remove"' unless $elem->candelete;
+ $json .= '],';
+ $json .= 'isFolder:true,' if $elem->check_for_children();
+ $json .= 'childIconSrc:"testopia/img/circle.gif"},';
+ }
+ foreach my $prop (@$properties)
+ {
+ $json .= '{title: "' . $prop->{'name'} .'",';
+ $json .= 'objectId:"' . $prop->{'property_id'} . '",';
+ $json .= 'widgetId:"property' . $prop->{'property_id'} . '",';
+ $json .= 'actionsDisabled:["addCategory","addElement","addProperty"';
+ $json .= $disable_move if $disable_move;
+ $json .= ',"remove"' unless $prop->candelete;
+ $json .= '],';
+ $json .= 'isFolder:true,' if($prop->check_for_validexp($prop));
+ $json .= 'childIconSrc:"testopia/img/triangle.gif"},';
+ }
+ chop $json if ($json ne '[');
+ $json .= ']';
+
+ return $json;
+}
+
+=head2 new_element_count
+
+Returns 1 if element has children
+
+=cut
+
+sub new_element_count{
+ my $self = shift;
+ my ($cat_id) = @_;
+ $cat_id ||= $self->{'env_category_id'};
+ my $dbh = Bugzilla->dbh;
+
+ my ($used) = $dbh->selectrow_array(
+ "SELECT COUNT(*)
+ FROM test_environment_element
+ WHERE name like 'New element%'
+ AND env_category_id = ?",
+ undef, $cat_id);
+
+ return $used + 1;
+}
+
+=head2 check_for_properties
+
+Returns 1 if element has properties
+
+=cut
+
+sub check_for_properties{
+ my $dbh = Bugzilla->dbh;
+ my $self = shift;
+
+ my ($used) = $dbh->selectrow_array(qq{
+ SELECT 1
+ FROM test_environment_property
+ WHERE element_id = ? },undef,$self->{'element_id'});
+
+ return $used;
+}
+
+
+=head2 store
+
+Serializes the new element to the database and returns the primary key or 0
+
+=cut
+
+sub store {
+ my $self = shift;
+ my $timestamp = Bugzilla::Testopia::Util::get_time_stamp();
+
+ # Verify name is available
+ return undef if $self->check_element($self->{'name'},$self->{'env_category_id'});
+
+ my $dbh = Bugzilla->dbh;
+ $dbh->do("INSERT INTO test_environment_element ($columns)
+ VALUES (?,?,?,?,?)",
+ undef, (undef, $self->{'env_category_id'}, $self->{'name'},
+ $self->{'parent_id'},$self->{'isprivate'}));
+ my $key = $dbh->bz_last_key('test_environment_element', 'element_id');
+ return $key;
+}
+
+
+
+=head2 update_element_name
+
+Updates the element in the database
+
+=cut
+
+sub update_element_name {
+ my $self = shift;
+ my $timestamp = Bugzilla::Testopia::Util::get_time_stamp();
+
+ my ($name) = (@_);
+
+ return 0 if check_element($name, $self->{'env_category_id'});
+
+ my $dbh = Bugzilla->dbh;
+ $dbh->do("UPDATE test_environment_element
+ SET name = ? WHERE element_id = ?",undef, ($name,$self->{'element_id'} ));
+ return 1;
+}
+
+=head2 update_element_category
+
+Updates the category of the element in the database
+
+=cut
+
+sub update_element_category {
+ my $self = shift;
+ my $timestamp = Bugzilla::Testopia::Util::get_time_stamp();
+
+ my ($catid) = (@_);
+
+ my $dbh = Bugzilla->dbh;
+ $dbh->do("UPDATE test_environment_element
+ SET env_category_id = ? WHERE element_id = ?",undef, ($catid,$self->{'element_id'} ));
+ return 1;
+}
+
+
+=head2 update_element_parent
+
+Updates the parent_id of the element in the database
+
+=cut
+
+sub update_element_parent {
+ my $self = shift;
+ my $timestamp = Bugzilla::Testopia::Util::get_time_stamp();
+
+ my ($parent_id) = (@_);
+
+ my $dbh = Bugzilla->dbh;
+ $dbh->do("UPDATE test_environment_element
+ SET parent_id = ? WHERE element_id = ?",undef, ($parent_id,$self->{'element_id'} ));
+ return 1;
+}
+
+=head2 obliterate
+
+Completely removes the element entry from the database.
+
+=cut
+
+sub obliterate {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->bz_lock_tables('test_environment_map READ', 'test_environment_element WRITE');
+ if (!$self->candelete) {
+ $dbh->bz_unlock_tables;
+ return 0;
+ }
+ $dbh->do("DELETE FROM test_environment_element
+ WHERE element_id = ?", undef, $self->{'element_id'});
+ $dbh->bz_unlock_tables;
+
+ return 1;
+}
+
+sub candelete {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ my $used = $dbh->selectrow_array("SELECT 1 FROM test_environment_map
+ WHERE element_id = ?",
+ undef, $self->id);
+ return !$used;
+
+}
+
+###############################
+#### Accessors ####
+###############################
+=head2 id
+
+Returns the ID of this object
+
+=head2 name
+
+Returns the name of this object
+
+=head2 product_id
+
+Returns the product_id of this object
+
+=head2 env_category_id
+
+Returns the category_id associated with this element
+
+=head2 parent_id
+
+Returns the element's parent_id associated with this element
+
+
+=cut
+
+sub id { return $_[0]->{'element_id'}; }
+sub name { return $_[0]->{'name'}; }
+sub product_id { return $_[0]->{'product_id'}; }
+sub env_category_id { return $_[0]->{'env_category_id'}; }
+sub parent_id { return $_[0]->{'parent_id'}; }
+sub isprivate { return $_[0]->{'isprivate'}; }
+sub get_parent { return $_[0]->new($_[0]->{'parent_id'}); }
+
+1;
diff --git a/mozilla/webtools/testopia/Bugzilla/Testopia/Environment/Property.pm b/mozilla/webtools/testopia/Bugzilla/Testopia/Environment/Property.pm
new file mode 100644
index 00000000000..e09718dd1bf
--- /dev/null
+++ b/mozilla/webtools/testopia/Bugzilla/Testopia/Environment/Property.pm
@@ -0,0 +1,372 @@
+# -*- 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 Test Runner System.
+#
+# The Initial Developer of the Original Code is Maciej Maczynski.
+# Portions created by Maciej Maczynski are Copyright (C) 2001
+# Maciej Maczynski. All Rights Reserved.
+#
+# Contributor(s): Greg Hendricks
+# Michael Hight
+# Garrett Braden
+
+=head1 NAME
+
+Bugzilla::Testopia::Environment::Property - A test environment element property
+
+=head1 DESCRIPTION
+
+Element properties describe the elements in more detail. An
+element can have an unlimited number of properties to describe it.
+The valid expression limits the possible descriptions to valid choices
+for each property.
+
+=head1 SYNOPSIS
+
+ $prop = Bugzilla::Testopia::Environment::Property->new($prop_id);
+ $prop = Bugzilla::Testopia::Environment::Property->new(\%prop_hash);
+
+=cut
+
+package Bugzilla::Testopia::Environment::Property;
+
+use strict;
+
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::User;
+###############################
+#### Initialization ####
+###############################
+
+=head1 FIELDS
+
+ property_id
+ element_id
+ name
+ validexp
+
+=cut
+
+use constant DB_COLUMNS => qw(
+ property_id
+ element_id
+ name
+ validexp
+ );
+
+our $columns = join(", ", DB_COLUMNS);
+
+###############################
+#### Methods ####
+###############################
+
+=head1 METHODS
+
+=head2 new
+
+Instantiates a new Property object
+
+=cut
+
+sub new {
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my $self = {};
+ bless($self, $class);
+ return $self->_init(@_);
+}
+
+=head2 _init
+
+Private constructor for the property class
+
+=cut
+
+sub _init {
+ my $self = shift;
+ my ($param) = (@_);
+ my $dbh = Bugzilla->dbh;
+
+ my $id = $param unless (ref $param eq 'HASH');
+ my $obj;
+
+ if (defined $id && detaint_natural($id)) {
+
+ $obj = $dbh->selectrow_hashref(qq{
+ SELECT $columns
+ FROM test_environment_property
+ WHERE property_id = ?}, undef, $id);
+ } elsif (ref $param eq 'HASH'){
+ $obj = $param;
+ }
+
+ return undef unless (defined $obj);
+
+ foreach my $field (keys %$obj) {
+ $self->{$field} = $obj->{$field};
+ }
+ return $self;
+}
+
+
+=head2 check_property
+
+Returns id if a property exists
+
+=cut
+
+sub check_property{
+ my $self = shift;
+ my ($name, $element_id) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ if ($name eq undef || $name eq '' || $element_id eq undef) {
+ return "check_product must be passed a valid name and product_id";
+ }
+
+ my ($used) = $dbh->selectrow_array(qq{
+ SELECT property_id
+ FROM test_environment_property
+ WHERE name = ? AND element_id = ?},undef,$name,$element_id);
+
+ return $used;
+}
+
+=head2 check_for_validexp
+
+Returns 1 if a validexp exist for the property
+
+=cut
+
+sub check_for_validexp{
+ my $self = shift;
+ my $name = (@_);
+ my $dbh = Bugzilla->dbh;
+
+ my ($used) = $dbh->selectrow_array(qq{
+ SELECT 1
+ FROM test_environment_property
+ WHERE property_id = ? AND (validexp IS NOT NULL AND validexp <> '')},undef,$self->{'property_id'});
+
+ return $used;
+}
+
+=head2 new_property_count
+
+Returns the count + 1 of new properties
+
+=cut
+
+sub new_property_count{
+ my $self = shift;
+ my ($element_id) = @_;
+ $element_id ||= $self->{'element_id'};
+ my $dbh = Bugzilla->dbh;
+
+ my ($used) = $dbh->selectrow_array(
+ "SELECT COUNT(*)
+ FROM test_environment_property
+ WHERE name like 'New property%'
+ AND element_id = ?",
+ undef, $element_id);
+
+ return $used + 1;
+}
+
+=head2 get_validexp
+
+Returns the validexp for the property
+
+=cut
+
+sub get_validexp{
+ my $self = shift;
+ my $name = (@_);
+ my $dbh = Bugzilla->dbh;
+
+ my $validexp = $dbh->selectrow_arrayref(qq{
+ SELECT validexp
+ FROM test_environment_property
+ WHERE property_id = ? },undef,$self->{'property_id'});
+
+ return $validexp;
+}
+
+
+=head2 store
+
+Serializes the new property to the database
+
+=cut
+
+sub store {
+ my $self = shift;
+ my $timestamp = Bugzilla::Testopia::Util::get_time_stamp();
+
+ # Verify name is available
+ return undef if $self->check_property($self->{'name'}, $self->{'element_id'});
+
+ my $dbh = Bugzilla->dbh;
+ $dbh->do("INSERT INTO test_environment_property ($columns)
+ VALUES (?,?,?,?)",undef, (undef, $self->{'element_id'}, $self->{'name'}, $self->{'validexp'}));
+ my $key = $dbh->bz_last_key( 'test_plans', 'plan_id' );
+ return $key;
+}
+
+=head2 set_name
+
+Updates the property name in the database
+
+=cut
+
+sub set_name {
+ my $self = shift;
+ my $timestamp = Bugzilla::Testopia::Util::get_time_stamp();
+
+ my ($name) = (@_);
+
+ my $dbh = Bugzilla->dbh;
+ $dbh->do("UPDATE test_environment_property
+ SET name = ? WHERE property_id = ?",undef, ($name,$self->{'property_id'} ));
+ return 1;
+}
+
+
+=head2 set_element
+
+Updates the elmnt_id in the database
+
+=cut
+
+sub set_element {
+ my $self = shift;
+ my $timestamp = Bugzilla::Testopia::Util::get_time_stamp();
+
+ my ($id) = (@_);
+
+ my $dbh = Bugzilla->dbh;
+ $dbh->do("UPDATE test_environment_property
+ SET element_id = ? WHERE property_id = ?",undef, ($id,$self->{'property_id'} ));
+ return 1;
+}
+
+=head2 update_property_validexp
+
+Updates the property valid expression in the database
+
+=cut
+
+sub update_property_validexp {
+ my $timestamp = Bugzilla::Testopia::Util::get_time_stamp();
+ my $self = shift;
+ my ($validexp) = (@_);
+
+ my $dbh = Bugzilla->dbh;
+ $dbh->do("UPDATE test_environment_property
+ SET validexp = ? WHERE property_id = ?",undef, ($validexp,$self->{'property_id'} ));
+ return 1;
+}
+
+sub valid_exp_to_json {
+ my $self = shift;
+ my ($disable_move, $env_id) = @_;
+ my $env = Bugzilla::Testopia::Environment->new($env_id) if $env_id;
+
+ $disable_move = ',"addChild","move","remove"' if $disable_move;
+ my $validexp = $self->get_validexp;
+
+ my @validexpressions = split(/\|/, @$validexp[0]);
+
+ my $json = '[';
+
+ foreach (@validexpressions)
+ {
+ $json .= '{title: "' . $_ . '",';
+ $json .= 'widgetId:"validexp' . $self->id . '~'. $_ .'",';
+ $json .= 'actionsDisabled:["addCategory","addElement","addProperty","addValue"'. $disable_move .'],';
+ $json .= 'objectId:"' . $self->id . '~' . $_ . '",';
+ if ($env && $env->get_value_selected($env->id, $self->element_id, $self->id) eq $_){
+ $json .= 'childIconSrc:"testopia/img/selected_value.png"},';
+ }
+ else{
+ $json .= 'childIconSrc:""},';
+ }
+ }
+ chop $json;
+ $json .= ']';
+
+ return $json;
+}
+
+=head2 obliterate
+
+Completely removes the element property entry from the database.
+
+=cut
+
+sub obliterate {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->bz_lock_tables('test_environment_map READ', 'test_environment_property WRITE');
+ if (!$self->candelete) {
+ $dbh->bz_unlock_tables;
+ return 0;
+ }
+ $dbh->do("DELETE FROM test_environment_property
+ WHERE property_id = ?", undef, $self->id);
+ $dbh->bz_unlock_tables;
+
+ return 1;
+
+}
+
+sub candelete {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ my $used = $dbh->selectrow_array("SELECT 1 FROM test_environment_map
+ WHERE property_id = ?",
+ undef, $self->id);
+ return !$used;
+
+}
+
+###############################
+#### Accessors ####
+###############################
+=head2 id
+
+Returns the ID of this object
+
+=head2 name
+
+Returns the name of this object
+
+=head2 validexp
+
+Returns the valid expression associated with this property
+
+=head2 element_id
+
+Returns the element's id associated with this property
+
+
+=cut
+
+sub id { return $_[0]->{'property_id'}; }
+sub name { return $_[0]->{'name'}; }
+sub validexp { return $_[0]->{'validexp'}; }
+sub element_id { return $_[0]->{'element_id'}; }
+
+1;
\ No newline at end of file
diff --git a/mozilla/webtools/testopia/Bugzilla/Testopia/Environment/Xml.pm b/mozilla/webtools/testopia/Bugzilla/Testopia/Environment/Xml.pm
new file mode 100644
index 00000000000..3b89a7b655d
--- /dev/null
+++ b/mozilla/webtools/testopia/Bugzilla/Testopia/Environment/Xml.pm
@@ -0,0 +1,585 @@
+# -*- 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 Test Runner System.
+#
+# The Initial Developer of the Original Code is Maciej Maczynski.
+# Portions created by Maciej Maczynski are Copyright (C) 2001
+# Maciej Maczynski. All Rights Reserved.
+#
+# Contributor(s): Garrett Braden
+
+
+=head1 NAME
+
+Bugzilla::Testopia::Environment::Xml - An XML representation of the Environment Object.
+
+=head1 DESCRIPTION
+
+This module is used to import and export environments via XML. It can parse an XML representation
+of a Testopia Environment and persist it to the database. An Environment.pm XML object can be
+initialized using new and passing it an XML scalar. It can also take two other parameters:
+
+ $admin - a boolean that automatically will store the imported environment to the database.
+ $max_depth - the max depth of child elements to import.
+
+Example:
+ my $env_xml = Bugzilla::Testopia::Environment::Xml->new($xml, 1, 5);
+
+Other subroutines can be called on the object. For example:
+ parse - takes the same three parameters as new
+ store - stores the imported xml Environment object to the database
+
+Misc. Other:
+ the module also contains two other valueable fields on it's hash:
+ $self->{'message'} - running list of valueable information upon parsing and storing
+ $self->{'error'} - running list of error messages upon parsing and storing
+ One other method exists(check_new_items) to check if there are new Elements, Properties, Categories,
+ and Selected Values and returns a scalar value report of the new items not present in the database.
+
+Import XML Environment Implementation Example: see tr_import_environment.cgi
+
+To export an environment by env_id to XML use export
+
+Example:
+ my $xml = Bugzilla::Testopia::Environment::Xml->export($env_id);
+
+Export Environment XML Implementation Example: see tr_export_environment.cgi
+
+=head1 SYNOPSIS
+
+use Bugzilla::Testopia::Environment::Xml;
+
+=cut
+
+package Bugzilla::Testopia::Environment::Xml;
+
+#************************************************** Uses ****************************************************#
+use strict;
+use warnings;
+use CGI;
+use lib ".";
+use XML::Twig;
+use Bugzilla::Util;
+use Bugzilla::User;
+use Bugzilla::Config;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Product;
+use Bugzilla::Testopia::Util;
+use Bugzilla::Testopia::Environment;
+use Bugzilla::Testopia::Product;
+use Bugzilla::Testopia::Environment::Category;
+use Bugzilla::Testopia::Environment::Element;
+use Bugzilla::Testopia::Environment::Property;
+
+our constant $max_depth = 7;
+
+
+=head2 new
+
+Instantiates a new Bugzilla::Testopia::Environment::Xml object
+
+=cut
+
+sub new {
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my $self = {};
+ bless($self, $class);
+ return $self->_init(@_);
+}
+
+
+=head2 _init
+
+Private constructor for the Bugzilla::Testopia::Environment::XML class
+
+=cut
+
+sub _init {
+ my ($self, $xml, $admin, $depth) = @_;
+ if (ref $xml eq 'HASH') {
+ $self = $xml;
+ }
+ elsif($xml) {
+ parse($self, $xml, $admin, $depth);
+ }
+ return undef unless (defined $xml);
+ return $self;
+}
+
+
+=head2 Parse Environment
+
+=head2 DESCRIPTION
+
+Parses the Environment XML
+
+=cut
+
+sub parse() {
+ my ($self, $xml, $admin, $depth) = @_;
+
+ if($depth eq undef) {
+ $self->{'max_depth'} = $max_depth;
+ }
+ else {
+ $self->{'max_depth'} = $depth
+ }
+ if ($admin) {
+ $self->{'message'} = "Importing XML Environment... ";
+ }
+ else {
+ $self->{'message'} = "Parsing and Validating XML Environment... ";
+ }
+ $self->{'error'} = undef;
+ if ($xml) {
+ trick_taint($xml);
+ }
+ my $twig = XML::Twig->new();
+ $twig->parse($xml);
+ my $root = $twig->root;
+ # Checking if Product and Environment already exist.
+ my $product_name = $root->{'att'}->{'product'};
+ my $product_id;
+ if (lc($product_name) eq "--all--") {
+ $self->{'message'} .= "..Using the --ALL-- PRODUCT . ";
+ $product_id = 0;
+ }
+ else {
+ $self->{'message'} .= "..Checking if $product_name PRODUCT already exists...";
+ ($product_id) = Bugzilla::Testopia::Product->check_product_by_name($product_name);
+ if ($product_id) {
+ $self->{'message'} .= "EXISTS. ";
+ }
+ else {
+ $self->{'message'} .= "DOESN'T EXIST . Importing XML Environment Failed! ";
+ $self->{'error'} .= "$product_name PRODUCT doesn't exist. Please be sure to use an existing product. ";
+ return 0;
+ }
+ }
+ ($self->{'product_id'}) = $product_id;
+ $self->{'product_name'} = $product_name;
+ my $environment_name = $root->{'att'}->{'name'};
+ $self->{'name'} = $environment_name;
+ $self->{'message'} .= "..Checking if $environment_name ENVIRONMENT NAME already exists for the $product_name PRODUCT ...";
+ my $environment = Bugzilla::Testopia::Environment->new({});
+ my ($env_id) = $environment->check_environment($environment_name, $product_id);
+ my $environment_id;
+ if ($env_id < 1) {
+ $self->{'message'} .= "DOESN'T EXIST ";
+ # Storing New Environment if Admin
+ if ($admin) {
+ $self->{'message'} .= "....Storing new $environment_name ENVIRONMENT NAME in the $self->{'product_name'} PRODUCT ...";
+ $environment->{'name'} = $environment_name;
+ ($environment_id) = Bugzilla::Testopia::Environment->store_environment_name($self->{'name'}, $product_id);
+ $self->{'message'} .= "DONE. ";
+ }
+ }
+ else {
+ ($environment_id) = $env_id;
+ $self->{'message'} .= "EXISTS Importing XML Environment Failed! ";
+ $self->{'error'} .= "$environment_name ENVIRONMENT NAME already exists for the $product_name PRODUCT . Please use another name.";
+ return 0;
+ }
+ ($self->{'environment_id'}) = $environment_id;
+ ($environment->{'product_id'}) = $self->{'product_id'};
+ # Parse recursively through the nested child elements.
+ foreach my $twig_category ($root->children("category")) {
+ my $category_name = $twig_category->{'att'}->{'name'};
+ # Makes sure to get the category_id by name and product_id
+ my $category = Bugzilla::Testopia::Environment::Category->new({});
+ my ($cat_id) = $category->check_category($category_name, $product_id);
+ my $category_id;
+ # Checking if Categories already exist.
+ $self->{'message'} .= "..Checking if $category_name CATEGORY already exists...";
+ if ($cat_id < 1) {
+ $self->{'message'} .= "DOESN'T EXIST . ";
+ my $new_category_names = $self->{'new_category_names'};
+ push (@$new_category_names, $category_name);
+ $self->{'new_category_names'} = $new_category_names;
+ # Storing New Categories if Admin
+ if ($admin) {
+ $self->{'message'} .= "....Storing new $category_name CATEGORY in the $self->{'product_name'} PRODUCT ...";
+ ($category->{'product_id'}) = $product_id;
+ $category->{'name'} = $category_name;
+ ($category_id) = $category->store();
+ $self->{'message'} .= "DONE. ";
+ }
+ }
+ else {
+ ($category_id) = $cat_id;
+ $self->{'message'} .= "EXISTS. ";
+ }
+ foreach my $twig_element ($twig_category->children("element")) {
+ my $element = $self->parse_child_elements(1, $category_id, $category_name, $twig_element, $admin);
+ my $elements = $self->{'elements'};
+ push (@$elements, $element);
+ $self->{'elements'} = $elements;
+ }
+ }
+ if ($admin) {
+ $self->{'message'} .= "Finished Importing XML Environment! ";
+ }
+ else {
+ $self->{'message'} .= "Finished Parsing and Validating XML Environment! ";
+ }
+}
+
+
+=head2 Parse Children Elements
+
+=head2 DESCRIPTION
+
+Parses through elements and their children elements recursively
+
+=cut
+
+sub parse_child_elements() {
+ my ($self, $depth, $env_category_id, $category_name, $twig_element, $admin, $parent_element) = @_;
+ if ($depth > $self->{'max_depth'}) {
+ return;
+ }
+ $depth++;
+ my $element_name = $twig_element->{'att'}->{'name'};
+ # Checking if Elements already exist.
+ for (my $i = 1; $i < $depth; $i++) {
+ $self->{'message'} .= "....";
+ }
+ $self->{'message'} .= "Checking if $element_name ELEMENT already exists in the $category_name CATEGORY ...";
+ my ($product_id) = $self->{'product_id'};
+ my $element = Bugzilla::Testopia::Environment::Element->new({});
+ my ($elem_id) = $element->check_element($element_name, $env_category_id);
+ my $element_id;
+ if ($elem_id < 1) {
+ $self->{'message'} .= "DOESN'T EXIST . ";
+ my $new_category_elements = $self->{'new_category_elements'};
+ my $new_category_element = {'env_category_id' => $env_category_id, 'category_name' => $category_name, 'element_name' => $element_name};
+ push (@$new_category_elements, $new_category_element);
+ $self->{'new_category_elements'} = $new_category_elements;
+ # Storing New Elements if Admin
+ if ($admin) {
+ for (my $i = 1; $i < $depth; $i++) {
+ $self->{'message'} .= "....";
+ }
+ $self->{'message'} .= "..Storing new $element_name ELEMENT in the $category_name CATEGORY ...";
+ ($element->{'env_category_id'}) = $env_category_id;
+ $element->{'name'} = $element_name;
+ ($element->{'product_id'}) = $self->{'product_id'};
+ $element->{'isprivate'} = 0;
+ if ($parent_element) {
+ $element->{'parent_id'} = $parent_element->{'element_id'};
+ }
+ ($element_id) = $element->store();
+ $self->{'message'} .= "DONE. ";
+ }
+ }
+ else {
+ ($element_id) = $elem_id;
+ $self->{'message'} .= "EXISTS. ";
+ }
+ ($element->{'element_id'}) = $element_id;
+ ($element->{'env_category_id'}) = $env_category_id;
+ $element->{'name'} = $element_name;
+ ($element->{'parent_id'}) = $parent_element->{'parent_id'};
+ my @properties;
+ foreach my $twig_property ($twig_element->children("property")) {
+ my $property_name = $twig_property->{'att'}->{'name'};
+ # Checking if Properties already exist.
+ for (my $i = 1; $i < $depth; $i++) {
+ $self->{'message'} .= "....";
+ }
+ $self->{'message'} .= "....Checking if $property_name PROPERTY already exists...";
+ my $property = Bugzilla::Testopia::Environment::Property->new({});
+ my ($prop_id) = $property->check_property($property_name, $element_id);
+ my $property_id;
+ if ($prop_id < 1) {
+ $self->{'message'} .= "DOESN'T EXIST . ";
+ my $new_property_names = $self->{'new_property_names'};
+ push (@$new_property_names, $property_name);
+ $self->{'new_property_names'} = $new_property_names;
+ # Storing New Property if Admin
+ if ($admin) {
+ for (my $i = 1; $i < $depth; $i++) {
+ $self->{'message'} .= "....";
+ }
+ $self->{'message'} .= "......Storing new $property_name PROPERTY ...";
+ $property->{'name'} = $property_name;
+ ($property->{'element_id'}) = $element_id;
+ ($property_id) = $property->store();
+ $self->{'message'} .= "DONE. ";
+ }
+ }
+ else {
+ ($property_id) = $prop_id;
+ $self->{'message'} .= "EXISTS. ";
+ }
+ $property = Bugzilla::Testopia::Environment::Property->new($property_id);
+ # Checking if new Selected Value and Valid Expression exist.
+ my $validexp;
+ if ($property) {
+ $validexp = $property->validexp();
+ }
+ my $value = $twig_property->field('value');
+ for (my $i = 1; $i < $depth; $i++) {
+ $self->{'message'} .= "....";
+ }
+ $self->{'message'} .= "........Checking if $value VALUE exists in the list of selectable values...";
+ if ( $validexp !~ m/$value/) {
+ $self->{'message'} .= "DOESN'T EXIST . ";
+ if ($admin) {
+ if (!defined($validexp)) {
+ for (my $i = 1; $i < $depth; $i++) {
+ $self->{'message'} .= "....";
+ }
+ $self->{'message'} .= "..........Setting $value VALID EXPRESSION equal to the VALUE for the first time...";
+ $validexp = $value;
+ }
+ else {
+ for (my $i = 1; $i < $depth; $i++) {
+ $self->{'message'} .= "....";
+ }
+ $self->{'message'} .= "..........Adding $value VALUE to the VALID EXPRESSION ...";
+ $validexp = "$validexp | $value";
+ }
+ $property->update_property_validexp($validexp);
+ $self->{'message'} .= "DONE. ";
+ }
+ else {
+ my $new_validexp_values = $self->{'new_validexp_values'};
+ my $new_validexp_value = {'property_id' => $property_id, 'property_name' => $property_name, 'value' => $value};
+ push (@$new_validexp_values, $new_validexp_value);
+ $self->{'new_validexp_values'} = $new_validexp_values;
+ }
+ }
+ elsif (!defined($validexp)) {
+ $self->{'message'} .= "VALID EXPRESSION DOESN'T EXIST YET. ";
+ }
+ else {
+ $self->{'message'} .= "EXISTS. ";
+ }
+ if ($property_id && $admin) {
+ for (my $i = 1; $i < $depth; $i++) {
+ $self->{'message'} .= "....";
+ }
+ $self->{'message'} .= "............Storing new VALUE SELECTED $value ...";
+ my $environment = Bugzilla::Testopia::Environment->new($self->{'environment_id'});
+ $environment->store_property_value($property_id, $element_id, $value);
+ $self->{'message'} .= "DONE. ";
+ }
+ $property->{'value_selected'} = $value;
+ push (@properties, $property);
+ }
+ my $elm_properties = $element->{'properties'};
+ push (@$elm_properties, @properties);
+ if ($parent_element) {
+ my $children = $parent_element->{'children'};
+ push (@$children, $element);
+ $parent_element->{'children'} = $children;
+ }
+ foreach my $twig_element_child ($twig_element->children("element")) {
+ $self->parse_child_elements($depth, $env_category_id, $category_name, $twig_element_child, $admin, $element);
+ }
+ return $element;
+}
+
+
+=head2 Checking Exists
+
+=head2 DESCRIPTION
+
+Checking if the Environment, Elements, Categories, and Properties already exist or not.
+
+=cut
+
+sub check_new_items() {
+ my $self = shift;
+ my $report;
+ my $new_category_names = $self->{'new_category_names'};
+ foreach my $new_category_name (@$new_category_names) {
+ $report .= "New $new_category_name CATEGORY . ";
+ }
+ my $new_category_elements = $self->{'new_category_elements'};
+ foreach my $new_category_element (@$new_category_elements) {
+ $report .= "New $new_category_element->{'element_name'} ELEMENT in the ";
+ if (!$new_category_element->{'env_category_id'}){
+ $report .= "new ";
+ }
+ $report .= "$new_category_element->{'category_name'} CATEGORY . ";
+ }
+ my $new_property_names = $self->{'new_property_names'};
+ foreach my $new_property_name (@$new_property_names) {
+ $report .= "New $new_property_name PROPERTY . ";
+ }
+ my $new_validexp_values = $self->{'new_validexp_values'};
+ foreach my $new_validexp_value (@$new_validexp_values) {
+ $report .= "New $new_validexp_value->{'value'} VALUE for the ";
+ if (!$new_validexp_value->{'property_id'}){
+ $report .= "new ";
+ }
+ $report .= "$new_validexp_value->{'property_name'} PROPERTY 's selectable value list. ";
+ }
+ return $report;
+}
+
+
+=head2 Store the Environment
+
+=head2 Description
+
+Store the Environment Name, Element-Category relationship, Element-Property relationship,
+Element-ChildElement relationship, Environment-Element-Property-Value.
+
+=cut
+
+sub store() {
+ my $self = shift;
+ $self->{'message'} .= "Storing new XML Environment...";
+ if (!$self->{'environment_id'}) {
+ $self->{'environment_id'} = Bugzilla::Testopia::Environment->store_environment_name($self->{'name'}, $self->{'product_id'});
+ }
+ my $environment = Bugzilla::Testopia::Environment->new($self);
+ my $success = $environment->update();
+ if (!$success) {
+ $self->{'message'} .= "ABORTED! ";
+ $self->{'error'} .= "Failed to store the Environment! ";
+ return 0;
+ }
+ $self->{'message'} .= "DONE! ";
+ return 1;
+}
+
+
+=head2 export
+
+=head2 Description
+
+Exports and Environment by env_id to a scalar XML value
+
+=cut
+
+sub export() {
+ my $self = shift;
+ my ($env_id) = @_;
+
+ my $xml;
+
+ my $environment = Bugzilla::Testopia::Environment->new($env_id);
+
+ $xml =
+ "" .
+ "" .
+ "get_environment_elements();
+
+ my $categories = {};
+ my $category_names = [];
+ my $categorized_elements = [];
+ my $elements = $environment->{'elements'};
+ my $used_elements = {};
+ foreach my $element (@$elements) {
+ my $root_element = $self->get_root_parent($element);
+ my $category_name = $root_element->cat_name();
+ $categorized_elements = $categories->{ $category_name };
+ for my $used_element (@$categorized_elements) {
+ $used_elements->{ $used_element->{'element_id'} } = 1;
+ }
+ if (!$used_elements->{$root_element->{'element_id'}}) {
+ push (@$categorized_elements, $root_element);
+ $categories->{ $category_name } = $categorized_elements;
+ }
+ }
+
+ foreach my $category_name (keys %$categories) {
+ $xml .= "";
+
+ $elements = $categories->{$category_name};
+
+
+ foreach my $element (@$elements) {
+ $element->get_children();
+ $xml .= $self->export_element_and_children(1, $element, $environment->{'environment_id'});
+ }
+
+ $xml .= " ";
+ }
+
+ return "$xml ";
+}
+
+
+=head2 get_root_parent
+
+=head2 Description
+
+Helper sub that returns the root parent element in the environment of the passed in element
+
+=cut
+
+sub get_root_parent {
+ my $self = shift;
+ my ($element) = @_;
+ my $parent = $element->get_parent();
+ if (!$parent) {
+ return $element;
+ }
+ else {
+ $self->get_root_parent($parent);
+ }
+}
+
+
+=head2 export_element_and_children
+
+=head2 Description
+
+Exports Elements and their child elements to XML Recursively.
+
+=cut
+
+sub export_element_and_children() {
+ my $self = shift;
+ my ($depth, $element, $env_id) = @_;
+ if ($depth > $max_depth) {
+ return;
+ }
+ $depth++;
+
+ my $xml = "";
+
+ my $properties = $element->{'properties'};
+ foreach my $property (@$properties) {
+ my $value_selected = Bugzilla::Testopia::Environment->get_value_selected(
+ $env_id, $element->{'element_id'}, $property->{'property_id'});
+ if (defined($value_selected)) {
+ $xml .=
+ "" .
+ "$value_selected ";
+ }
+ }
+
+ my $children = $element->{'children'};
+ foreach my $child_element (@$children) {
+ $child_element->get_children();
+ $xml .= $self->export_element_and_children($depth, $child_element, $env_id);
+ }
+ $xml .= " ";
+ return $xml;
+}
+
+1;
diff --git a/mozilla/webtools/testopia/Bugzilla/Testopia/Product.pm b/mozilla/webtools/testopia/Bugzilla/Testopia/Product.pm
index 7779249a287..502546e79ea 100644
--- a/mozilla/webtools/testopia/Bugzilla/Testopia/Product.pm
+++ b/mozilla/webtools/testopia/Bugzilla/Testopia/Product.pm
@@ -50,16 +50,60 @@ sub builds {
my $ref = $dbh->selectcol_arrayref(
"SELECT build_id
- FROM test_builds tb
- JOIN test_plans tp ON tb.plan_id = tp.plan_id
- WHERE tp.product_id = ?",
+ FROM test_builds
+ WHERE product_id = ?",
undef, $self->{'id'});
my @objs;
foreach my $id (@{$ref}){
push @objs, Bugzilla::Testopia::Build->new($id);
}
- $self->{'build'} = \@objs;
- return $self->{'build'};
+ $self->{'builds'} = \@objs;
+ return $self->{'builds'};
+}
+
+sub categories {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ my $ref = $dbh->selectcol_arrayref(
+ "SELECT category_id
+ FROM test_case_categories
+ WHERE product_id = ?",
+ undef, $self->{'id'});
+ my @objs;
+ foreach my $id (@{$ref}){
+ push @objs, Bugzilla::Testopia::Category->new($id);
+ }
+ $self->{'categories'} = \@objs;
+ return $self->{'categories'};
+}
+
+sub environment_categories {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ my $ref = $dbh->selectcol_arrayref(
+ "SELECT env_category_id
+ FROM test_environment_category
+ WHERE product_id = ?",
+ undef, $self->id);
+ my @objs;
+ foreach my $id (@{$ref}){
+ push @objs, Bugzilla::Testopia::Environment::Category->new($id);
+ }
+ $self->{'environment_categories'} = \@objs;
+ return $self->{'environment_categories'};
+}
+
+sub check_product_by_name {
+ my $self = shift;
+ my ($name) = @_;
+ my $dbh = Bugzilla->dbh;
+ my ($used) = $dbh->selectrow_array(qq{
+ SELECT id
+ FROM products
+ WHERE name = ?},undef,$name);
+ return $used;
}
1;
diff --git a/mozilla/webtools/testopia/Bugzilla/Testopia/Schema.pm b/mozilla/webtools/testopia/Bugzilla/Testopia/Schema.pm
deleted file mode 100644
index b3d8b167603..00000000000
--- a/mozilla/webtools/testopia/Bugzilla/Testopia/Schema.pm
+++ /dev/null
@@ -1,489 +0,0 @@
-# -*- 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 Bug Tracking System.
-#
-# The Initial Developer of the Original Code is Netscape Communications
-# Corporation. Portions created by Netscape are
-# Copyright (C) 1998 Netscape Communications Corporation. All
-# Rights Reserved.
-#
-# Contributor(s): Justin C. De Vries
-
-package Bugzilla::Testopia::Schema;
-
-use base qw(Bugzilla::DB::Schema);
-
-use strict;
-use Bugzilla::Error;
-use Bugzilla::Util;
-use Carp;
-use Safe;
-
-# Historical, needed for SCHEMA_VERSION = '1.00'
-use Storable qw(dclone freeze thaw);
-
-#New SCHEMA_Version (2.00) use this
-use Data::Dumper;
-
-use constant SCHEMA_VERSION => '2.00';
-use constant ABSTRACT_SCHEMA => {
- #Note: Most of types will need to be defined in a MySQL specific file some where.
- test_category_templates => {
- FIELDS => [
- # Note: UNSMALLSERIAL might need to be added to the MySQL.pm file. Its
- # definition is UNSIGNED SMALLINT AUTO_INCREMENT.
- category_template_id => {TYPE => 'UNSMALLSERIAL', NOTNULL => 1},
- name => {TYPE => 'varchar(255)', NOTNULL => 1},
- description => {TYPE => 'MEDIUMTEXT'},
- ],
- INDEXES => [
- category_template_name_idx => ['name'],
- ],
- },
-
- test_attachments => {
- FIELDS => [
- # Note: Serial is a MySQL specific type. This will need to
- # be written or modified to be database generic.
- attachment_id => {TYPE => 'SERIAL'},
- # Note: UNBIGINT need to be added to the MySQL.pm file. Its
- # definition is UNSIGNED BIGINT.
- plan_id => {TYPE => 'UNBIGINT'},
- case_id => {TYPE => 'UNBIGINT'},
- submitter_id => {TYPE => 'INT3', NOTNULL => 1},
- description => {TYPE => 'MEDIUMTEXT'},
- filename => {TYPE => 'MEDIUMTEXT'},
- creation_ts => {TYPE => 'DATETIME'},
- mime_type => {TYPE => 'varchar(100)', NOTNULL => 1},
- ],
- INDEXES => [
- attachment_plan_idx => ['plan_id'],
- attachment_case_idx => ['case_id'],
- ],
- },
-
- test_case_categories => {
- FIELDS => [
- category_id => {TYPE => 'UNSMALLSERIAL', NOTNULL => 1},
- product_id => {TYPE => 'INT2', NOTNULL => 1},
- name => {TYPE => 'varchar(240)', NOTNULL => 1},
- description => {TYPE => 'MEDIUMTEXT'},
- ],
- INDEXES => [
- category_name_idx => ['name'],
- ],
- },
-
- test_cases => {
- FIELDS => [
- case_id => {TYPE => 'SERIAL'},
- case_status_id => {TYPE => 'INT1', NOTNULL => 1},
- # Note: UNINT2 need to be added to the MySQL.pm file. Its
- # definition is UNSIGNED SMALLINT.
- category_id => {TYPE => 'UNINT2', NOTNULL => 1},
- priority_id => {TYPE => 'INT2'},
- author_id => {TYPE => 'INT3', NOTNULL => 1},
- creation_date => {TYPE => 'DATETIME', NOTNULL => 1},
- isautomated => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '0'},
- sortkey => {TYPE => 'INT4'},
- script => {TYPE => 'MEDIUMTEXT'},
- arguments => {TYPE => 'MEDIUMTEXT'},
- summary => {TYPE => 'varchar(255)'},
- requirement => {TYPE => 'varchar(255)'},
- alias => {TYPE => 'varchar(255)'},
- ],
- INDEXES => [
- test_case_category_idx => ['category_id'],
- test_case_author_idx => ['author_id'],
- test_case_creation_date_idx => ['creation_date'],
- test_case_sortkey_idx => ['sortkey'],
- test_case_requirement_idx => ['requirement'],
- test_case_shortname_idx => ['alias'],
- ],
- },
-
- test_case_bugs => {
- FIELDS => [
- bug_id => {TYPE => 'INT3', NOTNUTLL => 1},
- case_run_id => {TYPE => 'UNBIGINT'},
- ],
- INDEXES => [
- case_run_id_idx => [qw(case_run_id bug_id)],
- case_run_bug_id_idx => ['bug_id'],
- ],
- },
-
- test_case_runs => {
- FIELDS => [
- case_run_id => {TYPE => 'SERIAL'},
- run_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
- case_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
- assignee => {TYPE => 'INT3'},
- testedby => {TYPE => 'INT3'},
- case_run_status_id => {TYPE => 'UNINT1', NOTNULL => 1},
- case_text_version => {TYPE => 'UNINT3', NOTNULL => 1},
- build_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
- close_date => {TYPE => 'DATETIME'},
- notes => {TYPE => 'TEXT'},
- iscurrent => {TYPE => 'BOOLEAN', DEFAULT => '0'},
- sortkey => {TYPE => 'INT4'},
- ],
- INDEXES => [
- case_run_run_id_idx => ['run_id'],
- case_run_case_id_idx => ['case_id'],
- case_run_assignee_idx => ['assignee'],
- case_run_testedby_idx => ['testedby'],
- case_run_close_date_idx => ['close_date'],
- case_run_shortkey_idx => ['sortkey'],
- ],
- },
-
- test_case_texts => {
- FIELDS => [
- case_id => {TYPE => 'UNBIGINT', NOTNULL =>1},
- case_text_version => {TYPE => 'INT3', NOTNULL =>1},
- who => {TYPE => 'INT3', NOTNULL =>1},
- creation_ts => {TYPE => 'DATETIME', NOTNULL =>1},
- action => {TYPE => 'MEDIUMTEXT'},
- effect => {TYPE => 'MEDIUMTEXT'},
- ],
- INDEXES => [
- case_versions_idx => {FIELDS => [qw(case_id case_text_version)],
- TYPE => 'UNIQUE'},
- case_versions_who_idx => ['who'],
- case_versions_creation_ts_idx => ['creation_ts'],
- ],
- },
-
- test_case_tags => {
- FIELDS => [
- tag_id => {TYPE => 'UNINT4', NOTNULL => 1},
- case_id => {TYPE => 'UNBIGINT'},
- ],
- INDEXES => [
- case_tags_case_id_idx => [qw(case_id tag_id)],
- case_tags_tag_id_idx => [qw(tag_id case_id)],
- test_tag_name_indx => ['tag_name'],
- ],
- },
-
- test_plan_testers => {
- FIELDS => [
- tester_id => {TYPE => 'INT3', NOTNULL => 1},
- product_id => {TYPE => 'INT2'},
- read_only => {TYPE => 'BOOLEAN', DEFAULT => '1'},
- ],
- },
-
- test_tags => {
- FIELDS => [
- # Note: UNINT4SERIAL need to be added to the MySQL.pm file. Its
- # definition is UNSIGNED INTEGER AUTO_INCREMENT.
- tag_id => {TYPE => 'UNINT4SERIAL', NOTNULL => 1},
- tag_name => {TYPE => 'varchar(255)', NOTNULL => 1},
- ],
- },
-
- test_plans => {
- FIELDS => [
- plan_id => {TYPE => 'SERIAL'},
- product_id => {TYPE => 'INT2', NOTNULL => 1},
- author_id => {TYPE => 'INT3', NOTNULL => 1},
- editor_id => {TYPE => 'INT3', NOTNULL => 1},
- type_id => {TYPE => 'UNINT1', NOTNULL => 1},
- default_product_version => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
- name => {TYPE => 'varchar(255)', NOTNULL => 1},
- creation_date => {TYPE => 'DATETIME', NOTNULL => 1},
- isactive => {TYPE => 'BOOLEAN', NOTNULL =>, DEFAULT => '1'},
- ],
- INDEXES => [
- plan_product_plan_id_idx => [qw(product_id plan_id)],
- plan_author_idx => ['author_id'],
- plan_type_idx => ['type_id'],
- plan_editor_idx => ['editor_id'],
- plan_isactive_idx => ['isactive'],
- ],
- },
-
- text_plan_text => {
- FIELDS => [
- plan_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
- plan_text_version => {TYPE => 'INT4', NOTNULL => 1},
- who => {TYPE => 'INT3', NOTNULL => 1},
- creation_ts => {TYPE => 'DATETIME', NOTNULL => 1},
- # Note: LONGTEXT needs to be added to the MySQL.pm file. Its
- # definition is LONGTEXT.
- plan_text => {TYPE => 'LONGTEXT'},
- ],
- INDEXES => [
- test_plan_text_version_idx => [qw(plan_id plan_text_version)],
- test_plan_text_who_idx => ['who'],
- ],
- },
-
- test_plan_types => {
- FIELDS => [
- # Note: UNTINYINTSERIAL needs to be added to the MySQL.pm file. Its
- # definition is UNSIGNED TINYINT AUTO_INCREMENT.
- type_id => {TYPE => 'UNTINYSERIAL', NOTNULL => 1},
- name => {TYPE => 'varchar(64)', NOTNULL => 1},
- ],
- INDEXES => [
- plan_type_name_idx => ['name'],
- ],
- },
-
- test_runs => {
- FIELDS => [
- run_id => {TYPE => 'SERIAL'},
- plan_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
- environment_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
- product_version => {TYPE => 'MEDIUMTEXT'},
- build_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
- plan_text_version => {TYPE => 'INT4', NOTNULL => 1},
- manager_id => {TYPE => 'INT3', NOTNULL => 1},
- default_tester_id => {TYPE => 'INT3'},
- start_date => {TYPE => 'DATETIME'},
- stop_date => {TYPE => 'DATETIME'},
- summary => {TYPE => 'TINYTEXT', NOTNULL => 1},
- notes => {TYPE => 'MEDIUMTEXT'},
- ],
- INDEXES => [
- test_run_plan_id_run_id_idx => [qw(plan_id run_id)],
- test_run_manager_idx => ['manager_id'],
- test_run_start_date_idx => ['start_date'],
- ],
- },
-
- test_case_plans => {
- FIELDS => [
- plan_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
- case_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
- ],
- INDEXES => [
- case_plans_plan_id_idx => ['plan_id'],
- case_plans_case_id_idx => ['plan_id'],
- ],
- },
-
- test_case_activity => {
- FIELDS => [
- case_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
- fieldid => {TYPE => 'UNINT2', NOTNULL => 1},
- who => {TYPE => 'INT3', NOTNULL => 1},
- changed => {TYPE => 'DATETIME', NOTNULL => 1},
- oldvalue => {TYPE => 'MEDUIMTEXT'},
- newvalue => {TYPE => 'MEDUIMTEXT'},
- ],
- INDEXES => [
- case_activity_case_id_idx => ['case_id'],
- case_activity_who_idx => ['who'],
- case_activity_when_idx => ['changed'],
- case_activity_field_idx => ['fieldid'],
- ],
- },
-
- test_fielddefs => {
- FIELDS => [
- fieldid => {TYPE => 'UNSMALLSERIAL', NOTNULL => 1},
- name => {TPYE => 'varchar(100)', NOTNULL => 1},
- description => {TYPE => 'MEDIUMTEXT'},
- table_name => {TYPE => 'varchar(100)', NOTNULL => 1},
- ],
- INDEXES => [
- fielddefs_name_idx => ['name'],
- ],
- },
-
- test_plan_activity => {
- FIELDS => [
- plan_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
- fieldid => {TYPE => 'UNINT2', NOTNULL => 1},
- who => {TYPE => 'INT3', NOTNULL => 1},
- changed => {TYPE => 'DATETIME', NOTNULL => 1},
- oldvalue => {TYPE => 'MEDIUMTEXT'},
- newvalue => {TYPE => 'MEDIUMTEXT'},
- ],
- INDEXES => [
- plan_activity_who_idx => ['who'],
- ],
- },
-
- test_case_components => {
- FIELDS => [
- case_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
- component_id => {TYPE => 'INT2', NOTNULL => 1},
- ],
- INDEXES => [
- case_components_case_id_idx => ['case_id'],
- case_commponents_component_id_idx => ['case_id'],
- ],
- },
-
- test_run_activity => {
- FIELDS => [
- run_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
- fieldid => {TYPE => 'UNINT2', NOTNULL => 1},
- who => {TYPE => 'INT3', NOTNULL => 1},
- changed => {TYPE => 'DATETIME', NOTNULL => 1},
- oldvalue => {TYPE => 'MEDIUMTEXT'},
- newvalue => {TYPE => 'MEDIUMTEXT'},
- ],
- INDEXES => [
- run_activity_run_id_idx => ['run_id'],
- run_activity_who_idx => ['who'],
- run_activity_when_idx => ['changed'],
- ],
- },
-
- test_run_cc => {
- FIELDS => [
- run_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
- who => {TYPE => 'INT3', NOTNULL => 1},
- ],
- INDEXES => [
- run_cc_run_id_who_idx => [qw(run_id who)],
- ],
- },
-
- test_email_settings => {
- FIELDS => [
- userid => {TYPE => 'INT3', NOTNULL => 1},
- eventid => {TYPE => 'UNINT1', NOTNULL => 1},
- relationship_id => {TYPE => 'UNINT1', NOTNULL => 1},
- ],
- INDEXES => [
- test_event_user_event_idx => [qw(userid eventid)],
- test_event_user_relationship_idx => [qw(userid relationship_id)],
- ],
- },
-
- test_events => {
- FIELDS => [
- eventid => {TYPE => 'UNIN1', NOTNULL => 1},
- name => {TYPE => 'varchar(50)'},
- ],
- INDEXES => [
- test_event_name_idx => ['name'],
- ],
- },
-
- test_relationships => {
- FIELDS => [
- relationship_id => {TYPE => 'UNINT1', NOTNULL => 1},
- name => {TYPE => 'varchar(50)'},
- ],
- },
-
- test_case_run_status => {
- FIELDS => [
- case_run_status_id => {TYPE => 'UNTINYSERIAL', NOTNULL => 1},
- name => {TYPE => 'varchar(20)'},
- sortkey => {TYPE => 'INT4'},
- ],
- INDEXES => [
- case_run_status_name_idx => ['name'],
- case_run_status_sortkey_idx => ['sortkey'],
- ],
- },
-
- test_case_status => {
- FIELDS => [
- case_status_id => {TYPE => 'UNTINYSERIAL', NOTNULL => 1},
- name => {TYPE => 'varchar(255)'},
- ],
- INDEXES => [
- test_case_status_name_idx => ['name'],
- ],
- },
-
- test_case_dependencies => {
- FIELDS => [
- dependson => {TYPE => 'UNBIGINT', NOTNULL => 1},
- blocks => {TYPE => 'UNBIGINT'},
- ],
- },
-
- test_case_group_map => {
- FIELDS => [
- case_id => {TYPE => 'UNBIGINT', NOTNULL =>1},
- group_id => {TYPE => 'INT3'},
- ],
- },
-
- test_environments => {
- FIELDS => [
- environment_id => {TYPE => 'SERIAL', NOTNULL => 1},
- op_sys_id => {TYPE => 'INT4'},
- rep_platform_id => {TYPE => 'INT4'},
- name => {TYPE => 'varchar(255)'},
- xml => {TYPE => 'MEDIUMTEXT'},
- ],
- INDEXES => [
- environment_op_sys_idx => ['op_sys_id'],
- environment_platform_idx => ['rep_platform_id'],
- environment_name_idx => ['name'],
- ],
- },
-
- test_run_tags => {
- FIELDS => [
- tag_id => {TYPE => 'UNINT4', NOTNULL => 1},
- run_id => {TYPE => 'UNBIGINT'},
- ],
- INDEXES => [
- run_tags_idx => ['qw(tag_id, run_id'],
- ],
- },
-
- test_plan_tags => {
- FIELDS => [
- tag_id => {TYPE => 'UNINT4', NOTNULL => 1},
- plan_id => {TYPE => 'UNBIGINT'},
- ],
- INDEXES => [
- plan_tags_idx => ['qw(tag_id, plan_id'],
- ],
- },
-
- test_build => {
- FIELDS => [
- build_id => {TYPE => 'SERIAL', NOTNULL => 1},
- product_id => {TYPE => 'INT2', NOTNULL => 1},
- name => {TYPE => 'varchar(255)'},
- description => {TYPE => 'TEXT'},
- ],
- },
-
- test_attachment_data => {
- FIELDS => [
- attachment_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
- contents => {TYPE => 'LONGBLOB'},
- ],
- },
-
- test_named_queries => {
- FIELDS => [
- userid => {TYPE => 'INT3', NOTNULL => 1},
- name => {TYPE => 'varchar(64)', NOTNULL => 1},
- isvisible => {TYEP => 'BOOLEAN', NOTNULL => 1, DEFAULT => 1},
- query => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
- ],
- INDEXES => [
- test_namedquery_name_idx => ['name'],
- ],
- },
-
-};
-
-1;
diff --git a/mozilla/webtools/testopia/Bugzilla/Testopia/Search.pm b/mozilla/webtools/testopia/Bugzilla/Testopia/Search.pm
index af1ae436d5a..2e9de55c57d 100644
--- a/mozilla/webtools/testopia/Bugzilla/Testopia/Search.pm
+++ b/mozilla/webtools/testopia/Bugzilla/Testopia/Search.pm
@@ -133,9 +133,10 @@ sub init {
my $type;
my $obj = trim($cgi->param('current_tab')) || ThrowUserError('testopia-missing-parameter', {'param' => 'current_tab'});
- ThrowUserError('unknown-tab') if $obj !~ '^(case|plan|run|case_run)$';
+ ThrowUserError('unknown-tab') if $obj !~ '^(case|plan|run|case_run|environment)$';
trick_taint($obj);
+ # Set up tables for field sort order
my $order = $cgi->param('order') || '';
if ($order eq 'author') {
push @supptables, "INNER JOIN profiles as map_author ON map_author.userid = test_". $obj ."s.author_id";
@@ -158,7 +159,7 @@ sub init {
push @orderby, 'map_tester.login_name';
}
elsif ($order eq 'product') {
- push @supptables, "INNER JOIN products ON products.id = test_". $obj ."s.product_id";
+ push @supptables, "LEFT JOIN products ON products.id = test_". $obj ."s.product_id";
push @orderby, 'products.name';
}
elsif ($order eq 'build') {
@@ -178,6 +179,9 @@ sub init {
push @orderby, 'versions.value';
}
elsif ($order eq 'priority') {
+ if ($obj eq 'case_run'){
+ push @supptables, "INNER JOIN test_cases ON test_cases.case_id = test_case_runs.case_id";
+ }
push @supptables, "INNER JOIN priority ON priority.id = test_cases.priority_id";
push @orderby, 'test_cases.priority_id';
}
@@ -201,7 +205,13 @@ sub init {
push @orderby, 'case_status.name';
}
elsif ($order eq 'summary') {
- push @orderby, 'test_'. $obj .'s.summary';
+ if ($obj eq 'case_run'){
+ push @supptables, "INNER JOIN test_cases AS cases ON cases.case_id = test_case_runs.case_id";
+ push @orderby, 'cases.summary';
+ }
+ else{
+ push @orderby, 'test_'. $obj .'s.summary';
+ }
}
elsif ($order eq 'created') {
push @orderby, 'test_'. $obj .'s.creation_date';
@@ -215,6 +225,7 @@ sub init {
push @orderby, 'test_'. $obj .'s.' . $order;
}
}
+
my @funcdefs =
(
"^category," => sub {
@@ -250,12 +261,71 @@ sub init {
$f = "plan_texts.plan_text";
},
"^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";
+ }
+ },
+ "^env_products," => sub {
+ print STDERR "THIS IS HERE";
push(@supptables,
- "LEFT JOIN test_environments AS env " .
- "ON test_runs.environment_id = env.environment_id");
- $f = "env.xml";
+ "INNER JOIN products as env_products
+ ON test_environments.product_id = env_products.id");
+ $f = 'env_products.id'
+ },
+ "^env_categories," => sub {
+ push(@supptables,
+ "INNER JOIN test_environment_map as env_map_categories
+ ON test_environments.environment_id = env_map_categories.environment_id");
+ push(@supptables,
+ "INNER JOIN test_environment_element as env_element
+ ON env_map_categories.element_id = env_element.element_id");
+ push(@supptables,
+ "INNER JOIN test_environment_category as env_categories
+ ON env_element.env_category_id = env_categories.env_category_id");
+ $f = 'env_categories.env_category_id'
+ },
+ "^env_elements," => sub {
+ push(@supptables,
+ "INNER JOIN test_environment_map as env_map_elements
+ ON test_environments.environment_id = env_map_elements.environment_id");
+ push(@supptables,
+ "INNER JOIN test_environment_element as env_element
+ ON env_map_elements.element_id = env_element.element_id");
+ $f = 'env_element.element_id'
+ },
+ "^env_properties," => sub {
+ push(@supptables,
+ "INNER JOIN test_environment_map as env_map_properties
+ ON test_environments.environment_id = env_map_properties.environment_id");
+ push(@supptables,
+ "INNER JOIN test_environment_property as env_property
+ ON env_map_properties.property_id = env_property.property_id");
+ $f = 'env_property.property_id'
+ },
+ "^env_expressions," => sub {
+ push(@supptables,
+ "INNER JOIN test_environment_map as env_map_value
+ ON test_environments.environment_id = env_map_value.environment_id");
+ $f = 'env_map_value.value_selected'
+ },
+ "^env_value_selected," => sub {
+ push(@supptables,
+ "INNER JOIN test_environment_map as env_map_value_selected
+ ON test_environments.environment_id = env_map_value_selected.environment_id");
+ $f = 'env_map_value_selected.value_selected'
},
"^component," => sub {
+ if ($obj eq 'case_run'){
+ push(@supptables,
+ "INNER JOIN test_cases
+ ON test_cases.case_id = test_case_runs.case_id");
+ }
push(@supptables,
"INNER JOIN test_case_components AS tc_components " .
"ON test_cases.case_id = tc_components.case_id");
@@ -264,6 +334,22 @@ sub init {
"ON components.id = tc_components.component_id");
$f = "components.name";
},
+ "^priority_id," => sub {
+ if ($obj eq 'case_run'){
+ push(@supptables,
+ "INNER JOIN test_cases
+ ON test_cases.case_id = test_case_runs.case_id");
+ }
+ $f = "test_cases.priority_id";
+ },
+ "^isautomated," => sub {
+ if ($obj eq 'case_run'){
+ push(@supptables,
+ "INNER JOIN test_cases
+ ON test_cases.case_id = test_case_runs.case_id");
+ }
+ $f = "test_cases.isautomated";
+ },
"^milestone," => sub {
push(@supptables,
"INNER JOIN test_builds AS builds " .
@@ -282,13 +368,33 @@ sub init {
"ON case_bugs.bug_id = bugs.bug_id");
$f = "bugs.bug_id";
},
+ "^case_summary," => sub {
+ push(@supptables,
+ "INNER JOIN test_cases AS cases " .
+ "ON cases.case_id = test_case_runs.case_id");
+ $f = "cases.summary";
+ },
+
"^tags," => sub {
- push(@supptables,
- "INNER JOIN test_". $obj ."_tags AS ". $obj ."_tags " .
- "ON test_". $obj ."s.". $obj ."_id = ". $obj ."_tags.". $obj ."_id");
- push(@supptables,
- "INNER JOIN test_tags " .
- "ON ". $obj ."_tags.tag_id = test_tags.tag_id");
+ if ($obj eq 'case_run'){
+ push(@supptables,
+ "INNER JOIN test_cases " .
+ "ON test_case_runs.case_id = test_cases.case_id");
+ push(@supptables,
+ "INNER JOIN test_case_tags AS case_tags " .
+ "ON test_cases.case_id = case_tags.case_id");
+ push(@supptables,
+ "INNER JOIN test_tags " .
+ "ON case_tags.tag_id = test_tags.tag_id");
+ }
+ else{
+ push(@supptables,
+ "INNER JOIN test_". $obj ."_tags AS ". $obj ."_tags " .
+ "ON test_". $obj ."s.". $obj ."_id = ". $obj ."_tags.". $obj ."_id");
+ push(@supptables,
+ "INNER JOIN test_tags " .
+ "ON ". $obj ."_tags.tag_id = test_tags.tag_id");
+ }
$f = "test_tags.tag_name";
},
"^case_plan_id," => sub {
@@ -324,17 +430,28 @@ sub init {
"ON test_plans.product_id = products.id");
$f = "test_plans.product_id";
},
- "^(author|editor|manager|default_tester)," => sub {
+ "^(author|manager|default_tester)," => sub {
push(@supptables,
"INNER JOIN profiles AS map_$1 " .
"ON test_". $obj ."s.". $1 ."_id = map_$1.userid");
- $f = "map_$1.login_name";
+ $f = "map_$1.login_name";
},
"^(assignee|testedby)," => sub {
- push(@supptables,
+ if ($obj eq 'run'){
+ push(@supptables,
+ "LEFT JOIN test_case_runs AS case_run " .
+ "ON case_run.run_id = test_runs.run_id");
+ push(@supptables,
+ "INNER JOIN profiles AS map_$1 " .
+ "ON case_run.". $1 ." = map_$1.userid");
+ }
+ else {
+ push(@supptables,
"INNER JOIN profiles AS map_$1 " .
"ON test_". $obj ."s.". $1 ." = map_$1.userid");
+ }
$f = "map_$1.login_name";
+
},
",isnotnull" => sub {
$term = "$ff is not null";
@@ -409,22 +526,46 @@ sub init {
if ($cgi->param('case_id')) {
my $type = "anyexact";
- if ($cgi->param('caseidtype') && $cgi->param('caseidtype') eq 'exclude') {
- $type = "nowords";
+ if ($cgi->param('caseidtype'))
+ {
+ if ($cgi->param('caseidtype') eq 'exclude')
+ {
+ $type = "nowords";
+ }
+ else
+ {
+ $type = $cgi->param('caseidtype')
+ }
}
push(@specialchart, ["case_id", $type, join(',', $cgi->param('case_id'))]);
}
if ($cgi->param('run_id')) {
my $type = "anyexact";
- if ($cgi->param('runidtype') && $cgi->param('runidtype') eq 'exclude') {
- $type = "nowords";
+ if ($cgi->param('runidtype'))
+ {
+ if ($cgi->param('runidtype') eq 'exclude')
+ {
+ $type = "nowords";
+ }
+ else
+ {
+ $type = $cgi->param('runidtype')
+ }
}
push(@specialchart, ["run_id", $type, join(',', $cgi->param('run_id'))]);
}
if ($cgi->param('plan_id')) {
my $type = "anyexact";
- if ($cgi->param('planidtype') && $cgi->param('planidtype') eq 'exclude') {
- $type = "nowords";
+ if ($cgi->param('planidtype'))
+ {
+ if ($cgi->param('planidtype') eq 'exclude')
+ {
+ $type = "nowords";
+ }
+ else
+ {
+ $type = $cgi->param('planidtype')
+ }
}
if ($obj eq 'case'){
push(@specialchart, ["case_plan_id", $type, join(',', $cgi->param('plan_id'))]);
@@ -469,7 +610,8 @@ sub init {
my @legal_fields = ("case_status_id", "category", "priority_id",
"component", "isautomated", "case_run_status_id",
"default_product_version", "type_id",
- "build", "environment_id", "milestone");
+ "build", "environment_id", "milestone", "env_products",
+ "env_categories", "env_elements", "env_properties", "env_expressions");
foreach my $field ($cgi->param()) {
if (lsearch(\@legal_fields, $field) != -1) {
@@ -509,7 +651,7 @@ sub init {
}
# Check for author
my @clist;
- foreach my $profile ("author", "editor", "manager", "default_tester",
+ foreach my $profile ("author", "manager", "default_tester",
"assignee", "testedby"){
$t = $cgi->param($profile . "_type") || '';
if ($t eq "exact") {
@@ -535,9 +677,9 @@ sub init {
}
# check static text fields
- foreach my $f ("summary", "tcaction", "tceffect", "script",
+ foreach my $f ("case_summary", "summary", "tcaction", "tceffect", "script",
"requirement", "name", "plan_text", "environment",
- "notes") {
+ "notes", "env_value_selected") {
if (defined $cgi->param($f)) {
my $s = trim($cgi->param($f));
if ($s ne "") {
@@ -557,6 +699,7 @@ sub init {
push @wherepart, 'test_case_runs.iscurrent = 1';
}
}
+
my @funcnames;
while (@funcdefs) {
my $key = shift(@funcdefs);
diff --git a/mozilla/webtools/testopia/Bugzilla/Testopia/Table.pm b/mozilla/webtools/testopia/Bugzilla/Testopia/Table.pm
index 5b4e27b7d94..219068a6460 100644
--- a/mozilla/webtools/testopia/Bugzilla/Testopia/Table.pm
+++ b/mozilla/webtools/testopia/Bugzilla/Testopia/Table.pm
@@ -31,7 +31,7 @@ arguments:
=over
-=item type - one of 'case', 'plan', 'run', or 'caserun'
+=item type - one of 'case', 'plan', 'run', 'caserun', or 'environment'
=item url - the cgi file that is calling this
@@ -135,6 +135,9 @@ sub init {
elsif ($type eq 'case_run'){
$o = Bugzilla::Testopia::TestCaseRun->new($id);
}
+ elsif ($type eq 'environment'){
+ $o = Bugzilla::Testopia::Environment->new($id);
+ }
push (@ids, $id);
push (@list, $o);
}
@@ -248,7 +251,7 @@ sub save_list {
(join(",", $self->{'id_list'}), $self->{'user'}->id, "__". $self->{'type'} ."__"));
}
else {
- $dbh->do("INSERT INTO test_named_queries VALUES(?,?,?,?)", undef,
+ $dbh->do("INSERT INTO test_named_queries (userid, name, isvisible, query) VALUES(?,?,?,?)", undef,
($self->{'user'}->id, "__". $self->{'type'} ."__", 0, join(",", $self->{'id_list'})));
}
$dbh->bz_unlock_tables();
@@ -344,8 +347,10 @@ sub get_next{
###############################
sub list { return $_[0]->{'list'}; }
+sub id_list { return $_[0]->{'id_list'}; }
sub list_count { return $_[0]->{'list_count'}; }
sub page { return $_[0]->{'page'}; }
+sub url_loc { return $_[0]->{'url_loc'}; }
=head2 page_size
@@ -405,6 +410,21 @@ sub get_url {
return $self->{'url'};
}
+sub get_query_part {
+ my $self = shift;
+ my $cgi = $self->{'cgi'};
+ my @keys = $cgi->param;
+ my $qstring;
+ foreach my $key (@keys){
+ my @vals = $cgi->param($key);
+ foreach my $val (@vals){
+ $qstring .= $key ."=". $val ."&";
+ }
+ }
+ chop $qstring;
+ return $qstring
+}
+
=head2 page_count
Returns a total count of the number of pages returned by a query.
diff --git a/mozilla/webtools/testopia/Bugzilla/Testopia/TestCase.pm b/mozilla/webtools/testopia/Bugzilla/Testopia/TestCase.pm
index 524e26204bf..6bd90112afb 100644
--- a/mozilla/webtools/testopia/Bugzilla/Testopia/TestCase.pm
+++ b/mozilla/webtools/testopia/Bugzilla/Testopia/TestCase.pm
@@ -24,7 +24,7 @@
# Christopher Aillon
# Myk Melez
# Jeff Hedlund
-# Fr��ic Buclin
+# Frederic Buclin
#
# Contributor(s): Greg Hendricks
@@ -193,41 +193,26 @@ sub get_selectable_components {
my $self = shift;
my ($byid) = @_;
my $dbh = Bugzilla->dbh;
- my @compids;
my @exclusions;
unless ($byid) {
foreach my $e (@{$self->components}){
push @exclusions, $e->{'id'};
}
}
- my $query = "SELECT id FROM components
- WHERE product_id = ?";
+ my $query = "SELECT DISTINCT id FROM components
+ WHERE product_id IN (" . join(",", @{$self->get_product_ids}) . ") ";
if (@exclusions){
- $query .= " AND id NOT IN(". join(",", @exclusions) .")";
+ $query .= "AND id NOT IN(". join(",", @exclusions) .") ";
}
- foreach my $p (@{$self->plans}){
- my $ref = $dbh->selectcol_arrayref(
- $query, undef, $p->{'product_id'});
- push @compids, @{$ref};
- }
- my %compseen;
- foreach (@compids){
- $compseen{$_} = 1;
- }
- @compids = keys %compseen;
- #TODO: 2.22 use Bugzilla::Component
+ $query .= "ORDER BY name";
+
+ my $ref = $dbh->selectcol_arrayref($query);
my @comps;
push @comps, {'id' => '0', 'name' => '--Please Select--'} unless $byid;
- foreach my $id (@compids){
- my $ref = $dbh->selectrow_hashref(
- "SELECT id, name FROM components
- WHERE id = ?", undef, $id);
-
- push @comps, $ref;
- #push @comps, Bugzilla::Component->new($id);
+ foreach my $id (@$ref){
+ push @comps, Bugzilla::Component->new($id);
}
return \@comps;
-
}
=head2 get_available_components
@@ -275,35 +260,25 @@ plans referenced by this case.
#TODO: Move to Testopia::Product.pm in 2.22
sub get_category_list{
my $self = shift;
+ my ($by_name) = @_;
my $dbh = Bugzilla->dbh;
- my @categories;
+
+ my @product_ids;
foreach my $p (@{$self->plans}){
- if (Bugzilla::Testopia::Util::can_view_product($p->product_id)){
- push @categories, @{$p->categories};
- }
+ push @product_ids, $p->product_id;
}
- my %seen;
- foreach my $c (@categories){
- $seen{$c->id} = $c;
+ my $id_part;
+ my $name_part;
+ if ($by_name){
+ $id_part = "cat.name AS id";
+ $name_part = ", cat.name as name";
}
- @categories = values %seen;
- @categories = sort @categories;
- return \@categories;
-}
-
-=head2 get_distinct_categories
-
-Returns a list of all user viewable categories for use in searches
-
-=cut
-
-#TODO: Move to Testopia::Product.pm in 2.22
-sub get_distinct_categories{
- my $self = shift;
- my $dbh = Bugzilla->dbh;
- my $query = "SELECT DISTINCT cat.name AS id, cat.name " .
- "FROM test_case_categories AS cat " .
- "JOIN products ON cat.product_id = products.id " .
+ else {
+ $id_part = "category_id";
+ }
+ my $query = "SELECT DISTINCT $id_part $name_part ";
+ $query .= "FROM test_case_categories AS cat " .
+ "JOIN products ON cat.product_id = products.id " .
"LEFT JOIN group_control_map " .
"ON group_control_map.product_id = products.id ";
if (Param('useentrygroupdefault')) {
@@ -316,11 +291,50 @@ sub get_distinct_categories{
$query .= "AND group_id NOT IN(" .
join(',', values(%{Bugzilla->user->groups})) . ") ";
}
- $query .= "WHERE group_id IS NULL ORDER BY cat.name";
-
- my $ref = $dbh->selectall_arrayref($query, {'Slice'=>{}});
+ $query .= "WHERE group_id IS NULL ";
+ $query .= "AND cat.product_id IN (". join(",", @product_ids) . ") " unless ($by_name || !@product_ids);
+ $query .= "ORDER BY cat.name";
- return $ref;
+ if ($by_name){
+ my $ref = $dbh->selectall_arrayref($query, {'Slice'=>{}});
+ return $ref;
+ }
+ my $ids = $dbh->selectcol_arrayref($query);
+
+ my @categories;
+ foreach my $c (@$ids){
+ push @categories, Bugzilla::Testopia::Category->new($c);
+ }
+ return \@categories;
+}
+
+=head2 get_product_ids
+
+Returns the list of product ids that this case is associated with
+
+=cut
+
+sub get_product_ids {
+ my $self = shift;
+
+ if ($self->id == 0){
+ my @ids;
+ foreach my $plan (@{$self->plans}){
+ push @ids, $plan->product_id if Bugzilla->user->can_see_product($plan->product_name);
+ }
+ return \@ids;
+ }
+
+ my $dbh = Bugzilla->dbh;
+
+ my $ref = $dbh->selectcol_arrayref(
+ "SELECT DISTINCT products.id FROM products
+ JOIN test_plans AS plans ON plans.product_id = products.id
+ JOIN test_case_plans ON plans.plan_id = test_case_plans.plan_id
+ JOIN test_cases AS cases ON cases.case_id = test_case_plans.case_id
+ WHERE cases.case_id = ?
+ ORDER BY products.id", undef, $self->{'case_id'});
+ return $ref;
}
=head2 get_status_list
@@ -373,8 +387,8 @@ sub add_tag {
$dbh->bz_unlock_tables();
return 1;
}
- $dbh->do("INSERT INTO test_case_tags(tag_id, case_id) VALUES(?,?)",
- undef, $tag_id, $self->{'case_id'});
+ $dbh->do("INSERT INTO test_case_tags(tag_id, case_id, userid) VALUES(?,?,?)",
+ undef, $tag_id, $self->{'case_id'}, Bugzilla->user->id);
$dbh->bz_unlock_tables();
return 0;
@@ -396,6 +410,51 @@ sub remove_tag {
return;
}
+=head2 attach_bug
+
+Attaches the specified bug to this test case
+
+=cut
+
+sub attach_bug {
+ my $self = shift;
+ my ($bug, $case_id) = @_;
+ $case_id ||= $self->{'case_id'};
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->bz_lock_tables('test_case_bugs WRITE');
+ my ($exists) = $dbh->selectrow_array(
+ "SELECT bug_id
+ FROM test_case_bugs
+ WHERE case_id=?
+ AND bug_id=?",
+ undef, ($case_id, $bug));
+ if ($exists) {
+ $dbh->bz_unlock_tables();
+ return;
+ }
+ $dbh->do("INSERT INTO test_case_bugs (bug_id, case_run_id, case_id)
+ VALUES(?,?,?)", undef, ($bug, undef, $self->{'case_id'}));
+ $dbh->bz_unlock_tables();
+}
+
+=head2 detach_bug
+
+Removes the association of the specified bug from this test case-run
+
+=cut
+
+sub detach_bug {
+ my $self = shift;
+ my ($bug) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->do("DELETE FROM test_case_bugs
+ WHERE bug_id = ?
+ AND case_id = ?",
+ undef, ($bug, $self->{'case_id'}));
+}
+
=head2 add_component
Associates a component with this test case
@@ -465,14 +524,17 @@ text has changed and thus requiring a new version be created.
sub diff_case_doc {
my $self = shift;
- my ($newaction, $neweffect) = @_;
+ my ($newaction, $neweffect, $newsetup, $newbreakdown) = @_;
my $dbh = Bugzilla->dbh;
- my ($oldaction, $oldeffect) = $dbh->selectrow_array(
- "SELECT action, effect FROM test_case_texts
- WHERE case_id = ? AND case_text_version = ?",
- undef, $self->{'case_id'}, $self->version);
+ my ($oldaction, $oldeffect, $oldsetup, $oldbreakdown) = $dbh->selectrow_array(
+ "SELECT action, effect, setup, breakdown
+ FROM test_case_texts
+ WHERE case_id = ? AND case_text_version = ?",
+ undef, ($self->{'case_id'}, $self->version));
my $diff = diff(\$newaction, \$oldaction);
$diff .= diff(\$neweffect, \$oldeffect);
+ $diff .= diff(\$newsetup, \$oldsetup);
+ $diff .= diff(\$newbreakdown, \$oldbreakdown);
return $diff
}
@@ -516,7 +578,8 @@ sub store {
my $key = $dbh->bz_last_key( 'test_cases', 'case_id' );
- $self->store_text($key, $self->{'author_id'}, $self->{'action'}, $self->{'effect'}, $timestamp);
+ $self->store_text($key, $self->{'author_id'}, $self->{'action'}, $self->{'effect'},
+ $self->{'setup'}, $self->{'breakdown'}, $timestamp);
$self->update_deps($self->{'dependson'}, $self->{'blocks'}, $key);
foreach my $p (@{$self->{'plans'}}){
$self->link_plan($p->id, $key);
@@ -535,16 +598,16 @@ author id, action text, effect text, and an optional timestamp.
sub store_text {
my $self = shift;
my $dbh = Bugzilla->dbh;
- my ($key, $author, $action, $effect, $timestamp) = @_;
+ my ($key, $author, $action, $effect, $setup, $breakdown, $timestamp) = @_;
if (!defined $timestamp){
($timestamp) = Bugzilla::Testopia::Util::get_time_stamp();
}
- trick_taint($action);
- trick_taint($effect);
my $version = $self->version || 0;
- $dbh->do("INSERT INTO test_case_texts VALUES(?,?,?,?,?,?)",
+ $dbh->do("INSERT INTO test_case_texts
+ (case_id, case_text_version, who, creation_ts, action, effect, setup, breakdown)
+ VALUES(?,?,?,?,?,?,?,?)",
undef, $key, ++$version, $author,
- $timestamp, $action, $effect);
+ $timestamp, $action, $effect, $setup, $breakdown);
$self->{'version'} = $version;
return $self->{'version'};
@@ -579,6 +642,43 @@ sub link_plan {
$dbh->do("INSERT INTO test_case_plans (plan_id, case_id)
VALUES (?,?)", undef, $plan_id, $case_id);
$dbh->bz_unlock_tables();
+
+ # Update the plans array to include new plan added.
+
+ push @{$self->{'plans'}}, Bugzilla::Testopia::TestPlan->new($plan_id);
+
+}
+
+
+=head2 unlink_plan
+
+Removes the link to the specified plan id.
+
+=cut
+
+sub unlink_plan {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ my ($plan_id) = @_;
+
+ $dbh->bz_lock_tables('test_case_plans WRITE', 'test_case_runs WRITE',
+ 'test_runs READ', 'test_plans READ');
+
+ if (!$self->can_unlink_plan($plan_id)){
+ $dbh->bz_unlock_tables();
+ return 0;
+ }
+
+ $dbh->do("DELETE FROM test_case_plans
+ WHERE plan_id = ?
+ AND case_id = ?", undef, $plan_id, $self->{'case_id'});
+
+ $dbh->bz_unlock_tables();
+
+ # Update the plans array.
+ delete $self->{'plans'};
+
+ return 1;
}
=head2 copy
@@ -604,7 +704,9 @@ sub copy {
my $key = $dbh->bz_last_key( 'test_cases', 'case_id' );
if ($copydoc){
- $self->store_text($key, Bugzilla->user->id, $self->text->{'action'}, $self->text->{'effect'}, $timestamp);
+ $self->store_text($key, Bugzilla->user->id, $self->text->{'action'},
+ $self->text->{'effect'}, $self->text->{'setup'},
+ $self->text->{'breakdown'}, $timestamp);
}
return $key;
@@ -621,15 +723,37 @@ sub check_alias {
my $self = shift;
my ($alias) = @_;
+ return unless $alias;
+ my $id = $self->{'case_id'} || '';
+ my $dbh = Bugzilla->dbh;
+ ($id) = $dbh->selectrow_array(
+ "SELECT case_id
+ FROM test_cases
+ WHERE alias = ?
+ AND case_id != ?",
+ undef, ($alias, $id));
+
+ return $id;
+}
+
+=head2 class_check_alias
+
+Checks if the given alias exists already. Returns the case_id of
+the matching test case if it does.
+
+=cut
+
+sub class_check_alias {
+ my ($alias) = @_;
+
return unless $alias;
my $dbh = Bugzilla->dbh;
my ($id) = $dbh->selectrow_array(
"SELECT case_id
FROM test_cases
- WHERE alias = ?
- AND case_id != ?",
- undef, ($alias, $self->{'case_id'}));
+ WHERE alias = ?",
+ undef, ($alias));
return $id;
}
@@ -662,6 +786,7 @@ sub update {
# Update the history
my $field_id = Bugzilla::Testopia::Util::get_field_id($field, "test_cases");
$dbh->do("INSERT INTO test_case_activity
+ (case_id, fieldid, who, changed, oldvalue, newvalue)
VALUES(?,?,?,?,?,?)",
undef, $self->{'case_id'}, $field_id, Bugzilla->user->id,
$timestamp, $self->{$field}, $newvalues->{$field});
@@ -711,6 +836,21 @@ sub history {
return $ref;
}
+sub last_changed {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ my ($date) = $dbh->selectrow_array(
+ "SELECT MAX(changed)
+ FROM test_case_activity
+ WHERE case_id = ?",
+ undef, $self->id);
+
+ return $self->{'creation_date'} unless $date;
+ return $date;
+
+}
+
=head2 lookup_status
Takes an ID of the status field and returns the value
@@ -764,6 +904,23 @@ sub lookup_category {
return $value;
}
+=head2 lookup_category_by_name
+
+Returns the id of the category name passed.
+
+=cut
+
+sub lookup_category_by_name {
+ my ($name) = @_;
+ my $dbh = Bugzilla->dbh;
+ my ($value) = $dbh->selectrow_array(
+ "SELECT category_id
+ FROM test_case_categories
+ WHERE name = ?",
+ undef, $name);
+ return $value;
+}
+
=head2 lookup_priority
Takes an ID of the priority field and returns the value
@@ -782,6 +939,24 @@ sub lookup_priority {
return $value;
}
+=head2 lookup_priority_by_name
+
+Returns the id of the priority name passed.
+
+=cut
+
+sub lookup_priority_by_value {
+ my ($value) = @_;
+ my $dbh = Bugzilla->dbh;
+ my ($id) = $dbh->selectrow_array(
+ "SELECT id
+ FROM priority
+ WHERE value = ?",
+ undef, $value);
+ return $id;
+}
+
+
=head2 lookup_default_tester
Takes an ID of the default_tester field and returns the value
@@ -991,11 +1166,37 @@ sub _generate_dep_tree {
my $deps = _get_dep_lists("dependson", "blocked", $case_id);
return unless scalar @$deps;
foreach my $id (@$deps){
- #print "ID $id";
$self->_generate_dep_tree($id);
push @{$self->{'dep_list'}}, $id
}
}
+
+=head2 can_unlink_plan
+
+Returns true if this test case can be unlinked from the given plan
+
+=cut
+
+sub can_unlink_plan {
+ my $self = shift;
+ my ($plan_id) = @_;
+
+ return 0 if (!UserInGroup('managetestplans'));
+
+ return 0 if scalar @{$self->plans} < 2;
+
+ my $dbh = Bugzilla->dbh;
+ my ($res) = $dbh->selectrow_array(
+ "SELECT 1
+ FROM test_case_runs
+ INNER JOIN test_runs ON test_case_runs.run_id = test_runs.run_id
+ WHERE test_runs.plan_id = ?
+ AND test_case_runs.case_id = ?",
+ undef, ($plan_id, $self->{'case_id'}));
+
+ return !$res;
+}
+
=head2 canedit
Returns true if the logged in user has rights to edit this test case.
@@ -1221,16 +1422,19 @@ sub components {
my $dbh = Bugzilla->dbh;
return $self->{'components'} if exists $self->{'components'};
#TODO: 2.22 use Bugzilla::Component
- my $comps = $dbh->selectall_arrayref(
- "SELECT comp.id, comp.name
+ my $comps = $dbh->selectcol_arrayref(
+ "SELECT comp.id
FROM components AS comp
JOIN test_case_components AS tcc ON tcc.component_id = comp.id
JOIN test_cases ON tcc.case_id = test_cases.case_id
WHERE test_cases.case_id = ?",
{'Slice' => {}}, $self->{'case_id'});
-
- $self->{'components'} = $comps;
+ my @comps;
+ foreach my $id (@$comps){
+ push @comps, Bugzilla::Component->new($id);
+ }
+ $self->{'components'} = \@comps;
return $self->{'components'};
}
@@ -1293,8 +1497,7 @@ sub bugs {
return $self->{'bugs'} if exists $self->{'bugs'};
my $ref = $dbh->selectcol_arrayref(
"SELECT DISTINCT bug_id
- FROM test_case_bugs b
- JOIN test_case_runs r ON r.case_run_id = b.case_run_id
+ FROM test_case_bugs
WHERE case_id = ?",
undef, $self->{'case_id'});
my @bugs;
@@ -1317,11 +1520,12 @@ sub text {
my $self = shift;
my $dbh = Bugzilla->dbh;
return $self->{'text'} if exists $self->{'text'};
- my $text = $dbh->selectrow_hashref("SELECT action, effect
- FROM test_case_texts
- WHERE case_id=? AND case_text_version=?",
- undef, $self->{'case_id'},
- $self->version);
+ my $text = $dbh->selectrow_hashref(
+ "SELECT action, effect, setup, breakdown, who author_id, case_text_version AS version
+ FROM test_case_texts
+ WHERE case_id=? AND case_text_version=?",
+ undef, $self->{'case_id'},
+ $self->version);
$self->{'text'} = $text;
return $self->{'text'};
}
@@ -1409,7 +1613,21 @@ sub blocked_list {
my $ref = _get_dep_lists("dependson", "blocked", $self->{'case_id'});
$self->{'blocked_list'} = join(",", @$ref);
return $self->{'blocked_list'};
-}
+}
+
+=head2 blocked_list_uncached
+
+Returns a space separated list of test cases that are blocked by this test case.
+This method does not cache the blocked test cases so each call will result
+in a database read.
+
+=cut
+
+sub blocked_list_uncached {
+ my ($self) = @_;
+ my $ref = _get_dep_lists("dependson", "blocked", $self->{'case_id'});
+ return join(" ", @$ref)
+}
=head2 dependson
@@ -1439,6 +1657,20 @@ sub dependson_list {
return $self->{'dependson_list'};
}
+=head2 dependson_list_uncached
+
+Returns a space separated list of test cases that depend on this test case.
+This method does not cache the dependent test cases so each call will result
+in a database read.
+
+=cut
+
+sub dependson_list_uncached {
+ my ($self) = @_;
+ my $ref = _get_dep_lists("blocked", "dependson", $self->{'case_id'});
+ return join(" ", @$ref)
+}
+
=head1 TODO
diff --git a/mozilla/webtools/testopia/Bugzilla/Testopia/TestCaseRun.pm b/mozilla/webtools/testopia/Bugzilla/Testopia/TestCaseRun.pm
index da8836507fa..320e5dbaa46 100644
--- a/mozilla/webtools/testopia/Bugzilla/Testopia/TestCaseRun.pm
+++ b/mozilla/webtools/testopia/Bugzilla/Testopia/TestCaseRun.pm
@@ -50,6 +50,7 @@ use Bugzilla::User;
use Bugzilla::Config;
use Bugzilla::Testopia::Util;
use Bugzilla::Testopia::Constants;
+use Date::Format;
###############################
#### Initialization ####
@@ -81,6 +82,7 @@ use constant DB_COLUMNS => qw(
test_case_runs.case_run_status_id
test_case_runs.case_text_version
test_case_runs.build_id
+ test_case_runs.environment_id
test_case_runs.notes
test_case_runs.close_date
test_case_runs.iscurrent
@@ -161,10 +163,11 @@ sub store {
my $self = shift;
my $dbh = Bugzilla->dbh;
$dbh->do("INSERT INTO test_case_runs ($columns)
- VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", undef,
+ VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", undef,
(undef, $self->{'run_id'}, $self->{'case_id'}, $self->{'assignee'},
undef, IDLE, $self->{'case_text_version'},
- $self->{'build_id'}, $self->{'notes'}, undef, 1, 0));
+ $self->{'build_id'}, $self->{'environment_id'},
+ undef, undef, 1, 0));
my $key = $dbh->bz_last_key( 'test_case_runs', 'case_run_id' );
return $key;
@@ -180,7 +183,10 @@ sub clone {
my $self = shift;
my ($fields) = @_;
my $dbh = Bugzilla->dbh;
- $self->{'build_id'} = $fields->{'build_id'};
+ my $note = "Build or Environment changed. Resetting to IDLE.";
+ $self->append_note($note);
+ $self->{'build_id'} = $fields->{'build_id'} if $fields->{'build_id'};
+ $self->{'environment_id'} = $fields->{'environment_id'} if $fields->{'environment_id'};
my $entry = $self->store;
$self->set_as_current($entry);
return $entry;
@@ -188,28 +194,54 @@ sub clone {
=head2 check_exists
-Checks for an existing entry with the same build for this case and run
-and returns the id if it is found
+Checks for an existing entry with the same build and environment for this
+case and run and returns the id if it is found
=cut
sub check_exists {
my $self = shift;
- my ($run_id, $case_id, $build_id) = @_;
+ my ($run_id, $case_id, $build_id, $env_id) = @_;
$run_id ||= $self->{'run_id'};
$case_id ||= $self->{'case_id'};
$build_id ||= $self->{'build_id'};
-
+ $env_id ||= $self->{'environment_id'};
+
my $dbh = Bugzilla->dbh;
my ($is) = $dbh->selectrow_array(
+ "SELECT case_run_id
+ FROM test_case_runs
+ WHERE run_id = ?
+ AND case_id = ?
+ AND build_id = ?
+ AND environment_id = ?",
+ undef, ($run_id, $case_id, $build_id, $env_id));
+
+ return $is;
+
+}
+
+=head2 lookup_case_run_id
+
+Checks for an existing entry based on run, case, and build,
+and returns the id if it is found
+
+=cut
+
+sub lookup_case_run_id
+{
+ my ($run_id, $case_id, $build_id) = @_;
+
+ my $dbh = Bugzilla->dbh;
+
+ my ($case_run_id) = $dbh->selectrow_array(
"SELECT case_run_id
FROM test_case_runs
WHERE run_id = ? AND case_id = ? AND build_id = ?",
undef, ($run_id, $case_id, $build_id));
- return $is;
-
+ return $case_run_id;
}
=head2 update
@@ -227,9 +259,9 @@ sub update {
if ($self->is_closed_status($fields->{'case_run_status_id'})){
$fields->{'close_date'} = Bugzilla::Testopia::Util::get_time_stamp();
}
- my ($is) = $self->check_exists($self->run_id, $self->case_id, $fields->{'build_id'});
+ my ($is) = $self->check_exists($self->run_id, $self->case_id, $fields->{'build_id'}, $fields->{'environment_id'});
- if ($fields->{'build_id'} != $self->{'build_id'}){
+ if ($fields->{'build_id'} != $self->{'build_id'} || $fields->{'environment_id'} != $self->{'environment_id'}){
if ($is){
return $is;
}
@@ -311,6 +343,19 @@ sub set_build {
$self->{'build_id'} = $build_id;
}
+=head2 set_environment
+
+Sets the environment on a case-run
+
+=cut
+
+sub set_environment {
+ my $self = shift;
+ my ($env_id) = @_;
+ $self->_update_fields({'environment_id' => $env_id});
+ $self->{'environment_id'} = $env_id;
+}
+
=head2 set_status
Sets the status on a case-run and updates the close_date and testedby
@@ -356,17 +401,56 @@ sub set_assignee {
$self->_update_fields({'assignee' => $user_id});
}
-=head2 set_note
+=head2 lookup_status
+
+Returns the status name of the given case_run_status_id
+
+=cut
+
+sub lookup_status {
+ my $self = shift;
+ my ($status_id) = @_;
+ my $dbh = Bugzilla->dbh;
+ my ($status) = $dbh->selectrow_array(
+ "SELECT name
+ FROM test_case_run_status
+ WHERE case_run_status_id = ?",
+ undef, $status_id);
+ return $status;
+}
+
+=head2 lookup_status_by_name
+
+Returns the id of the status name passed.
+
+=cut
+
+sub lookup_status_by_name {
+ my ($name) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my ($value) = $dbh->selectrow_array(
+ "SELECT case_run_status_id
+ FROM test_case_run_status
+ WHERE name = ?",
+ undef, $name);
+ return $value;
+}
+
+=head2 append_note
Updates the notes field for the case-run
=cut
-sub set_note {
+sub append_note {
my $self = shift;
- my ($note, $append) = @_;
- if ($append){
- my $note = $self->notes . "\n" . $note;
+ my ($note) = @_;
+ return unless $note;
+ my $timestamp = time2str("%c", time());
+ $note = "$timestamp: $note";
+ if ($self->{'notes'}){
+ $note = $self->{'notes'} . "\n" . $note;
}
$self->_update_fields({'notes' => $note});
$self->{'notes'} = $note;
@@ -456,18 +540,35 @@ sub attach_bug {
my $dbh = Bugzilla->dbh;
$dbh->bz_lock_tables('test_case_bugs WRITE');
- my ($is) = $dbh->selectrow_array(
+ my ($exists) = $dbh->selectrow_array(
"SELECT bug_id
FROM test_case_bugs
WHERE case_run_id=?
AND bug_id=?",
undef, ($caserun_id, $bug));
- if ($is) {
+ if ($exists) {
$dbh->bz_unlock_tables();
return;
}
- $dbh->do("INSERT INTO test_case_bugs (bug_id, case_run_id)
- VALUES(?,?)", undef, ($bug, $self->{'case_run_id'}));
+ my ($check) = $dbh->selectrow_array(
+ "SELECT bug_id
+ FROM test_case_bugs
+ WHERE case_id=?
+ AND bug_id=?
+ AND case_run_id=?",
+ undef, ($caserun_id, $bug, undef));
+
+ if ($check){
+ $dbh->do("UPDATE test_case_bugs
+ SET test_case_run_id = ?
+ WHERE case_id = ?
+ AND bug_id = ?",
+ undef, ($bug, $self->{'case_run_id'}));
+ }
+ else{
+ $dbh->do("INSERT INTO test_case_bugs (bug_id, case_run_id, case_id)
+ VALUES(?,?,?)", undef, ($bug, $self->{'case_run_id'}, $self->{'case_id'}));
+ }
$dbh->bz_unlock_tables();
}
@@ -569,7 +670,6 @@ sub run_id { return $_[0]->{'run_id'}; }
sub testedby { return Bugzilla::User->new($_[0]->{'testedby'}); }
sub assignee { return Bugzilla::User->new($_[0]->{'assignee'}); }
sub case_text_version { return $_[0]->{'case_text_version'}; }
-sub notes { return $_[0]->{'notes'}; }
sub close_date { return $_[0]->{'close_date'}; }
sub iscurrent { return $_[0]->{'iscurrent'}; }
sub status_id { return $_[0]->{'case_run_status_id'}; }
@@ -577,6 +677,25 @@ sub sortkey { return $_[0]->{'sortkey'}; }
sub isprivate { return $_[0]->{'isprivate'}; }
sub updated_deps { return $_[0]->{'updated_deps'}; }
+=head2 notes
+
+Returns the cumulative notes of all caserun records of this case and run.
+
+=cut
+
+sub notes {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ my $notes = $dbh->selectcol_arrayref(
+ "SELECT notes
+ FROM test_case_runs
+ WHERE case_id = ? AND run_id = ?
+ ORDER BY case_run_id",
+ undef,($self->case_id, $self->run_id));
+
+ return join("\n", @$notes);
+}
+
=head2 run
Returns the TestRun object that this case-run is associated with
@@ -618,6 +737,19 @@ sub build {
return $self->{'build'};
}
+=head2 environment
+
+Returns the Build object that this case-run is associated with
+
+=cut
+
+sub environment {
+ my $self = shift;
+ return $self->{'environment'} if exists $self->{'environment'};
+ $self->{'environment'} = Bugzilla::Testopia::Environment->new($self->{'environment_id'});
+ return $self->{'environment'};
+}
+
=head2 status
Looks up the status name of the associated status_id for this object
@@ -817,7 +949,17 @@ Returns true if the logged in user has rights to delete this case-run.
sub candelete {
my $self = shift;
- return $self->canedit && Param('allow-test-deletion');
+
+ return ($self->canedit
+ && Param('allow-test-deletion')
+ && scalar @{$self->get_case_run_list} == 1
+ && $self->status eq 'IDLE');
+}
+
+sub do_delete {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ $dbh->do("DELETE FROM test_case_runs WHERE case_run_id = ?", undef, $self->id);
}
=head1 SEE ALSO
diff --git a/mozilla/webtools/testopia/Bugzilla/Testopia/TestPlan.pm b/mozilla/webtools/testopia/Bugzilla/Testopia/TestPlan.pm
index 86d681e0327..c9225e9853f 100644
--- a/mozilla/webtools/testopia/Bugzilla/Testopia/TestPlan.pm
+++ b/mozilla/webtools/testopia/Bugzilla/Testopia/TestPlan.pm
@@ -66,7 +66,6 @@ use base qw(Exporter);
plan_id
product_id
author_id
- editor_id
type_id
default_product_version
name
@@ -79,7 +78,6 @@ use constant DB_COLUMNS => qw(
test_plans.plan_id
test_plans.product_id
test_plans.author_id
- test_plans.editor_id
test_plans.type_id
test_plans.default_product_version
test_plans.name
@@ -158,13 +156,13 @@ sub store {
my $dbh = Bugzilla->dbh;
my $timestamp = Bugzilla::Testopia::Util::get_time_stamp();
$dbh->do("INSERT INTO test_plans ($columns)
- VALUES (?,?,?,?,?,?,?,?,?)",
+ VALUES (?,?,?,?,?,?,?,?)",
undef, (undef, $self->{'product_id'}, $self->{'author_id'},
- $self->{'editor_id'}, $self->{'type_id'},
- $self->{'default_product_version'}, $self->{'name'},
+ $self->{'type_id'}, $self->{'default_product_version'}, $self->{'name'},
$timestamp, 1));
my $key = $dbh->bz_last_key( 'test_plans', 'plan_id' );
$self->store_text($key, $self->{'author_id'}, $self->text, $timestamp);
+ $self->{'plan_id'} = $key;
return $key;
}
@@ -186,7 +184,9 @@ sub store_text {
$text ||= '';
trick_taint($text);
my $version = $self->version || 0;
- $dbh->do("INSERT INTO test_plan_texts VALUES(?,?,?,?,?)",
+ $dbh->do("INSERT INTO test_plan_texts
+ (plan_id, plan_text_version, who, creation_ts, plan_text)
+ VALUES(?,?,?,?,?)",
undef, $key, ++$version, $author,
$timestamp, $text);
$self->{'version'} = $version;
@@ -208,10 +208,9 @@ sub clone {
my $dbh = Bugzilla->dbh;
my ($timestamp) = Bugzilla::Testopia::Util::get_time_stamp();
$dbh->do("INSERT INTO test_plans ($columns)
- VALUES (?,?,?,?,?,?,?,?,?)",
+ VALUES (?,?,?,?,?,?,?,?)",
undef, (undef, $self->{'product_id'}, $self->{'author_id'},
- $self->{'editor_id'}, $self->{'type_id'},
- $self->{'default_product_version'}, $name,
+ $self->{'type_id'}, $self->{'default_product_version'}, $name,
$timestamp, 1));
my $key = $dbh->bz_last_key( 'test_plans', 'plan_id' );
if ($store_doc){
@@ -241,6 +240,7 @@ sub toggle_archive {
WHERE plan_id = ?", undef, $newvalue, $self->{'plan_id'});
my $field_id = Bugzilla::Testopia::Util::get_field_id("isactive", "test_plans");
$dbh->do("INSERT INTO test_plan_activity
+ (plan_id, fieldid, who, changed, oldvalue, newvalue)
VALUES(?,?,?,?,?,?)",
undef, ($self->{'plan_id'}, $field_id, Bugzilla->user->id,
$timestamp, $oldvalue, $newvalue));
@@ -270,8 +270,8 @@ sub add_tag {
$dbh->bz_unlock_tables();
return 1;
}
- $dbh->do("INSERT INTO test_plan_tags VALUES(?,?)",
- undef, ($tag_id, $self->{'plan_id'}));
+ $dbh->do("INSERT INTO test_plan_tags(tag_id, plan_id, userid) VALUES(?,?,?)",
+ undef, ($tag_id, $self->{'plan_id'}), Bugzilla->user->id);
$dbh->bz_unlock_tables();
}
@@ -450,6 +450,103 @@ sub get_plan_types {
ORDER BY name",
{"Slice"=>{}});
return $types;
+
+}
+
+=head2 last_changed
+
+Returns the date of the last change in the history table
+
+=cut
+
+sub last_changed {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ my ($date) = $dbh->selectrow_array(
+ "SELECT MAX(changed)
+ FROM test_plan_activity
+ WHERE plan_id = ?",
+ undef, $self->id);
+
+ return $self->{'creation_date'} unless $date;
+ return $date;
+}
+
+=head2 lookup_plan_type
+
+Returns a type name matching the given type id
+
+=cut
+
+sub lookup_plan_type {
+ my $self = shift;
+ my $type_id = shift;
+ my $dbh = Bugzilla->dbh;
+
+ my $type = $dbh->selectrow_hashref(
+ "SELECT type_id AS id, name
+ FROM test_plan_types
+ WHERE type_id = ?",
+ undef, $type_id);
+
+ return $type;
+}
+
+=head2 check_plan_type
+
+Returns true if a type with the given name exists
+
+=cut
+
+sub check_plan_type {
+ my $self = shift;
+ my $name = shift;
+ my $dbh = Bugzilla->dbh;
+
+ my $type = $dbh->selectrow_hashref(
+ "SELECT 1
+ FROM test_plan_types
+ WHERE name = ?",
+ undef, $name);
+
+ return $type;
+}
+
+=head2 update_plan_type
+
+Update the given type
+
+=cut
+
+sub update_plan_type {
+ my $self = shift;
+ my ($type_id, $name) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $type = $dbh->do(
+ "UPDATE test_plan_types
+ SET name = ?
+ WHERE type_id = ?",
+ undef, ($name,$type_id));
+
+}
+
+=head2 add_plan_type
+
+Add the given type
+
+=cut
+
+sub add_plan_type {
+ my $self = shift;
+ my ($name) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $type = $dbh->do(
+ "INSERT INTO test_plan_types (type_id, name)
+ VALUES(?,?)",
+ undef, (undef, $name));
}
=head2 get_fields
@@ -551,6 +648,7 @@ sub update {
# Update the history
my $field_id = Bugzilla::Testopia::Util::get_field_id($field, "test_plans");
$dbh->do("INSERT INTO test_plan_activity
+ (plan_id, fieldid, who, changed, oldvalue, newvalue)
VALUES(?,?,?,?,?,?)",
undef, ($self->{'plan_id'}, $field_id, Bugzilla->user->id,
$timestamp, $self->{$field}, $newvalues->{$field}));
@@ -609,6 +707,25 @@ sub lookup_type {
undef, $id);
return $value;
}
+
+=head2 lookup_type_by_name
+
+Returns the id of the type name passed.
+
+=cut
+
+sub lookup_type_by_name {
+ my ($name) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my ($value) = $dbh->selectrow_array(
+ "SELECT type_id
+ FROM test_plan_types
+ WHERE name = ?",
+ undef, $name);
+ return $value;
+}
+
=head2 lookup_status
Takes an ID of the status field and returns the value
@@ -665,9 +782,8 @@ Returns true if the logged in user has rights to view this plan
sub canview {
my $self = shift;
- return $self->{'canview'} if exists $self->{'canview'};
- $self->{'canview'} = Bugzilla::Testopia::Util::can_view_product($self->product_id);
- return $self->{'canview'};
+ return 1 if (Bugzilla->user->id == $self->author->id);
+ return Bugzilla::Testopia::Util::can_view_product($self->product_id);
}
=head2 delete
@@ -703,10 +819,6 @@ Returns the product version for this object
Returns the product id for this object
-=head2 editor
-
-Returns a Bugzilla::User object representing the plan's last editor
-
=head2 author
Returns a Bugzilla::User object representing the plan author
@@ -729,7 +841,6 @@ sub id { return $_[0]->{'plan_id'}; }
sub creation_date { return $_[0]->{'creation_date'}; }
sub product_version { return $_[0]->{'default_product_version'}; }
sub product_id { return $_[0]->{'product_id'}; }
-sub editor { return Bugzilla::User->new($_[0]->{'editor_id'}); }
sub author { return Bugzilla::User->new($_[0]->{'author_id'}); }
sub name { return $_[0]->{'name'}; }
sub type_id { return $_[0]->{'type_id'}; }
diff --git a/mozilla/webtools/testopia/Bugzilla/Testopia/TestRun.pm b/mozilla/webtools/testopia/Bugzilla/Testopia/TestRun.pm
index 5e8f248072c..65138e2f2ed 100644
--- a/mozilla/webtools/testopia/Bugzilla/Testopia/TestRun.pm
+++ b/mozilla/webtools/testopia/Bugzilla/Testopia/TestRun.pm
@@ -161,7 +161,7 @@ sub calculate_percent_completed {
my $total = $idle + $run;
my $percent;
if ($total == 0) {
- $percent = 100;
+ $percent = 0;
} else {
$percent = $run*100/$total;
$percent = int($percent + 0.5);
@@ -226,8 +226,8 @@ sub add_tag {
$dbh->bz_unlock_tables;
return 1;
}
- $dbh->do("INSERT INTO test_run_tags VALUES(?,?)",
- undef, $tag_id, $self->{'run_id'});
+ $dbh->do("INSERT INTO test_run_tags(tag_id, run_id, userid) VALUES(?,?,?)",
+ undef, $tag_id, $self->{'run_id'}, Bugzilla->user->id);
$dbh->bz_unlock_tables;
return 0;
@@ -259,6 +259,7 @@ the test_case_runs table
sub add_case_run {
my $self = shift;
my ($case_id) = @_;
+ return 0 if $self->check_case($case_id);
my $case = Bugzilla::Testopia::TestCase->new($case_id);
my $caserun = Bugzilla::Testopia::TestCaseRun->new({
'run_id' => $self->{'run_id'},
@@ -266,6 +267,7 @@ sub add_case_run {
'assignee' => $case->default_tester->id,
'case_text_version' => $case->version,
'build_id' => $self->build->id,
+ 'environment_id' => $self->environment_id,
});
$caserun->store;
}
@@ -318,6 +320,7 @@ sub update {
# Update the history
my $field_id = Bugzilla::Testopia::Util::get_field_id($field, "test_runs");
$dbh->do("INSERT INTO test_run_activity
+ (run_id, fieldid, who, changed, oldvalue, newvalue)
VALUES(?,?,?,?,?,?)",
undef, ($self->{'run_id'}, $field_id, Bugzilla->user->id,
$timestamp, $self->{$field}, $newvalues->{$field}));
@@ -400,6 +403,25 @@ sub history {
return $ref;
}
+
+=head2 Check_case
+
+Checks if the given test case is already associated with this run
+
+=cut
+
+sub check_case {
+ my $self = shift;
+ my ($case_id) = @_;
+ my $dbh = Bugzilla->dbh;
+ my ($value) = $dbh->selectrow_array(
+ "SELECT case_run_id
+ FROM test_case_runs
+ WHERE case_id = ? AND run_id = ?",
+ undef, ($case_id, $self->{'run_id'}));
+ return $value;
+}
+
=head2 lookup_environment
Takes an ID of the envionment field and returns the value
@@ -418,6 +440,23 @@ sub lookup_environment {
return $value;
}
+=head2 lookup_environment_by_name
+
+Takes the name of an envionment and returns its id
+
+=cut
+
+sub lookup_environment_by_name {
+ my ($name) = @_;
+ my $dbh = Bugzilla->dbh;
+ my ($value) = $dbh->selectrow_array(
+ "SELECT environment_id
+ FROM test_environments
+ WHERE name = ?",
+ undef, $name);
+ return $value;
+}
+
=head2 lookup_build
Takes an ID of the build field and returns the value
@@ -454,6 +493,84 @@ sub lookup_manager {
return $value;
}
+=head2 last_changed
+
+Returns the date of the last change in the history table
+
+=cut
+
+sub last_changed {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ my ($date) = $dbh->selectrow_array(
+ "SELECT MAX(changed)
+ FROM test_run_activity
+ WHERE run_id = ?",
+ undef, $self->id);
+
+ return $self->{'creation_date'} unless $date;
+ return $date;
+}
+
+sub filter_case_categories {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ my $ids = $dbh->selectcol_arrayref(
+ "SELECT DISTINCT tcc.category_id
+ FROM test_case_categories AS tcc
+ JOIN test_cases ON test_cases.category_id = tcc.category_id
+ JOIN test_case_runs AS tcr ON test_cases.case_id = tcr.case_id
+ WHERE run_id = ?",
+ undef, $self->id);
+
+ my @categories;
+ foreach my $id (@$ids){
+ push @categories, Bugzilla::Testopia::Category->new($id);
+ }
+
+ return \@categories;
+}
+
+sub filter_builds {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ my $ids = $dbh->selectcol_arrayref(
+ "SELECT DISTINCT build_id
+ FROM test_case_runs
+ WHERE run_id = ?",
+ undef, $self->id);
+
+ my @builds;
+ foreach my $id (@$ids){
+ push @builds, Bugzilla::Testopia::Build->new($id);
+ }
+ return \@builds;
+}
+
+sub filter_components {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ my $ids = $dbh->selectcol_arrayref(
+ "SELECT DISTINCT components.id
+ FROM components
+ JOIN test_case_components AS tcc ON tcc.component_id = components.id
+ JOIN test_cases ON test_cases.case_id = tcc.case_id
+ JOIN test_case_runs AS tcr ON test_cases.case_id = tcr.case_id
+ WHERE run_id = ?",
+ undef, $self->id);
+
+ my @components;
+ foreach my $id (@$ids){
+ push @components, Bugzilla::Component->new($id);
+ }
+
+ return \@components;
+}
+
=head2 environments
Returns a reference to a list of Testopia::Environment objects.
@@ -960,6 +1077,39 @@ sub percent_complete {
return $self->{'percent_complete'};
}
+sub percent_passed {
+ my $self = shift;
+ my $notrun = $self->idle_count + $self->running_count + $self->paused_count + $self->failed_count + $self->blocked_count;
+ my $run = $self->passed_count;
+ $self->{'percent_passed'} = calculate_percent_completed($notrun, $run);
+ return $self->{'percent_passed'};
+}
+
+sub percent_failed {
+ my $self = shift;
+ my $notrun = $self->idle_count + $self->running_count + $self->paused_count + $self->passed_count + $self->blocked_count;
+ my $run = $self->failed_count;
+ $self->{'percent_failed'} = calculate_percent_completed($notrun, $run);
+ return $self->{'percent_failed'};
+}
+
+sub percent_blocked {
+ my $self = shift;
+ my $notrun = $self->idle_count + $self->running_count + $self->paused_count + $self->passed_count + $self->failed_count;
+ my $run = $self->blocked_count;
+ $self->{'percent_blocked'} = calculate_percent_completed($notrun, $run);
+ return $self->{'percent_blocked'};
+}
+
+sub percent_not_run {
+ my $self = shift;
+ my $notrun = $self->failed_count + $self->running_count + $self->paused_count + $self->passed_count;
+ my $run = $self->idle_count + $self->blocked_count;
+ $self->{'percent_not_run'} = calculate_percent_completed($notrun, $run);
+ return $self->{'percent_not_run'};
+}
+
+
=head2 current_caseruns
Returns a reference to a list of TestCaseRun objects that are the
diff --git a/mozilla/webtools/testopia/Bugzilla/Testopia/TestTag.pm b/mozilla/webtools/testopia/Bugzilla/Testopia/TestTag.pm
index 7b3b76df48e..87ccd878722 100644
--- a/mozilla/webtools/testopia/Bugzilla/Testopia/TestTag.pm
+++ b/mozilla/webtools/testopia/Bugzilla/Testopia/TestTag.pm
@@ -173,7 +173,8 @@ sub store {
undef, (undef, $self->{'tag_name'}));
$key = $dbh->bz_last_key( 'test_tags', 'tag_id' );
$dbh->bz_unlock_tables();
-
+
+ $self->{'tag_id'} = $key;
return $key;
}
diff --git a/mozilla/webtools/testopia/Bugzilla/Testopia/Xml.pm b/mozilla/webtools/testopia/Bugzilla/Testopia/Xml.pm
index 86b2a1d5081..a05d3651d69 100644
--- a/mozilla/webtools/testopia/Bugzilla/Testopia/Xml.pm
+++ b/mozilla/webtools/testopia/Bugzilla/Testopia/Xml.pm
@@ -38,9 +38,9 @@ package Bugzilla::Testopia::Xml;
#use fields qw(testplans testcases tags categories builds);
use strict;
+#use base qw(Exporter);
use Bugzilla::Config;
-use Bugzilla::Error;
use Bugzilla::Product;
use Bugzilla::Testopia::Attachment;
use Bugzilla::Testopia::Build;
@@ -54,6 +54,27 @@ use Bugzilla::Testopia::XmlTestCase;
use Bugzilla::User;
use Bugzilla::Util;
+use constant AUTOMATIC => "AUTOMATIC";
+use constant BLOCKS => "blocks";
+our $DATABASE_DESCRIPTION = "Database_descripiton";
+our $DATABASE_ID = "Database_id";
+use constant IGNORECASE => 1;
+use constant DEPENDSON => "dependson";
+use constant PCDATA => "#PCDATA";
+our $TESTOPIA_GT = "&testopia_gt;";
+our $TESTOPIA_LT = "&testopia_lt;";
+use constant TESTPLAN_REFERENCE => "testplan_reference";
+our $XML_DESCRIPTION = "Xml_description";
+our $XML_AMP = "&[Aa][Mm][Pp];";
+our $XML_APOS = "&[Aa][Pp][Oo][Ss];";
+our $XML_GT = "&[Gg][Tt];";
+our $XML_LT = "&[Ll][Tt];";
+our $XML_QUOT = "&[Qq][Uu][Oo][Tt];";
+
+use constant XMLREFERENCES_FIELDS => "Database_descripiton Database_id Xml_description";
+@Bugzilla::Testopia::Xml::EXPORT = qw($DATABASE_DESCRIPTION $DATABASE_ID $XML_DESCRIPTION);
+
+
use Class::Struct;
#
# The Xml structure is used to keep track of all new Testopia objects being created.
@@ -62,13 +83,22 @@ struct
(
'Bugzilla::Testopia::Xml',
{
+ # Array of attachments read for xml source.
attachments => '@',
- builds => '@',
+#TODO builds => '@',
+ # Array of categories read from xml source. Categories
categories => '@',
tags => '@',
- testenvironments => '@',
+#TODO testenvironments => '@',
+ # Array of testcases read from xml source.
testcases => '@',
+ # Hash of testcase aliases indexed by testcase id. Used during verfication to
+ # insure that alias does not exist and that new aliases are used in only one testcase.
+ testcase_aliases => '%',
+ # Array of testplans read from xml source.
testplans => '@',
+ # If true indicates some type of error has occurred processing the XML. Used to prevent
+ # updating Testopia with contents of current XML.
parse_error => '$',
}
);
@@ -76,7 +106,6 @@ struct
#TODO: Add this to checksetup
use Text::Diff;
-#use base qw(Exporter);
###############################
@@ -91,35 +120,11 @@ use Text::Diff;
#### Methods ####
###############################
-=head2 new
-
-Instantiate a new xml object. This takes a single argument
-
-Instantiate a new test plan. This takes a single argument
-either a test plan ID or a reference to a hash containing keys
-identical to a test plan's fields and desired values.
-
-=cut
-
-#sub new {
-# my Bugzilla::Testopia::Xml $self = fields::new(ref($invocant) || $invocant);
-# $self->{testplans} = [];
-# $self->{testcases} = [];
-# $self->{tags} = [];
-# $self->{categories} = [];
-# $self->{builds} = [];
-# return $self;
-#}
-
sub debug_display()
{
my ($self) = @_;
- my $attachments_array_ref = $self->attachments;
- my $categories_array_ref = $self->categories;
- my $testcases_array_ref = $self->testcases;
- my $testplans_array_ref = $self->testplans;
- foreach my $category ( @$categories_array_ref )
+ foreach my $category ( @{$self->categories})
{
bless($category,"Bugzilla::Testopia::Category");
@@ -131,7 +136,7 @@ sub debug_display()
print STDERR " Description " . $category->description() . "\n";
}
- foreach my $testplan ( @$testplans_array_ref )
+ foreach my $testplan ( @{$self->testplans} )
{
bless($testplan,"Bugzilla::Testopia::TestPlan");
@@ -147,10 +152,6 @@ sub debug_display()
print STDERR " Description " . $testplan->text() . "\n";
print STDERR " Default Product Version " . $testplan->product_version() . "\n";
print STDERR " Document " . $testplan->text() . "\n";
- my %editor = %{$testplan->editor()};
- my $editor_id = $editor{"id"};
- my $editor_login = $editor{"login"};
- print STDERR " Editor " . $editor_login . " (id=" . $editor_id . ", hash=" . $testplan->editor() . ")\n";
print STDERR " Is Active " . $testplan->isactive() . "\n";
print STDERR " Product " . $testplan->product_id() . "\n";
print STDERR " Type " . $testplan->type_id() . "\n";
@@ -173,13 +174,68 @@ sub debug_display()
}
}
- foreach my $testcase ( @$testcases_array_ref )
+ foreach my $testcase ( @{$self->testplans} )
{
bless($testcase,"Bugzilla::Testopia::XmlTestCase");
$testcase->debug_display();
}
}
+=head2 entity_replace_testopia
+
+Replace testopia entities in the string supplied.
+
+This method is called for any field that is stored in HTML format. The Testopia entities are
+provided to allow users to pass HTML tags.
+
+For example, if you want to store a < in Bold font, the following XML
+
+&testopia_lt;B&testopia_gt;<&testopia_lt;/B&testopia_gt;
+
+is passed to the HTML field as
+
+<
+
+=cut
+
+sub entity_replace_testopia
+{
+ my ($string) = @_;
+
+ return undef if ( ! defined $string );
+
+ $string =~ s/$TESTOPIA_GT/>/g;
+ $string =~ s/$TESTOPIA_LT//g;
+
+ return $string;
+}
+
+=head2 entity_replace_xml
+
+Replace xml entities in the string supplied.
+
+This method is called for any field that is not stored in HTML format. The source is XML so any
+XML entity will be in the &; format. These entities need to be converted back to their
+character representation.
+
+=cut
+
+sub entity_replace_xml
+{
+ my ($string) = @_;
+
+ return undef if ( ! defined $string );
+
+ $string =~ s/$XML_GT/>/g;
+ $string =~ s/$XML_LT/new();
+
+ my $twig = XML::Twig->new( load_DTD => 1, keep_encoding => 1 );
+
$twig->parse($xml);
my $root = $twig->root;
- my $attachments_array_ref = $self->attachments;
- my $categories_array_ref = $self->categories;
- my $testplans_array_ref = $self->testplans;
- my $testcases_array_ref = $self->testcases;
-
foreach my $twig_category ($root->children('category'))
{
my $product = new Bugzilla::Product({name => $twig_category->field('product')});
@@ -216,7 +269,7 @@ sub parse()
product_id => $product->id(),
description => $twig_category->field('description'),
});
- push @$categories_array_ref, $category;
+ push @{$self->categories}, $category;
}
my $testplan = Bugzilla::Testopia::TestPlan->new({ 'name' => 'dummy' });
@@ -230,7 +283,7 @@ sub parse()
foreach my $twig_testplan ($root->children('testplan'))
{
- my $author = $twig_testplan->field('author');
+ my $author = $twig_testplan->att('author');
# Bugzilla::User::match returns a array with a user hash. Fields of the hash needed
# are 'id' and 'login'.
my $author_ref = Bugzilla::User::match($author, 1, 0);
@@ -245,50 +298,43 @@ sub parse()
bless($author_user,"Bugzilla::User");
$author_id = $author_user->id();
}
- my $editor = $twig_testplan->field('editor');
- my $editor_ref = Bugzilla::User::match($editor, 1, 0);
- my $editor_id = -1;
- if ( ! $editor_ref->[0] )
+
+ my $product_id = Bugzilla::Testopia::TestPlan::lookup_product_by_name($twig_testplan->field('product'));
+ if ( ! defined($product_id) )
{
- $self->error("Cannot find editor '" . $editor . "' in test plan '" . $twig_testplan->field('name') . "'.");
- }
- else
- {
- my $editor_user = $editor_ref->[0];
- bless($editor_user,"Bugzilla::User");
- $editor_id = $editor_user->id();
+ $self->error("Cannot find product '" . $twig_testplan->field('product') . "' in test plan '" . $twig_testplan->field('name') . "'.");
}
+
$testplan = Bugzilla::Testopia::TestPlan->new({
- 'name' => $twig_testplan->field('name'),
- 'product_id' => Bugzilla::Testopia::TestPlan::lookup_product_by_name($twig_testplan->field('product')),
- 'default_product_version' => $twig_testplan->field('productversion'),
- 'type_id' => $plantype_ids{$twig_testplan->field('type')},
- 'text' => $twig_testplan->field('document'),
- 'author_id' => $author_id,
- 'editor_id' => $editor_id,
- 'isactive' => $twig_testplan->field('archive'),
- 'creation_date' => $twig_testplan->field('created')
+ 'name' => entity_replace_xml($twig_testplan->field('name')),
+ 'product_id' => $product_id,
+ 'default_product_version' => entity_replace_xml($twig_testplan->field('productversion')),
+ 'type_id' => $plantype_ids{$twig_testplan->att('type')},
+ 'text' => entity_replace_testopia($twig_testplan->field('document')),
+ 'author_id' => $author_id,
+ 'isactive' => entity_replace_xml($twig_testplan->field('archive')),
+ 'creation_date' => entity_replace_xml($twig_testplan->field('created'))
});
- push @$testplans_array_ref, $testplan;
+ push @{$self->testplans}, $testplan;
my @tags = $twig_testplan->children('tag');
foreach my $twig_tag (@tags)
{
- push @{$self->tags}, $twig_tag->text();
+ push @{$self->tags}, entity_replace_xml($twig_tag->text());
}
my @attachments = $twig_testplan->children('attachment');
foreach my $twig_attachments (@attachments)
{
my $attachment = Bugzilla::Testopia::Attachment->new({
- 'description' => $twig_attachments->field('description'),
- 'filename' => $twig_attachments->field('filename'),
- 'submitter_id' => $twig_attachments->field('submitter'),
- 'mime_type' => $twig_attachments->field('mimetype'),
- 'creation_ts' => $twig_attachments->field('created'),
- 'data' => $twig_attachments->field('data')
+ 'description' => entity_replace_xml($twig_attachments->field('description')),
+ 'filename' => entity_replace_xml($twig_attachments->field('filename')),
+ 'submitter_id' => entity_replace_xml($twig_attachments->field('submitter')),
+ 'mime_type' => entity_replace_xml($twig_attachments->field('mimetype')),
+ 'creation_ts' => entity_replace_xml($twig_attachments->field('created')),
+ 'data' => entity_replace_xml($twig_attachments->field('data'))
});
- push @$attachments_array_ref, $attachment;
+ push @{$self->attachments}, $attachment;
#$testplan->add_tag($tag->id());
}
}
@@ -309,14 +355,15 @@ sub parse()
}
foreach my $twig_testcase ($root->children('testcase'))
{
- my $author = $twig_testcase->field('author');
+ my $summary = entity_replace_xml($twig_testcase->field('summary')) || undef;
+ my $author = $twig_testcase->att('author');
# Bugzilla::User::match returns a array with a user hash. Fields of the hash needed
# are 'id' and 'login'.
my $author_ref = Bugzilla::User::match($author, 1, 0);
my $author_id = -1;
if ( ! $author_ref->[0] )
{
- $self->error("Cannot find author '" . $author . "' in test case '" . $twig_testcase->field('summary') . "'.");
+ $self->error("Cannot find author '" . $author . "' in test case '" . $summary . "'.");
}
else
{
@@ -324,14 +371,14 @@ sub parse()
bless($author_user,"Bugzilla::User");
$author_id = $author_user->id();
}
- my $tester = $twig_testcase->field('defaulttester');
+ my $tester = entity_replace_xml($twig_testcase->field('defaulttester'));
# Bugzilla::User::match returns a array with a user hash. Fields of the hash needed
# are 'id' and 'login'.
my $tester_ref = Bugzilla::User::match($tester, 1, 0);
my $tester_id = -1;
if ( ! $tester_ref->[0] )
{
- $self->error("Cannot find default tester '" . $tester . "' in test case '" . $twig_testcase->field('summary') . "'.");
+ $self->error("Cannot find default tester '" . $tester . "' in test case '" . $summary . "'.");
}
else
{
@@ -339,74 +386,158 @@ sub parse()
bless($tester_user,"Bugzilla::User");
$tester_id = $tester_user->id();
}
- my $status_id = Bugzilla::Testopia::TestCase::lookup_status_by_name($twig_testcase->field('status'));
- $self->error("Cannot find status '" . $twig_testcase->field('status') . "' in test case '" . $twig_testcase->field('summary') . "'.") if ( ! defined($status_id) );
+ my $status_id = Bugzilla::Testopia::TestCase::lookup_status_by_name($twig_testcase->att('status'));
+ $self->error("Cannot find status '" . $twig_testcase->att('status') . "' in test case '" . $summary . "'.") if ( ! defined($status_id) );
my $xml_testcase = new Bugzilla::Testopia::XmlTestCase;
+ $xml_testcase->blocks(Bugzilla::Testopia::XmlReferences->new(IGNORECASE, XMLREFERENCES_FIELDS));
+ $xml_testcase->dependson(Bugzilla::Testopia::XmlReferences->new(IGNORECASE, XMLREFERENCES_FIELDS));
+ $xml_testcase->testplan(Bugzilla::Testopia::XmlReferences->new(IGNORECASE, XMLREFERENCES_FIELDS));
+ push @{$self->testcases}, $xml_testcase;
+ my $alias = entity_replace_xml($twig_testcase->field('alias')) || undef;
+
$xml_testcase->testcase(Bugzilla::Testopia::TestCase->new({
- 'action' => $twig_testcase->field('action'),
- 'alias' => $twig_testcase->field('alias') || undef,
- 'arguments' => $twig_testcase->field('arguments'),
+ 'action' => entity_replace_testopia($twig_testcase->field('action')),
+ 'alias' => $alias,
+ 'arguments' => entity_replace_xml($twig_testcase->field('arguments')),
'author_id' => $author_id,
- #TODO: Blocks
'blocks' => undef,
-
+ 'breakdown' => entity_replace_testopia($twig_testcase->field('breakdown')),
'case_status_id' => $status_id,
'category_id' => undef,
- 'creation_date' => $twig_testcase->field('created'),
+ 'creation_date' => $twig_testcase->att('created'),
'default_tester_id' => $tester_id,
- #TODO: Depends On
'dependson' => undef,
- 'effect' => $twig_testcase->field('effect'),
- 'expectedresults' => $twig_testcase->field('expectedresults'),
- 'isautomated' => $twig_testcase->field('isautomated'),
+ 'effect' => entity_replace_testopia($twig_testcase->field('expectedresults')),
+ 'isautomated' => ( uc $twig_testcase->att('automated') ) eq AUTOMATIC,
'plans' => undef,
- 'priority_id' => $priority_ids{$twig_testcase->field('priority')},
- 'requirement' => $twig_testcase->field('requirement'),
- 'script' => $twig_testcase->field('script'),
- 'summary' => $twig_testcase->field('summary'),
+ 'priority_id' => $priority_ids{$twig_testcase->att('priority')},
+ 'requirement' => entity_replace_xml($twig_testcase->field('requirement')),
+ 'setup' => entity_replace_testopia($twig_testcase->field('setup')),
+ 'script' => entity_replace_xml($twig_testcase->field('script')),
+ 'summary' => $summary,
}));
- # Action field is html format.
- my $action = $twig_testcase->field('action');
- $action =~ s/\n/ /g;
- $xml_testcase->action($action);
- $xml_testcase->category($twig_testcase->field('category'));
- # Expectedresults field is html format.
- my $expectedresults = $twig_testcase->field('expectedresults');
- $expectedresults =~ s/\n/ /g;
- $xml_testcase->expectedresults($expectedresults);
- push @$testcases_array_ref, $xml_testcase;
-
+ foreach my $twig_testplan_reference ( $twig_testcase->children(TESTPLAN_REFERENCE) )
+ {
+ my $testplan_reference = $twig_testplan_reference->children_text(PCDATA);
+ if ( $testplan_reference eq "" )
+ {
+ $self->error("No test plan included for type '"
+ . $twig_testplan_reference->att('type')
+ . "' in test case '" . $twig_testcase->field('summary') . "'." );
+ }
+ elsif ( ! $xml_testcase->testplan->add($twig_testplan_reference->att('type'),entity_replace_xml($testplan_reference)) )
+ {
+ $self->error("Do not know how to handle test plan of type '"
+ . $twig_testplan_reference->att('type')
+ . "' in test case '" . $twig_testcase->field('summary') . "'."
+ . "\nKnow types are: (" . uc XMLREFERENCES_FIELDS . ").");
+ }
+ }
+ # Keep track of this testcase's alias. Used during verification to insure aliases are unique.
+ $self->testcase_aliases(entity_replace_xml($twig_testcase->field('summary')),$alias) if ( defined $alias );
+ # Keep track of this testcase's category. To create a category at this time would require
+ # getting the product from the Test Plan that this Test Case is associated with. The category
+ # will created when each Test Case is stored.
+ $xml_testcase->category(entity_replace_xml($twig_testcase->field('categoryname')));
+
my @tags = $twig_testcase->children('tag');
- foreach my $twig_tag (@tags)
+ foreach my $twig_tag ( @tags )
{
- $xml_testcase->add_tag($twig_tag->text());
+ $xml_testcase->add_tag(entity_replace_xml($twig_tag->text()));
}
- #my @components;
- #foreach my $id (@comps){
- # detaint_natural($id);
- # validate_selection($id, 'id', 'components');
- # push @components, $id;
- #}
-
- #$case->add_component($_) foreach (@components);
+ my @components = $twig_testcase->children('component');
+ foreach my $twig_component ( @components )
+ {
+ $xml_testcase->add_component(entity_replace_xml($twig_component->text()));
+ }
+
+ foreach my $twig_blocks ( $twig_testcase->children(BLOCKS) )
+ {
+ if ( ! $xml_testcase->blocks->add($twig_blocks->att('type'),entity_replace_xml($twig_blocks->children_text(PCDATA))) )
+ {
+ $self->error("Do not know how to handle a blocking test case of type '" . $twig_blocks->att('type') . "' in test case '" . $xml_testcase->testcase->summary() . "'.")
+ }
+ }
+
+ foreach my $twig_dependson ( $twig_testcase->children(DEPENDSON) )
+ {
+ if ( ! $xml_testcase->dependson->add($twig_dependson->att('type'),entity_replace_xml($twig_dependson->children_text(PCDATA))) )
+ {
+ $self->error("Do not know how to handle dependency of type '" . $twig_dependson->att('type') . "' in test case '" . entity_replace_xml($xml_testcase->testcase->summary()) . "'.")
+ }
+ }
+ }
+
+ #
+ # Start of data integrity check.
+ #
+ # Run through the Test Plans and Test Cases looking for integrity errors.
+ #
+
+ # Check for duplicate aliases. Loop though all testcases that have a alias.
+ my %used_alias;
+ my %duplicate_alias;
+ foreach my $summary ( keys %{$self->testcase_aliases} )
+ {
+ # Get the alias.
+ my $alias = $self->testcase_aliases($summary);
+ # Is the alias used by a testcase in the database already? If so add it to the duplicate list
+ # and move onto next testcase.
+ my $alias_testcase_id = Bugzilla::Testopia::TestCase::class_check_alias($alias);
+ if ( $alias_testcase_id )
+ {
+ $duplicate_alias{$alias} = $alias_testcase_id;
+ next;
+ }
+ # Is the alias in the used_alias array?
+ if ( defined( $used_alias{$alias} ) )
+ {
+ # If so then another testcase being created also used the alias. Add the alias to the
+ # duplicate list.
+ $duplicate_alias{$alias} = "import";
+ }
+ else
+ {
+ # Alias has not been seen yet. Add it to the used_alias list to keep track of it.
+ $used_alias{$alias} = "";
+ }
+ }
+ # The @duplicate_alias list contains aliases used by more that one test case. Display them and set
+ # error condition
+ foreach my $summary ( keys %{$self->testcase_aliases} )
+ {
+ my $alias = $self->testcase_aliases($summary);
+ if ( exists $duplicate_alias{$alias} )
+ {
+ my $error_message = "Test Case '" . $summary . "' has a non-unique alias '" . $alias . "'.";
+ if ( $duplicate_alias{$alias} ne "import" )
+ {
+ $error_message .= " Test Case " . $duplicate_alias{$alias} . " already uses the alias '" . $alias . "'.";
+ }
+ else
+ {
+ $error_message .= " Additional test cases being imported are using the alias '" . $alias . "'.";
+ }
+ $self->error($error_message);
+ }
}
- #TODO Verfication of data.
- #Test cases require a category or store will fail.
-
- # Don't really care about the value of parse_error. If it has been defined then a error occurred
- # parsing the XML.
+ #
+ # Start of data store.
+ #
+ # No data has been written prior to this point. If parse_error has not been set the XML is valid
+ # and no integrity errors were found. It's time to start storing the Test Plans and Test Cases.
+ #
+
if ( ! defined $self->parse_error )
{
-# $self->debug_display();
- my @testplanarray;
-
foreach my $testplan ( @{$self->testplans} )
{
my $plan_id = $testplan->store();
$testplan->{'plan_id'} = $plan_id;
+ print "Created Test Plan $plan_id: " . $testplan->name() . "\n";
foreach my $asciitag ( @{$self->tags} )
{
my $classtag = Bugzilla::Testopia::TestTag->new({'tag_name' => $asciitag});
@@ -414,20 +545,30 @@ sub parse()
$testplan->{'tag_id'} = $tagid;
$testplan->add_tag($tagid);
}
-
- push @testplanarray, $testplan;
}
+ # Store the testcases.
foreach my $testcase ( @{$self->testcases} )
{
bless($testcase,"Bugzilla::Testopia::XmlTestCase");
- my $result = $testcase->store($testplan->{'author_id'},@testplanarray);
+ my $result = $testcase->store(@{$self->testplans});
if ( $result ne "" )
{
$self->error($result);
}
+ else
+ {
+ print "Created Test Case " . $testcase->testcase->id() . ": " . $testcase->testcase->summary() . "\n";
+ }
+ }
+ # Now that each testcase has been stored we loop though them again and create
+ # relationships like blocks or dependson.
+ foreach my $testcase ( @{$self->testcases} )
+ {
+ $testcase->store_relationships(@{$self->testcases});
}
}
+
$twig->purge;
}
diff --git a/mozilla/webtools/testopia/Bugzilla/Testopia/XmlReferences.pm b/mozilla/webtools/testopia/Bugzilla/Testopia/XmlReferences.pm
new file mode 100644
index 00000000000..27ac20acd36
--- /dev/null
+++ b/mozilla/webtools/testopia/Bugzilla/Testopia/XmlReferences.pm
@@ -0,0 +1,101 @@
+# -*- 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 Test Runner System.
+#
+# The Initial Developer of the Original Code is Maciej Maczynski.
+# Portions created by Maciej Maczynski are Copyright (C) 2001
+# Maciej Maczynski. All Rights Reserved.
+#
+# Contributor(s): David Koenig
+
+=head1 NAME
+
+Bugzilla::Testopia::XmlReferences - Testopia XmlReferences object
+
+=head1 DESCRIPTION
+
+This module maintains references to objects while the XML data is
+being imported. Test plans and Test cases can be referenced by
+database id, database description or XML description. The references
+are stored here and processed as needed.
+
+=head1 SYNOPSIS
+
+use Bugzilla::Testopia::XmlReferences;
+
+
+=cut
+
+package Bugzilla::Testopia::XmlReferences;
+
+use constant IGNORECASE => "ignorecase";
+
+#use strict;
+
+sub new
+{
+ my ($invocant,$ignorecase,$fields) = @_;
+
+ my $class = ref($invocant) || $invocant;
+ my $self = {};
+ bless($self, $class);
+ $self{IGNORECASE} = $ignorecase;
+ for my $field ( split(/ /, $fields) )
+ {
+ $field = uc $field if ( $self{IGNORECASE} );
+ $self->{$field} = [];
+ }
+ return $self;
+}
+
+sub add
+{
+ my ($self, $type, $object) = @_;
+
+ $type = uc $type if ( $self{IGNORECASE} );
+
+ return 0 if ( ! exists $self->{$type} );
+
+ push @{$self->{$type}}, $object;
+}
+
+sub display
+{
+ my ($self) = @_;
+
+ print "display() self=" . $self . "\n";
+ foreach $key (keys %$self)
+ {
+ if ( defined $self->{$key} )
+ {
+ print "display() key=$key value=" . $self->{$key} . "\n";
+ }
+ else
+ {
+ print "display() key=$key value=undefined\n";
+ }
+ }
+}
+
+sub get
+{
+ my ($self, $type) = @_;
+
+ $type = uc $type if ( $self{IGNORECASE} );
+
+ return 0 if ( ! exists $self->{$type} );
+
+ return $self->{$type};
+}
+
+1;
diff --git a/mozilla/webtools/testopia/Bugzilla/Testopia/XmlTestCase.pm b/mozilla/webtools/testopia/Bugzilla/Testopia/XmlTestCase.pm
index dc6c21f3726..b12b7280856 100644
--- a/mozilla/webtools/testopia/Bugzilla/Testopia/XmlTestCase.pm
+++ b/mozilla/webtools/testopia/Bugzilla/Testopia/XmlTestCase.pm
@@ -48,6 +48,7 @@ use Bugzilla::Testopia::TestPlan;
use Bugzilla::Testopia::TestRun;
use Bugzilla::Testopia::TestTag;
use Bugzilla::Testopia::Util;
+use Bugzilla::Testopia::XmlReferences;
use Bugzilla::User;
use Bugzilla::Util;
@@ -62,129 +63,224 @@ struct
(
'Bugzilla::Testopia::XmlTestCase',
{
- action => '$',
- blocks => '@',
- category => '$',
- dependson => '@',
- expectedresults => '$',
- tags => '@',
- testcase => 'Bugzilla::Testopia::TestCase',
+# action => '$',
+ blocks => 'Bugzilla::Testopia::XmlReferences',
+ category => '$',
+ components => '@',
+ dependson => 'Bugzilla::Testopia::XmlReferences',
+# expectedresults => '$',
+ tags => '@',
+ testcase => 'Bugzilla::Testopia::TestCase',
+ testplan => 'Bugzilla::Testopia::XmlReferences',
}
);
+sub add_tag()
+{
+ my ($self,$tag) = @_;
+
+ push @{$self->tags}, $tag;
+}
+
+sub add_component()
+{
+ my ($self,$component) = @_;
+
+ push @{$self->components}, $component;
+}
+
sub debug_display()
{
my ($self) = @_;
my $display_variable = "";
+ my %text = %{$self->testcase->text()} if ( defined $self->testcase->text() );
print STDERR "Testcase: " . $self->testcase->summary() . "\n";
- my $testcase_id = "null";
- $testcase_id = $self->testcase->id() if ( $self->testcase->id() );
- print STDERR " ID " . $testcase_id . "\n";
- print STDERR " Action\n";
- my @results = split(/\n/,$self->action);
+ my $testcase_id = "null";
+ $testcase_id = $self->testcase->id() if ( $self->testcase->id() );
+ print STDERR " ID " . $testcase_id . "\n";
+ print STDERR " Action\n";
+ if ( defined $text{'action'} )
+ {
+ my @results = split(/\n/,$text{'action'});
foreach my $result (@results)
{
print STDERR " $result\n";
}
- my $alias = "null";
- $alias = $self->testcase-alias() if ( $self->testcase->alias() );
- print STDERR " Alias " . $alias . "\n";
- my %author = %{$self->testcase->author()};
- my $author_id = $author{"id"};
- my $author_login = $author{"login"};
- print STDERR " Author " . $author_login . " (id=" . $author_id . ", hash=" . $self->testcase->author() . ")\n";
- foreach my $loop ( @{$self->blocks} )
- {
- $display_variable .= $loop . ",";
- }
- chop $display_variable;
- print STDERR " Blocks " . $display_variable . "\n";
- $display_variable = "";
- print STDERR " Category " . $self->category . "\n";
- print STDERR " Creation Date " . $self->testcase->creation_date() . "\n";
- foreach my $loop ( @{$self->blocks} )
- {
- $display_variable .= $loop . ",";
- }
- chop $display_variable;
- print STDERR " Dependson " . $display_variable . "\n";
- $display_variable = "";
- print STDERR " Expected Results\n";
- @results = split(/\n/,$self->expectedresults);
+ }
+ my $alias = "null";
+ $alias = $self->testcase-alias() if ( $self->testcase->alias() );
+ print STDERR " Alias " . $alias . "\n";
+ my %author = %{$self->testcase->author()};
+ my $author_id = $author{"id"};
+ my $author_login = $author{"login"};
+ print STDERR " Author " . $author_login . " (id=" . $author_id . ", hash=" . $self->testcase->author() . ")\n";
+ print STDERR " Blocks " . $self->blocks->display() . "\n";
+ print STDERR " Category " . $self->category . "\n";
+ $display_variable = $self->testcase->creation_date();
+ if ( defined $display_variable )
+ {
+ print STDERR " Creation Date " . $display_variable . "\n";
+ }
+ print STDERR " Depends On " . $self->dependson->display() . "\n";
+ print STDERR " Expected Results\n";
+ if ( defined $text{'effect'} )
+ {
+ my @results = split(/\n/,$text{'effect'});
foreach my $result (@results)
{
print STDERR " $result\n";
}
- print STDERR " Summary " . $self->testcase->summary() . "\n";
- #TODO:print STDERR " Default Product Version " . $self->testcase->product_version() . "\n";
- #TODO:print STDERR " Document " . $self->testcase->text() . "\n";
- my %tester = %{$self->testcase->default_tester()};
- my $tester_id = $tester{"id"};
- my $tester_login = $tester{"login"};
- print STDERR " Tester " . $tester_login . " (id=" . $tester_id . ", hash=" . $self->testcase->default_tester() . ")\n";
- print STDERR " Is Automated " . $self->testcase->isautomated() . "\n";
- #TODO:print STDERR " Plans " . $self->testcase->plans() . "\n";
- #TODO:print STDERR " Priority " . $self->testcase->priority_id() . "\n";
- #TODO:print STDERR " Product " . $self->testcase->product_id() . "\n";
- print STDERR " Requirement " . $self->testcase->requirement() . "\n";
+ }
+ print STDERR " Summary " . $self->testcase->summary() . "\n";
+ #TODO:print STDERR " Default Product Version " . $self->testcase->product_version() . "\n";
+ #TODO:print STDERR " Document " . $self->testcase->text() . "\n";
+ my %tester = %{$self->testcase->default_tester()};
+ my $tester_id = $tester{"id"};
+ my $tester_login = $tester{"login"};
+ print STDERR " Tester " . $tester_login . " (id=" . $tester_id . ", hash=" . $self->testcase->default_tester() . ")\n";
+ print STDERR " Is Automated " . $self->testcase->isautomated() . "\n";
+ #TODO:print STDERR " Plans " . $self->testcase->plans() . "\n";
+ #TODO:print STDERR " Priority " . $self->testcase->priority_id() . "\n";
+ #TODO:print STDERR " Product " . $self->testcase->product_id() . "\n";
+ print STDERR " Requirement " . $self->testcase->requirement() . "\n";
- print STDERR " Script " . $self->testcase->script() . "\n";
- print STDERR " Script Arguments " . $self->testcase->arguments() . "\n";
- print STDERR " Status " . $self->testcase->status() . "\n";
+ print STDERR " Script " . $self->testcase->script() . "\n";
+ print STDERR " Script Arguments " . $self->testcase->arguments() . "\n";
+ print STDERR " Status " . $self->testcase->status() . "\n";
- foreach my $tag (@{$self->tags})
- {
- print STDERR " Tag " . $tag . "\n";
- }
+ foreach my $tag (@{$self->tags})
+ {
+ print STDERR " Tag " . $tag . "\n";
+ }
- my @attachments = @{$self->testcase->attachments()};
- foreach my $attachment (@attachments)
- {
- my %submitter = %{$self->testcase->submitter()};
- my $author_login = $author{"login"};
- print STDERR " Attachment " . $attachment->description() . "\n";
- print STDERR " Creation Date " . $attachment->creation_ts(). "\n";
- print STDERR " Filename " . $attachment->filename() . "\n";
- print STDERR " Mime Type " . $attachment->mime_type(). "\n";
- print STDERR " Submitter " . $author_login . "\n";
- }
+ my @attachments = @{$self->testcase->attachments()};
+ foreach my $attachment (@attachments)
+ {
+ my %submitter = %{$self->testcase->submitter()};
+ $author_login = $author{"login"};
+ print STDERR " Attachment " . $attachment->description() . "\n";
+ print STDERR " Creation Date " . $attachment->creation_ts(). "\n";
+ print STDERR " Filename " . $attachment->filename() . "\n";
+ print STDERR " Mime Type " . $attachment->mime_type(). "\n";
+ print STDERR " Submitter " . $author_login . "\n";
+ }
}
-sub add_tag()
+sub get_testcase_ids()
{
- my ($self,$tag) = @_;
-
- push @{$self->tags}, $tag;
+ my ($self, $field, @new_testcases) = @_;
+ my $error_message = "";
+
+ my @testcase_id = @{$self->$field->get(uc $Bugzilla::Testopia::Xml::DATABASE_ID)};
+
+ foreach my $testcase_summary ( @{$self->$field->get(uc $Bugzilla::Testopia::Xml::XML_DESCRIPTION)} )
+ {
+ foreach my $testcase (@new_testcases)
+ {
+ push @testcase_id, $testcase->testcase->id() if ( $testcase->testcase->summary() eq $testcase_summary );
+ }
+ }
+
+ #TODO Testplans using Database_Description
+ foreach my $testcase_summary ( @{$self->$field->get(uc $Bugzilla::Testopia::Xml::DATABASE_DESCRIPTION)} )
+ {
+ $error_message .= "\n" if ( $error_message ne "" );
+ $error_message .= "Have not implemented code for $Bugzilla::Testopia::Xml::DATABASE_DESCRIPTION lookup for blocking test case " . $testcase_summary . "' for Test Case '". $self->testcase->summary . "'.";
+ }
+ return $error_message if ( $error_message ne "" );
+
+ my @return_testcase_id;
+ foreach my $testcase_id (@testcase_id)
+ {
+ my $testcase = Bugzilla::Testopia::TestCase->new($testcase_id);
+ if ( ! defined($testcase) )
+ {
+ $error_message .= "\n" if ( $error_message ne "" );
+ $error_message .= "Could not find blocking Test Case '" . $testcase_id . "' for Test Case '". $self->testcase->summary . "'.";
+ }
+ else
+ {
+ push @return_testcase_id, $testcase->id();
+ }
+ }
+ return $error_message if ( $error_message ne "" );
+
+ return @return_testcase_id;
}
sub store()
{
- my ($self, $userid, @testplans) = @_;
+ my ($self, @new_testplans) = @_;
+ my $error_message = "";
+ my @testplan_id = @{$self->testplan->get(uc $Bugzilla::Testopia::Xml::DATABASE_ID)};
- #
- # Following code pulled from Bugzilla::Testopia::TestCase->get_category_list().
- # Cannot call Bugzilla::Testopia::TestCase->get_category_list() until we create a
- # TestCase but cannot create a TestCase without a category.
- #
- my @categories;
- foreach my $testplan (@testplans)
+ # If we have any references to test plans from the XML data we need to search the @new_testplans
+ # array to find the actual test plan id. The new_testplans array contains all test plans created
+ # from the XML.
+ # Order of looping does not matter, number of test plans associated to each test case should be small.
+ foreach my $testplan_name ( @{$self->testplan->get(uc $Bugzilla::Testopia::Xml::XML_DESCRIPTION)} )
{
- push @categories, @{$testplan->categories};
- }
- my $categoryid = -1;
- foreach my $category (@categories)
- {
- my %temphash = %{$category};
- if ( $self->category eq $temphash{"name"} )
+ foreach my $testplan (@new_testplans)
{
- $categoryid = $temphash{"category_id"};
- last;
+ push @testplan_id, $testplan->id() if ( $testplan->name() eq $testplan_name );
}
- }
- return "Cannot save test case '" . $self->testcase->summary . "', Category '" . $self->category . "' is not valid." if ( $categoryid == -1 );
- $self->testcase->{'category_id'} = $categoryid;
+ }
+ #TODO Testplans using Database_Description
+ foreach my $testplan_name ( @{$self->testplan->get(uc $Bugzilla::Testopia::Xml::DATABASE_DESCRIPTION)} )
+ {
+ $error_message .= "\n" if ( $error_message ne "" );
+ $error_message .= "Have not implemented code for $Bugzilla::Testopia::Xml::DATABASE_DESCRIPTION lookup of test plan " . $testplan_name . "' for Test Case '". $self->testcase->summary . "'.";
+ }
+ return $error_message if ( $error_message ne "" );
+
+ # Have to have a testplan to determine valid categories for testcase.
+ return "Test Case '" . $self->testcase->summary . "' needs a Test Plan." if ( $#testplan_id == -1 );
+
+ # Verify that each testplan exists.
+ my @testplan;
+ foreach my $testplan_id (@testplan_id)
+ {
+ my $testplan = Bugzilla::Testopia::TestPlan->new($testplan_id);
+ if ( ! defined($testplan) )
+ {
+ $error_message .= "\n" if ( $error_message ne "" );
+ $error_message .= "Could not find Test Plan '" . $testplan_id . "' for Test Case '". $self->testcase->summary . "'.";
+ }
+ else
+ {
+ push @testplan, $testplan;
+ }
+ }
+ return $error_message if ( $error_message ne "" );
+
+ # Verify that each testplan has the testcase's category associated to it. If the category does not
+ # exist it will be created.
+ foreach my $testplan (@testplan)
+ {
+ my $categoryid = -1;
+
+ push my @categories, @{$testplan->categories};
+ foreach my $category (@categories)
+ {
+ if ( $category->name eq $self->category )
+ {
+ $categoryid = $category->id;
+ last;
+ }
+ }
+ if ( $categoryid == -1 )
+ {
+ my $new_category = Bugzilla::Testopia::Category->new({
+ product_id => $testplan->product_id,
+ name => $self->category,
+ description => "FIX ME. Created during Test Plan import."
+ });
+ $categoryid = $new_category->store();
+ }
+ $self->testcase->{'category_id'} = $categoryid if ( ! defined($self->testcase->{'category_id'}) );
+ }
my $case_id = $self->testcase->store();
$self->testcase->{'case_id'} = $case_id;
foreach my $asciitag ( @{$self->tags} )
@@ -193,13 +289,33 @@ sub store()
my $tagid = $classtag->store;
$self->testcase->add_tag($tagid);
}
- foreach my $testplan ( @testplans )
+
+ # Link the testcase to each of it's testplans.
+ foreach my $testplan ( @testplan )
{
$self->testcase->link_plan($testplan->id(),$case_id)
}
- $self->testcase->store_text($case_id,$userid,$self->action,$self->expectedresults);
- return "";
+ # Code below requires the testplans to be linked into testcases before being run.
+ foreach my $component ( @{$self->components} )
+ {
+ foreach my $available_component ( @{$self->testcase->get_selectable_components()} )
+ {
+ $self->testcase->add_component($available_component->{'id'}) if ( $component eq $available_component->{'name'} );
+ }
+ }
+
+ return $error_message;
+}
+
+sub store_relationships()
+{
+ my ($self, @new_testcases) = @_;
+
+ my $blocks = $self->testcase->blocked_list_uncached() . " " . join(' ',$self->get_testcase_ids("blocks",@new_testcases));
+ my $dependson = $self->testcase->dependson_list_uncached() . " " . join(' ',$self->get_testcase_ids("dependson",@new_testcases));
+
+ $self->testcase->update_deps($dependson,$blocks);
}
1;
diff --git a/mozilla/webtools/testopia/Bugzilla/WebService/Constants.pm b/mozilla/webtools/testopia/Bugzilla/WebService/Constants.pm
new file mode 100644
index 00000000000..df86986bd59
--- /dev/null
+++ b/mozilla/webtools/testopia/Bugzilla/WebService/Constants.pm
@@ -0,0 +1,36 @@
+# -*- 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 Bug Tracking System.
+#
+# Contributor(s): Marc Schumann
+
+package Bugzilla::WebService::Constants;
+
+use strict;
+use base qw(Exporter);
+
+@Bugzilla::WebService::Constants::EXPORT = qw(
+ ERROR_AUTH_NODATA
+ ERROR_UNIMPLEMENTED
+ ERROR_GENERAL
+ ERROR_FAULT_SERVER
+);
+
+use constant ERROR_AUTH_NODATA => 410;
+use constant ERROR_UNIMPLEMENTED => 910;
+use constant ERROR_GENERAL => 999;
+
+# RPC Fault Code must be an integer
+use constant ERROR_FAULT_SERVER => 998;
+
+1;
\ No newline at end of file
diff --git a/mozilla/webtools/testopia/Bugzilla/WebService/Testopia/TestCase.pm b/mozilla/webtools/testopia/Bugzilla/WebService/Testopia/TestCase.pm
new file mode 100644
index 00000000000..6fc93d4fa8b
--- /dev/null
+++ b/mozilla/webtools/testopia/Bugzilla/WebService/Testopia/TestCase.pm
@@ -0,0 +1,257 @@
+# -*- 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 Bug Tracking System.
+#
+# Contributor(s): Marc Schumann
+# Dallas Harken
+
+package Bugzilla::WebService::Testopia::TestCase;
+
+use strict;
+use base qw(Bugzilla::WebService);
+use Bugzilla::User;
+use Bugzilla::Util qw(detaint_natural);
+use Bugzilla::Testopia::TestCase;
+use Bugzilla::Testopia::Search;
+use Bugzilla::Testopia::Table;
+
+
+# Convert string field values to their respective integer id's
+sub _convert_to_ids
+{
+ my ($hash) = @_;
+
+ if (defined($$hash{"author"}))
+ {
+ $$hash{"author_id"} = login_to_id($$hash{"author"});
+ }
+ delete $$hash{"author"};
+
+ if (defined($$hash{"case_status"}))
+ {
+ $$hash{"case_status_id"} = Bugzilla::Testopia::TestCase::lookup_status_by_name($$hash{"case_status"});
+ }
+ delete $$hash{"case_status"};
+
+ if (defined($$hash{"category"}))
+ {
+ $$hash{"category_id"} = Bugzilla::Testopia::TestCase::lookup_category_by_name($$hash{"category"});
+ }
+ delete $$hash{"category"};
+
+ if (defined($$hash{"default_tester"}))
+ {
+ $$hash{"default_tester_id"} = login_to_id($$hash{"default_tester"});
+ }
+ delete $$hash{"default_tester"};
+
+ if (defined($$hash{"priority"}))
+ {
+ $$hash{"priority_id"} = Bugzilla::Testopia::TestCase::lookup_priority_by_value($$hash{"priority"});
+ }
+ delete $$hash{"priority"};
+ }
+
+# Convert fields with integer id's to their respective string values
+sub _convert_to_strings
+{
+ my ($hash) = @_;
+
+ $$hash{"author"} = "";
+ if (defined($$hash{"author_id"}))
+ {
+ $$hash{"author"} = new Bugzilla::User($$hash{"author_id"})->login;
+ }
+ delete $$hash{"author_id"};
+
+ $$hash{"case_status"} = "";
+ if (defined($$hash{"case_status_id"}))
+ {
+ $$hash{"case_status"} = Bugzilla::Testopia::TestCase->lookup_status($$hash{"case_status_id"});
+ }
+ delete $$hash{"case_status_id"};
+
+ $$hash{"category"} = "";
+ if (defined($$hash{"category_id"}))
+ {
+ $$hash{"category"} = Bugzilla::Testopia::TestCase->lookup_category($$hash{"category_id"});
+ }
+ delete $$hash{"category_id"};
+
+ $$hash{"default_tester"} = "";
+ if (defined($$hash{"default_tester_id"}))
+ {
+ $$hash{"default_tester"} = new Bugzilla::User($$hash{"default_tester_id"})->login;
+ }
+ delete $$hash{"default_tester_id"};
+
+ $$hash{"priority"} = "";
+ if (defined($$hash{"priority_id"}))
+ {
+ $$hash{"priority"} = Bugzilla::Testopia::TestCase->lookup_priority($$hash{"priority_id"});
+ }
+ delete $$hash{"priority_id"};
+ }
+
+# Utility method called by the list method
+sub _list
+{
+ my ($query) = @_;
+
+ my $cgi = Bugzilla->cgi;
+
+ $cgi->param("viewall", 1);
+
+ $cgi->param("current_tab", "case");
+
+ foreach (keys(%$query))
+ {
+ $cgi->param($_, $$query{$_});
+ }
+
+ my $search = Bugzilla::Testopia::Search->new($cgi);
+
+ # Result is an array of test plan hash maps
+ return Bugzilla::Testopia::Table->new('case',
+ 'tr_xmlrpc.cgi',
+ $cgi,
+ undef,
+ $search->query()
+ )->list();
+}
+
+sub get
+{
+ my $self = shift;
+ my ($test_case_id) = @_;
+
+ Bugzilla->login;
+
+ # We can detaint immediately if what we get passed is fully numeric.
+ # We leave bug alias checks to Bugzilla::Testopia::TestCase::new.
+
+ if ($test_case_id =~ /^[0-9]+$/) {
+ detaint_natural($test_case_id);
+ }
+
+ #Result is a test case hash map
+ my $testcase = new Bugzilla::Testopia::TestCase($test_case_id);
+
+ _convert_to_strings($testcase);
+
+ Bugzilla->logout;
+
+ return $testcase;
+}
+
+sub list
+{
+ Bugzilla->login;
+
+ my $self = shift;
+ my ($query) = @_;
+
+ _convert_to_ids($query);
+
+ my $list = _list($query);
+
+ foreach (@$list)
+ {
+ _convert_to_strings($_);
+ }
+
+ Bugzilla->logout;
+
+ return $list;
+}
+
+sub create
+{
+ my $self =shift;
+ my ($new_values) = @_;
+
+ Bugzilla->login;
+
+ _convert_to_ids($new_values);
+
+ my $test_case = new Bugzilla::Testopia::TestCase($new_values);
+
+ Bugzilla->logout;
+
+ # Result is new test plan id
+ return $test_case->store();
+}
+
+sub update
+{
+ my $self =shift;
+ my ($test_case_id, $new_values) = @_;
+
+ Bugzilla->login;
+
+ my $test_case = new Bugzilla::Testopia::TestCase($test_case_id);
+
+ _convert_to_ids($new_values);
+
+ $test_case->update($new_values);
+
+ Bugzilla->logout;
+
+ # Result is zero on success, otherwise an exception will be thrown
+ return 0;
+}
+
+sub get_text
+{
+ my $self =shift;
+ my ($test_case_id) = @_;
+
+ Bugzilla->login;
+
+ my $test_case = new Bugzilla::Testopia::TestCase($test_case_id);
+
+ my $doc = $test_case->text();
+
+ $$doc{"author"} = "";
+ if (defined($$doc{"author_id"}))
+ {
+ $$doc{"author"} = new Bugzilla::User($$doc{"author_id"})->login;
+ }
+ delete $$doc{"author_id"};
+
+ Bugzilla->logout;
+
+ #Result is the latest test case doc hash map
+ return $doc;
+}
+
+sub store_text
+{
+ my $self =shift;
+ my ($test_case_id, $author, $action, $effect) = @_;
+
+ Bugzilla->login;
+
+ my $author_id = login_to_id($author);
+
+ my $test_case = new Bugzilla::Testopia::TestCase($test_case_id);
+
+ my $version = $test_case->store_text($test_case_id, $author_id, $action, $effect);
+
+ Bugzilla->logout;
+
+ # Result is new test case doc version on success, otherwise an exception will be thrown
+ return $version;
+}
+
+1;
\ No newline at end of file
diff --git a/mozilla/webtools/testopia/Bugzilla/WebService/Testopia/TestCaseRun.pm b/mozilla/webtools/testopia/Bugzilla/WebService/Testopia/TestCaseRun.pm
new file mode 100644
index 00000000000..0053371f0f8
--- /dev/null
+++ b/mozilla/webtools/testopia/Bugzilla/WebService/Testopia/TestCaseRun.pm
@@ -0,0 +1,186 @@
+# -*- 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 Bug Tracking System.
+#
+# Contributor(s): Marc Schumann
+# Dallas Harken
+
+package Bugzilla::WebService::Testopia::TestCaseRun;
+
+use strict;
+use base qw(Bugzilla::WebService);
+use Bugzilla::Util qw(detaint_natural);
+use Bugzilla::Testopia::TestCaseRun;
+use Bugzilla::User;
+use Bugzilla::Testopia::Search;
+use Bugzilla::Testopia::Table;
+
+# Convert string field values to their respective integer id's
+sub _convert_to_ids
+{
+ my ($hash) = @_;
+
+ if (defined($$hash{"assignee"}))
+ {
+ $$hash{"assignee"} = login_to_id($$hash{"assignee"});
+ }
+
+ if (defined($$hash{"testedby"}))
+ {
+ $$hash{"testedby"} = login_to_id($$hash{"testedby"});
+ }
+
+ if (defined($$hash{"case_run_status"}))
+ {
+ $$hash{"case_run_status_id"} = Bugzilla::Testopia::TestCaseRun::lookup_status_by_name($$hash{"case_run_status"});
+ }
+ delete $$hash{"case_run_status"};
+}
+
+# Convert fields with integer id's to their respective string values
+sub _convert_to_strings
+{
+ my ($hash) = @_;
+
+ if (defined($$hash{"assignee"}))
+ {
+ $$hash{"assignee"} = new Bugzilla::User($$hash{"assignee"})->login;
+ }
+
+ if (defined($$hash{"testedby"}))
+ {
+ $$hash{"testedby"} = new Bugzilla::User($$hash{"testedby"})->login;
+ }
+
+ $$hash{"case_run_status"} = "";
+ if (defined($$hash{"case_run_status_id"}))
+ {
+ $$hash{"case_run_status"} = Bugzilla::Testopia::TestCaseRun->lookup_status($$hash{"case_run_status_id"});
+ }
+ delete $$hash{"case_run_status_id"};
+}
+
+# Utility method called by the list method
+sub _list
+{
+ my ($query) = @_;
+
+ my $cgi = Bugzilla->cgi;
+
+ $cgi->param("viewall", 1);
+
+ $cgi->param("current_tab", "case_run");
+
+ foreach (keys(%$query))
+ {
+ $cgi->param($_, $$query{$_});
+ }
+
+ my $search = Bugzilla::Testopia::Search->new($cgi);
+
+ # Result is an array of test case run hash maps
+ return Bugzilla::Testopia::Table->new('case_run',
+ 'tr_xmlrpc.cgi',
+ $cgi,
+ undef,
+ $search->query()
+ )->list();
+}
+
+sub get
+{
+ my $self = shift;
+ my ($test_case_run_id) = @_;
+
+ Bugzilla->login;
+
+ # We can detaint immediately if what we get passed is fully numeric.
+ # We leave bug alias checks to Bugzilla::Testopia::TestCaseRun::new.
+
+ if ($test_case_run_id =~ /^[0-9]+$/) {
+ detaint_natural($test_case_run_id);
+ }
+
+ #Result is a test case run hash map
+ my $test_case_run = new Bugzilla::Testopia::TestCaseRun($test_case_run_id);
+
+ _convert_to_strings($test_case_run);
+
+ Bugzilla->logout;
+
+ return $test_case_run;
+
+}
+
+sub list
+{
+ Bugzilla->login;
+
+ my $self = shift;
+ my ($query) = @_;
+
+ _convert_to_ids($query);
+
+ my $list = _list($query);
+
+ foreach (@$list)
+ {
+ _convert_to_strings($_);
+ }
+
+ Bugzilla->logout;
+
+ return $list;
+}
+
+sub create
+{
+ my $self =shift;
+ my ($new_values) = @_;
+
+ Bugzilla->login;
+
+ _convert_to_ids($new_values);
+
+ my $test_case_run = new Bugzilla::Testopia::TestCaseRun($new_values);
+
+ Bugzilla->logout;
+
+ # Result is new test case run id
+ return $test_case_run->store();
+}
+
+sub update
+{
+ my $self =shift;
+ my ($run_id, $case_id, $build_id, $new_values) = @_;
+
+ Bugzilla->login;
+
+ my $test_case_run_id = Bugzilla::Testopia::TestCaseRun::lookup_case_run_id($run_id, $case_id, $build_id);
+
+ my $test_case_run = new Bugzilla::Testopia::TestCaseRun($test_case_run_id);
+
+ _convert_to_ids($new_values);
+
+ $$new_values{build_id} = $build_id; # Needed by TestCaseRun's update member function
+
+ $test_case_run->update($new_values);
+
+ Bugzilla->logout;
+
+ # Result is zero on success, otherwise an exception will be thrown
+ return 0;
+}
+
+1;
diff --git a/mozilla/webtools/testopia/Bugzilla/WebService/Testopia/TestPlan.pm b/mozilla/webtools/testopia/Bugzilla/WebService/Testopia/TestPlan.pm
new file mode 100644
index 00000000000..e079b6fb5e2
--- /dev/null
+++ b/mozilla/webtools/testopia/Bugzilla/WebService/Testopia/TestPlan.pm
@@ -0,0 +1,190 @@
+# -*- 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 Bug Tracking System.
+#
+# Contributor(s): Marc Schumann
+# Dallas Harken
+
+package Bugzilla::WebService::Testopia::TestPlan;
+
+use strict;
+
+use base qw(Bugzilla::WebService);
+
+use Bugzilla::Util qw(detaint_natural);
+use Bugzilla::Product;
+use Bugzilla::User;
+use Bugzilla::Testopia::TestPlan;
+use Bugzilla::Testopia::Search;
+use Bugzilla::Testopia::Table;
+
+# Convert string field values to their respective integer id's
+sub _convert_to_ids
+{
+ my ($hash) = @_;
+
+ if (defined($$hash{"author"}))
+ {
+ $$hash{"author_id"} = login_to_id($$hash{"author"});
+ }
+ delete $$hash{"author"};
+ if (defined($$hash{"product"}))
+ {
+ my $product = Bugzilla::Product::check_product($$hash{"product"});
+ $$hash{"product_id"} = $product->id;
+ }
+ delete $$hash{"product"};
+
+ if (defined($$hash{"type"}))
+ {
+ $$hash{"type_id"} = Bugzilla::Testopia::TestPlan::lookup_type_by_name($$hash{"type"});
+ }
+ delete $$hash{"type"};
+}
+
+# Convert fields with integer id's to their respective string values
+sub _convert_to_strings
+{
+ my ($hash) = @_;
+
+ $$hash{"author"} = "";
+ if (defined($$hash{"author_id"}))
+ {
+ $$hash{"author"} = new Bugzilla::User($$hash{"author_id"})->login;
+ }
+ delete $$hash{"author_id"};
+
+ $$hash{"product"} = "";
+ if (defined($$hash{"product_id"}))
+ {
+ $$hash{"product"} = new Bugzilla::Product($$hash{"product_id"})->name;
+ }
+ delete $$hash{"product_id"};
+
+ $$hash{"type"} = "";
+ if (defined($$hash{"type_id"}))
+ {
+ $$hash{"type"} = Bugzilla::Testopia::TestPlan->lookup_type($$hash{"type_id"});
+ }
+ delete $$hash{"type_id"};
+}
+
+# Utility method called by the list method
+sub _list
+{
+ my ($query) = @_;
+
+ my $cgi = Bugzilla->cgi;
+
+ $cgi->param("viewall", 1);
+
+ $cgi->param("current_tab", "plan");
+
+ foreach (keys(%$query))
+ {
+ $cgi->param($_, $$query{$_});
+ }
+
+ my $search = Bugzilla::Testopia::Search->new($cgi);
+
+ # Result is an array of test plan hash maps
+ return Bugzilla::Testopia::Table->new('plan',
+ 'tr_xmlrpc.cgi',
+ $cgi,
+ undef,
+ $search->query()
+ )->list();
+}
+
+sub get
+{
+ my $self = shift;
+ my ($test_plan_id) = @_;
+
+ Bugzilla->login;
+
+ # We can detaint immediately if what we get passed is fully numeric.
+ # We leave bug alias checks to Bugzilla::Testopia::TestPlan::new.
+
+ if ($test_plan_id =~ /^[0-9]+$/) {
+ detaint_natural($test_plan_id);
+ }
+
+ #Result is a test plan hash map
+ my $testplan = new Bugzilla::Testopia::TestPlan($test_plan_id);
+
+ _convert_to_strings($testplan);
+
+ Bugzilla->logout;
+
+ return $testplan;
+}
+
+sub list
+{
+ Bugzilla->login;
+
+ my $self = shift;
+ my ($query) = @_;
+
+ _convert_to_ids($query);
+
+ my $list = _list($query);
+
+ foreach (@$list)
+ {
+ _convert_to_strings($_);
+ }
+
+ Bugzilla->logout;
+
+ return $list;
+}
+
+sub create
+{
+ my $self =shift;
+ my ($new_values) = @_;
+
+ Bugzilla->login;
+
+ _convert_to_ids($new_values);
+
+ my $test_plan = new Bugzilla::Testopia::TestPlan($new_values);
+
+ Bugzilla->logout;
+
+ # Result is new test plan id
+ return $test_plan->store();
+}
+
+sub update
+{
+ my $self =shift;
+ my ($test_plan_id, $new_values) = @_;
+
+ Bugzilla->login;
+
+ my $test_plan = new Bugzilla::Testopia::TestPlan($test_plan_id);
+
+ _convert_to_ids($new_values);
+
+ $test_plan->update($new_values);
+
+ Bugzilla->logout;
+
+ # Result is zero on success, otherwise an exception will be thrown
+ return 0;
+}
+
+1;
\ No newline at end of file
diff --git a/mozilla/webtools/testopia/Bugzilla/WebService/Testopia/TestRun.pm b/mozilla/webtools/testopia/Bugzilla/WebService/Testopia/TestRun.pm
new file mode 100644
index 00000000000..23769ba137e
--- /dev/null
+++ b/mozilla/webtools/testopia/Bugzilla/WebService/Testopia/TestRun.pm
@@ -0,0 +1,177 @@
+# -*- 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 Bug Tracking System.
+#
+# Contributor(s): Marc Schumann
+# Dallas Harken
+
+package Bugzilla::WebService::Testopia::TestRun;
+
+use strict;
+
+use base qw(Bugzilla::WebService);
+
+use Bugzilla::Util qw(detaint_natural);
+use Bugzilla::Product;
+use Bugzilla::User;
+use Bugzilla::Testopia::TestRun;
+use Bugzilla::Testopia::Search;
+use Bugzilla::Testopia::Table;
+
+# Convert string field values to their respective integer id's
+sub _convert_to_ids
+{
+ my ($hash) = @_;
+
+ if (defined($$hash{"manager"}))
+ {
+ $$hash{"manager_id"} = login_to_id($$hash{"manager"});
+ }
+ delete $$hash{"manager"};
+
+ if (defined($$hash{"environment"}))
+ {
+ $$hash{"environment_id"} = Bugzilla::Testopia::TestRun::lookup_environment_by_name($$hash{"environment"});
+ }
+ delete $$hash{"environment"};
+}
+
+# Convert fields with integer id's to their respective string values
+sub _convert_to_strings
+{
+ my ($hash) = @_;
+
+ $$hash{"manager"} = "";
+ if (defined($$hash{"manager_id"}))
+ {
+ $$hash{"manager"} = new Bugzilla::User($$hash{"manager_id"})->login;
+ }
+ delete $$hash{"manager_id"};
+
+ $$hash{"environment"} = "";
+ if (defined($$hash{"environment_id"}))
+ {
+ $$hash{"environment"} = Bugzilla::Testopia::TestRun->lookup_environment($$hash{"environment_id"});
+ }
+ delete $$hash{"environment_id"};
+}
+
+# Utility method called by the list method
+sub _list
+{
+ my ($query) = @_;
+
+ my $cgi = Bugzilla->cgi;
+
+ $cgi->param("viewall", 1);
+
+ $cgi->param("current_tab", "run");
+
+ foreach (keys(%$query))
+ {
+ $cgi->param($_, $$query{$_});
+ }
+
+ my $search = Bugzilla::Testopia::Search->new($cgi);
+
+ # Result is an array of test run hash maps
+ return Bugzilla::Testopia::Table->new('run',
+ 'tr_xmlrpc.cgi',
+ $cgi,
+ undef,
+ $search->query()
+ )->list();
+}
+
+sub get
+{
+ my $self = shift;
+ my ($test_run_id) = @_;
+
+ Bugzilla->login;
+
+ # We can detaint immediately if what we get passed is fully numeric.
+ # We leave bug alias checks to Bugzilla::Testopia::TestRun::new.
+
+ if ($test_run_id =~ /^[0-9]+$/) {
+ detaint_natural($test_run_id);
+ }
+
+ #Result is a test run hash map
+ my $testrun = new Bugzilla::Testopia::TestRun($test_run_id);
+
+ _convert_to_strings($testrun);
+
+ Bugzilla->logout;
+
+ return $testrun;
+}
+
+sub list
+{
+ Bugzilla->login;
+
+ my $self = shift;
+ my ($query) = @_;
+
+ _convert_to_ids($query);
+
+ my $list = _list($query);
+
+ foreach (@$list)
+ {
+ _convert_to_strings($_);
+ }
+
+ Bugzilla->logout;
+
+ return $list;
+}
+
+sub create
+{
+ my $self =shift;
+ my ($new_values) = @_;
+
+ Bugzilla->login;
+
+ _convert_to_ids($new_values);
+
+ my $test_run = new Bugzilla::Testopia::TestRun($new_values);
+
+ Bugzilla->logout;
+
+ # Result is new test run id
+ return $test_run->store();
+}
+
+sub update
+{
+ my $self =shift;
+ my ($test_run_id, $new_values) = @_;
+
+ Bugzilla->login;
+
+ my $test_run = new Bugzilla::Testopia::TestRun($test_run_id);
+
+ _convert_to_ids($new_values);
+
+ $test_run->update($new_values);
+
+ Bugzilla->logout;
+
+ # Result is zero on success, otherwise an exception will be thrown
+ return 0;
+}
+
+1;
\ No newline at end of file
diff --git a/mozilla/webtools/testopia/template/en/default/hook/global/useful-links.html.tmpl/end/tr.html.tmpl b/mozilla/webtools/testopia/template/en/default/hook/global/useful-links.html.tmpl/end/tr.html.tmpl
index 292186dde15..93d82f8eb89 100644
--- a/mozilla/webtools/testopia/template/en/default/hook/global/useful-links.html.tmpl/end/tr.html.tmpl
+++ b/mozilla/webtools/testopia/template/en/default/hook/global/useful-links.html.tmpl/end/tr.html.tmpl
@@ -16,11 +16,24 @@
Testopia:
diff --git a/mozilla/webtools/testopia/template/en/default/hook/global/user-error.html.tmpl/errors/tr-user-error.html.tmpl b/mozilla/webtools/testopia/template/en/default/hook/global/user-error.html.tmpl/errors/tr-user-error.html.tmpl
index 414bb406d9e..666f0ca4d3a 100644
--- a/mozilla/webtools/testopia/template/en/default/hook/global/user-error.html.tmpl/errors/tr-user-error.html.tmpl
+++ b/mozilla/webtools/testopia/template/en/default/hook/global/user-error.html.tmpl/errors/tr-user-error.html.tmpl
@@ -88,7 +88,7 @@
[% ELSIF error == "testopia-create-environment" %]
[% title = "No Environments Defined" %]
No environments have been created yet.
- You can create environments here .
+ You can create environments here .
[% ELSIF error == "testopia-missing-required-field" %]
[% title = "Missing Required Field" %]
It seems there was no value entered for [% field FILTER none %].
diff --git a/mozilla/webtools/testopia/template/en/default/hook/index.html.tmpl/links/tr.html.tmpl b/mozilla/webtools/testopia/template/en/default/hook/index.html.tmpl/links/tr.html.tmpl
index b0e956eaa4c..b2d3437f33d 100644
--- a/mozilla/webtools/testopia/template/en/default/hook/index.html.tmpl/links/tr.html.tmpl
+++ b/mozilla/webtools/testopia/template/en/default/hook/index.html.tmpl/links/tr.html.tmpl
@@ -15,7 +15,7 @@
Do some testing
Manage test plans
Search existing test cases
- Manage run environments
+ Manage run environments
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/admin/plantypes/add.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/admin/plantypes/add.html.tmpl
new file mode 100644
index 00000000000..00976f08078
--- /dev/null
+++ b/mozilla/webtools/testopia/template/en/default/testopia/admin/plantypes/add.html.tmpl
@@ -0,0 +1,32 @@
+[%# 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
+ #%]
+
+ [% PROCESS global/header.html.tmpl
+ title = "Add a New Test Plan Type"
+ %]
+
+
+
+[% PROCESS global/footer.html.tmpl %]
\ No newline at end of file
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/admin/plantypes/delete.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/admin/plantypes/delete.html.tmpl
new file mode 100644
index 00000000000..b22b9089014
--- /dev/null
+++ b/mozilla/webtools/testopia/template/en/default/testopia/admin/plantypes/delete.html.tmpl
@@ -0,0 +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
+ #%]
+
+ [% PROCESS global/header.html.tmpl
+ title = "Test Plan Types"
+ %]
\ No newline at end of file
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/admin/plantypes/edit.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/admin/plantypes/edit.html.tmpl
new file mode 100644
index 00000000000..a60878dfbea
--- /dev/null
+++ b/mozilla/webtools/testopia/template/en/default/testopia/admin/plantypes/edit.html.tmpl
@@ -0,0 +1,33 @@
+[%# 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
+ #%]
+
+ [% PROCESS global/header.html.tmpl
+ title = "Edit Test Plan Types"
+ %]
+
+
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/admin/plantypes/show.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/admin/plantypes/show.html.tmpl
new file mode 100644
index 00000000000..d13d874404c
--- /dev/null
+++ b/mozilla/webtools/testopia/template/en/default/testopia/admin/plantypes/show.html.tmpl
@@ -0,0 +1,41 @@
+[%# 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
+ #%]
+
+ [% PROCESS global/header.html.tmpl
+ title = "Test Plan Types"
+ %]
+
+
+
+ Name
+ Actions
+
+ [% FOREACH type = plan.get_plan_types %]
+
+ [% type.name FILTER html %]
+ Edit
+
+ [% END %]
+
+
+ Add
+
+
+[% PROCESS global/footer.html.tmpl %]
\ No newline at end of file
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/admin/show.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/admin/show.html.tmpl
new file mode 100644
index 00000000000..b460dc1da0e
--- /dev/null
+++ b/mozilla/webtools/testopia/template/en/default/testopia/admin/show.html.tmpl
@@ -0,0 +1,44 @@
+[%# 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 Test Runner System.
+ #
+ # The Initial Developer of the Original Code is Maciej Maczynski.
+ # Portions created by Maciej Maczynski are Copyright (C) 2001
+ # Maciej Maczynski. All Rights Reserved.
+ #
+ # Contributor(s): Greg Hendricks
+ #%]
+
+[%# INTERFACE:
+ # ...
+ #%]
+
+
+[%############################################################################%]
+[%# Template Initialization #%]
+[%############################################################################%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% title = "Admin Settings for Testopia" %]
+
+[%############################################################################%]
+[%# Page Header #%]
+[%############################################################################%]
+
+[% PROCESS global/header.html.tmpl %]
+
+[% PROCESS testopia/style.none.tmpl %]
+
+Plan Types
+
+[% PROCESS global/footer.html.tmpl %]
\ No newline at end of file
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/blocks.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/blocks.html.tmpl
index bd2719ad9f3..e4b06d1088d 100644
--- a/mozilla/webtools/testopia/template/en/default/testopia/blocks.html.tmpl
+++ b/mozilla/webtools/testopia/template/en/default/testopia/blocks.html.tmpl
@@ -31,7 +31,7 @@
[%- sel.events IF sel.events %]
[%- sel.style IF sel.style %]>
[% FOREACH item = sel.list %]
- -1) %]>
+ -1) %]>
[% item.name FILTER html %]
[% END %]
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/case/add.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/case/add.html.tmpl
index 96edfd6cb30..6841865aa18 100644
--- a/mozilla/webtools/testopia/template/en/default/testopia/case/add.html.tmpl
+++ b/mozilla/webtools/testopia/template/en/default/testopia/case/add.html.tmpl
@@ -35,7 +35,13 @@
title = title
style = style
%]
-
+
+
[% PROCESS testopia/style.none.tmpl %]
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/case/bugs.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/case/bugs.html.tmpl
new file mode 100644
index 00000000000..c7362d03621
--- /dev/null
+++ b/mozilla/webtools/testopia/template/en/default/testopia/case/bugs.html.tmpl
@@ -0,0 +1,44 @@
+[%# 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 Test Runner System.
+ #
+ # The Initial Developer of the Original Code is Maciej Maczynski.
+ # Portions created by Maciej Maczynski are Copyright (C) 2001
+ # Maciej Maczynski. All Rights Reserved.
+ #
+ # Contributor(s): Greg Hendricks
+ #%]
+
+
+
+
+[% FOREACH bug = case.bugs %]
+
+ [% bug.bug_id FILTER bug_link(bug.bug_id) %]
+ [% bug.short_desc FILTER html %]
+
+
+[% END %]
+
+
+
+
+
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/case/filter.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/case/filter.html.tmpl
new file mode 100644
index 00000000000..13ca6d71491
--- /dev/null
+++ b/mozilla/webtools/testopia/template/en/default/testopia/case/filter.html.tmpl
@@ -0,0 +1,85 @@
+[%# 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 Test Runner System.
+ #
+ # The Initial Developer of the Original Code is Maciej Maczynski.
+ # Portions created by Maciej Maczynski are Copyright (C) 2001
+ # Maciej Maczynski. All Rights Reserved.
+ #
+ # Contributor(s): Greg Hendricks
+ #%]
+
+Filter
+
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/case/form.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/case/form.html.tmpl
index c42238a9e63..d7569f200e2 100644
--- a/mozilla/webtools/testopia/template/en/default/testopia/case/form.html.tmpl
+++ b/mozilla/webtools/testopia/template/en/default/testopia/case/form.html.tmpl
@@ -24,8 +24,9 @@
#%]
[% IF case.canedit %]
+
[% END %]
@@ -53,7 +62,7 @@
Default Tester
-
+
Add Tags
@@ -126,7 +135,8 @@
default => comps
mult => 1
deflist => 1
- elements => 7 } %]
+ elements => 7
+ events => "onChange=\"chgTester(this)\""} %]
[% END %]
@@ -173,23 +183,36 @@
-
-[%# action, effect %]
- Action
-
-
-
-
- Expected Results
-
-
-
+
+
+
+
+[%# action, effect %]
+ Set Up
+
+
+
+
+ Break Down
+
+
+
+
+ Action
+
+
+
+
+ Expected Results
+
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/case/list.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/case/list.html.tmpl
index 8e71ef25be2..cee87e0ae2b 100644
--- a/mozilla/webtools/testopia/template/en/default/testopia/case/list.html.tmpl
+++ b/mozilla/webtools/testopia/template/en/default/testopia/case/list.html.tmpl
@@ -43,6 +43,8 @@
+[% PROCESS testopia/case/filter.html.tmpl IF addrun %]
+
\ No newline at end of file
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/caserun/form.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/caserun/form.html.tmpl
index 6baffd36a49..ebf2c946db1 100644
--- a/mozilla/webtools/testopia/template/en/default/testopia/caserun/form.html.tmpl
+++ b/mozilla/webtools/testopia/template/en/default/testopia/caserun/form.html.tmpl
@@ -83,6 +83,8 @@
default => default_build }
%]
+ Environment
+
Assignee
Attach [% terms.Bugs %]
@@ -119,9 +121,15 @@
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/caserun/list.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/caserun/list.html.tmpl
index 9379c4853b9..362df81febc 100644
--- a/mozilla/webtools/testopia/template/en/default/testopia/caserun/list.html.tmpl
+++ b/mozilla/webtools/testopia/template/en/default/testopia/caserun/list.html.tmpl
@@ -43,8 +43,9 @@
-
@@ -57,6 +59,9 @@
[% PROCESS "testopia/caserun/short-form-header.html.tmpl" %]
+
+ Summary:
+ [% caserun.case.summary FILTER html %]
[% PROCESS "testopia/caserun/short-form.html.tmpl" %]
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/environment/add.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/environment/add.html.tmpl
index fac9860e75d..abe7ab3961a 100644
--- a/mozilla/webtools/testopia/template/en/default/testopia/environment/add.html.tmpl
+++ b/mozilla/webtools/testopia/template/en/default/testopia/environment/add.html.tmpl
@@ -39,6 +39,48 @@
[% PROCESS testopia/blocks.html.tmpl %]
[% PROCESS testopia/messages.html.tmpl %]
-[% PROCESS testopia/environment/form.html.tmpl %]
+
+
+[%############################################################################%]
+[%# New Environment #%]
+[%############################################################################%]
+ New Environment
+
+
+
+
+
+
+
+
+
+[%############################################################################%]
+[%# Upload XML #%]
+[%############################################################################%]
+
+ OR Upload XML
+
+
+
+
+ Help
+
+
+
+
[% PROCESS global/footer.html.tmpl %]
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/environment/admin/category.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/environment/admin/category.html.tmpl
new file mode 100644
index 00000000000..5f36792f23e
--- /dev/null
+++ b/mozilla/webtools/testopia/template/en/default/testopia/environment/admin/category.html.tmpl
@@ -0,0 +1,65 @@
+[%# 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 Test Runner System.
+ #
+ # The Initial Developer of the Original Code is Maciej Maczynski.
+ # Portions created by Maciej Maczynski are Copyright (C) 2001
+ # Maciej Maczynski. All Rights Reserved.
+ #
+ # Contributor(s): Greg Hendricks
+ # Michael Hight
+ #%]
+
+[%# INTERFACE:
+ # ...
+ #%]
+
+
+
+Category Administration
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/environment/admin/element.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/environment/admin/element.html.tmpl
new file mode 100644
index 00000000000..847bee59e08
--- /dev/null
+++ b/mozilla/webtools/testopia/template/en/default/testopia/environment/admin/element.html.tmpl
@@ -0,0 +1,128 @@
+[%# 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 Test Runner System.
+ #
+ # The Initial Developer of the Original Code is Maciej Maczynski.
+ # Portions created by Maciej Maczynski are Copyright (C) 2001
+ # Maciej Maczynski. All Rights Reserved.
+ #
+ # Contributor(s): Greg Hendricks
+ # Brian Kramer
+ # Michael Hight
+ #%]
+
+[%# INTERFACE:
+ # ...
+ #%]
+
+
+
+
+
+Element Administration
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+[%### Product ###################################################################################### %]
+
+ [% currentproduct.name %]
+ [% FOREACH product = products %]
+ [% IF currentproduct.id != product.id %]
+ [% product.name %]
+ [% END %]
+ [% END %]
+
+
+
+
+
+
+
+
+
+
+
+
+[%### Category ###################################################################################### %]
+
+ [% currentcategory.name %]
+ [% FOREACH category = categories %]
+ [% IF currentcategory.env_category_id != category.name %]
+ [% category.name %]
+ [% END %]
+ [% END %]
+
+
+
+
+
+
+
+
+
+
+
+
+[%### Element ###################################################################################### %]
+
+ [% IF element.parent_id == 0 %]
+ [none]
+ [% FOREACH e = elements %]
+ [% e.name %]
+ [% END %]
+ [% ELSE %]
+ [% currentelement.name %]
+ [none]
+ [% FOREACH e = elements %]
+ [% IF currentelement.id != e.id%]
+ [% e.name %]
+ [% END %]
+ [% END %]
+ [% END %]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/environment/admin/property.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/environment/admin/property.html.tmpl
new file mode 100644
index 00000000000..b76ed5230cd
--- /dev/null
+++ b/mozilla/webtools/testopia/template/en/default/testopia/environment/admin/property.html.tmpl
@@ -0,0 +1,58 @@
+[%# 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 Test Runner System.
+ #
+ # The Initial Developer of the Original Code is Maciej Maczynski.
+ # Portions created by Maciej Maczynski are Copyright (C) 2001
+ # Maciej Maczynski. All Rights Reserved.
+ #
+ # Contributor(s): Greg Hendricks
+ # Michael Hight
+ # Scott Sudweeks
+ #%]
+
+[%# INTERFACE:
+ # ...
+ #%]
+
+
+
+Property Administration
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/environment/admin/show.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/environment/admin/show.html.tmpl
new file mode 100644
index 00000000000..60231ba2fa1
--- /dev/null
+++ b/mozilla/webtools/testopia/template/en/default/testopia/environment/admin/show.html.tmpl
@@ -0,0 +1,75 @@
+[%# 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 Test Runner System.
+ #
+ # The Initial Developer of the Original Code is Maciej Maczynski.
+ # Portions created by Maciej Maczynski are Copyright (C) 2001
+ # Maciej Maczynski. All Rights Reserved.
+ #
+ # Contributor(s): Greg Hendricks
+ #%]
+
+[%# INTERFACE:
+ # ...
+ #%]
+
+[%############################################################################%]
+[%# Template Initialization #%]
+[%############################################################################%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% title = "Environment Administration" %]
+
+[%############################################################################%]
+[%# Page Header #%]
+[%############################################################################%]
+
+[% PROCESS global/header.html.tmpl %]
+
+[% PROCESS testopia/style.none.tmpl %]
+[% PROCESS testopia/blocks.html.tmpl %]
+
+
+
+
+This is the environment admin tool.
+You can create, edit, and remove environment elements by right clicking on a node in the
+tree. Changes are applied immediately unless prompted.
+Before adding categories and elements to a product, be sure that they do not
+already exist in the --ALL-- product.
+
+
+
+
+ Legend
+
+
+ Environment Category
+
+
+ Environment Element
+
+
+ Element Property
+
+
+
+
+
+
+
+[% PROCESS testopia/messages.html.tmpl %]
+
+[% PROCESS testopia/environment/admin/tree.html.tmpl %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/environment/admin/tree.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/environment/admin/tree.html.tmpl
new file mode 100644
index 00000000000..515f36c7983
--- /dev/null
+++ b/mozilla/webtools/testopia/template/en/default/testopia/environment/admin/tree.html.tmpl
@@ -0,0 +1,460 @@
+[%# 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 Test Runner System.
+ #
+ # The Initial Developer of the Original Code is Maciej Maczynski.
+ # Portions created by Maciej Maczynski are Copyright (C) 2001
+ # Maciej Maczynski. All Rights Reserved.
+ #
+ # Contributor(s): Greg Hendricks
+ # Brian Kramer
+ # Michael Hight
+ #%]
+
+[%# INTERFACE:
+ # ...
+ #%]
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Environment Variables
+
+
+
+
+
+ [% IF type == 'classification' %]
+
+ [% END %]
+ [% FOREACH item = toplevel %]
+
0) %] >
+ [% END %]
+
+
+
+
+
+
+
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/environment/admin/valid_exp.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/environment/admin/valid_exp.html.tmpl
new file mode 100644
index 00000000000..c2800511496
--- /dev/null
+++ b/mozilla/webtools/testopia/template/en/default/testopia/environment/admin/valid_exp.html.tmpl
@@ -0,0 +1,70 @@
+[%# 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 Test Runner System.
+ #
+ # The Initial Developer of the Original Code is Maciej Maczynski.
+ # Portions created by Maciej Maczynski are Copyright (C) 2001
+ # Maciej Maczynski. All Rights Reserved.
+ #
+ # Contributor(s): Greg Hendricks
+ # Michael Hight
+ # Scott Sudweeks
+ #%]
+
+[%# INTERFACE:
+ # ...
+ #%]
+
+
+Property Value Administration
+
+
+
+
+
+
+
+
+
+
+
+You must click Save Changes for your changes to take effect
\ No newline at end of file
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/environment/choose.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/environment/choose.html.tmpl
new file mode 100644
index 00000000000..331885effd5
--- /dev/null
+++ b/mozilla/webtools/testopia/template/en/default/testopia/environment/choose.html.tmpl
@@ -0,0 +1,38 @@
+[%# 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 Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham
+ # Greg Hendricks
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Search by Test Case Number"
+ %]
+
+
+
+ You may find a test environment by entering its id here:
+
+
+
+ Or you can search for environments Here .
+
+
+[% PROCESS global/footer.html.tmpl %]
\ No newline at end of file
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/environment/delete.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/environment/delete.html.tmpl
index cf07136ef50..ebca42bd2d0 100644
--- a/mozilla/webtools/testopia/template/en/default/testopia/environment/delete.html.tmpl
+++ b/mozilla/webtools/testopia/template/en/default/testopia/environment/delete.html.tmpl
@@ -47,7 +47,7 @@ test runs. Please reassign these runs to a different Environment before deleting
[% ELSE %]
You are about to permanantly delete this environment.
-
+
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/environment/export.xml.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/environment/export.xml.tmpl
new file mode 100644
index 00000000000..8bf97bcf78b
--- /dev/null
+++ b/mozilla/webtools/testopia/template/en/default/testopia/environment/export.xml.tmpl
@@ -0,0 +1,45 @@
+[%# 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 Test Runner System.
+ #
+ # The Initial Developer of the Original Code is Maciej Maczynski.
+ # Portions created by Maciej Maczynski are Copyright (C) 2001
+ # Maciej Maczynski. All Rights Reserved.
+ #
+ # Contributor(s): Garrett Braden
+ #%]
+
+[%# INTERFACE:
+ # ...
+ #%]
+
+[%############################################################################%]
+[%# Template Initialization #%]
+[%############################################################################%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% title = "Export Environment XML" %]
+
+[%############################################################################%]
+[%# Page Header #%]
+[%############################################################################%]
+
+[% PROCESS global/header.html.tmpl %]
+
+[% PROCESS testopia/style.none.tmpl %]
+[% PROCESS testopia/blocks.html.tmpl %]
+[% PROCESS testopia/messages.html.tmpl %]
+
+Environment XML
+[%- xml %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/environment/form.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/environment/form.html.tmpl
index 86b62800b7d..1687ea2a61e 100644
--- a/mozilla/webtools/testopia/template/en/default/testopia/environment/form.html.tmpl
+++ b/mozilla/webtools/testopia/template/en/default/testopia/environment/form.html.tmpl
@@ -22,41 +22,56 @@
# ...
#%]
-
-
-
-Attributes
-
+
+
+
-
-
XML
-
[% environment.xml FILTER none %]
-
-
Help
-
-
-
-
-[% IF Param("allow-test-deletion") %]
-
Delete Attachment
-[% END %]
+
+
+
\ No newline at end of file
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/environment/import.xml.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/environment/import.xml.tmpl
new file mode 100644
index 00000000000..63756ad1e93
--- /dev/null
+++ b/mozilla/webtools/testopia/template/en/default/testopia/environment/import.xml.tmpl
@@ -0,0 +1,57 @@
+[%# 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 Test Runner System.
+ #
+ # The Initial Developer of the Original Code is Maciej Maczynski.
+ # Portions created by Maciej Maczynski are Copyright (C) 2001
+ # Maciej Maczynski. All Rights Reserved.
+ #
+ # Contributor(s): Garrett Braden
+ #%]
+
+[%# INTERFACE:
+ # ...
+ #%]
+
+[%############################################################################%]
+[%# Template Initialization #%]
+[%############################################################################%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% title = "Import XML Environment" %]
+
+[%############################################################################%]
+[%# Page Header #%]
+[%############################################################################%]
+
+[% PROCESS global/header.html.tmpl %]
+
+[% PROCESS testopia/style.none.tmpl %]
+[% PROCESS testopia/blocks.html.tmpl %]
+[% PROCESS testopia/messages.html.tmpl %]
+
+[% IF action == "admin" %]
+
+ Add the above new data?
+
+
+
+
+[% ELSE %]
+
+ Upload XML Environment:
+
+
+
+[% END %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/environment/list.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/environment/list.html.tmpl
index be48bd32fd9..b506e9117da 100644
--- a/mozilla/webtools/testopia/template/en/default/testopia/environment/list.html.tmpl
+++ b/mozilla/webtools/testopia/template/en/default/testopia/environment/list.html.tmpl
@@ -41,10 +41,5 @@
[% PROCESS testopia/messages.html.tmpl %]
[% PROCESS testopia/environment/table.html.tmpl %]
-
-
-
-
-
[% PROCESS global/footer.html.tmpl %]
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/environment/show.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/environment/show.html.tmpl
index e953a6d966d..0cbb0785429 100644
--- a/mozilla/webtools/testopia/template/en/default/testopia/environment/show.html.tmpl
+++ b/mozilla/webtools/testopia/template/en/default/testopia/environment/show.html.tmpl
@@ -16,19 +16,20 @@
# Maciej Maczynski. All Rights Reserved.
#
# Contributor(s): Greg Hendricks
+ # Scott Sudweeks
#%]
[%# INTERFACE:
# ...
#%]
-
+
[%############################################################################%]
[%# Template Initialization #%]
[%############################################################################%]
[% PROCESS global/variables.none.tmpl %]
-[% title = "Edit Test Environment: $environment.name" %]
+[% title = "Environment Editor" %]
[%############################################################################%]
[%# Page Header #%]
@@ -38,8 +39,203 @@
[% PROCESS testopia/style.none.tmpl %]
[% PROCESS testopia/blocks.html.tmpl %]
-[% PROCESS testopia/messages.html.tmpl %]
+
+
+
+
+
+
+[% PROCESS testopia/messages.html.tmpl %]
[% PROCESS testopia/environment/form.html.tmpl %]
+
+
+
+
+
+
+ Environment: [% environment.name %]
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Environment Items
+
+
+
+
+
+ [% IF type == 'classification' %]
+
+ [% END %]
+ [% FOREACH item = toplevel %]
+
0) %] >
+ [% END %]
+
+
+
+
+
+
+
+
[% PROCESS global/footer.html.tmpl %]
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/environment/table.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/environment/table.html.tmpl
index fef02be957d..15cf7e940b7 100644
--- a/mozilla/webtools/testopia/template/en/default/testopia/environment/table.html.tmpl
+++ b/mozilla/webtools/testopia/template/en/default/testopia/environment/table.html.tmpl
@@ -21,26 +21,28 @@
[%# INTERFACE:
# ...
#%]
+
+[% link = "${table.get_order_url}&order=" %]
-
+[% DECORATIVE_BORDER_BEGIN %]
+
+ [% IF NOT table.viewall %]
+ [% PROCESS navigation %]
+ [% END %]
+[% DECORATIVE_BORDER_END %]
+
+
+Create a New Environment
\ No newline at end of file
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/percent_bar.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/percent_bar.html.tmpl
index 140c79532a8..d555c184ee7 100644
--- a/mozilla/webtools/testopia/template/en/default/testopia/percent_bar.html.tmpl
+++ b/mozilla/webtools/testopia/template/en/default/testopia/percent_bar.html.tmpl
@@ -23,12 +23,22 @@
#%]
+
+
+ Passed: [% run.passed_count %] [% run.percent_passed %]%
+ Failed: [% run.failed_count %] [% run.percent_failed %]%
+ Blocked: [% run.blocked_count %] [% run.percent_blocked %]%
+ Total: [% run.case_count %]
+
+
\ No newline at end of file
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/plan/add.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/plan/add.html.tmpl
index 958eae1ddb7..6d926802e3c 100644
--- a/mozilla/webtools/testopia/template/en/default/testopia/plan/add.html.tmpl
+++ b/mozilla/webtools/testopia/template/en/default/testopia/plan/add.html.tmpl
@@ -35,7 +35,14 @@
title = title
style = style
%]
-
+
+
+
[% PROCESS testopia/style.none.tmpl %]
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/plan/form.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/plan/form.html.tmpl
index 38c5a46d5b1..21d8cb40cdb 100644
--- a/mozilla/webtools/testopia/template/en/default/testopia/plan/form.html.tmpl
+++ b/mozilla/webtools/testopia/template/en/default/testopia/plan/form.html.tmpl
@@ -19,18 +19,10 @@
# Greg Hendricks
#%]
-
@@ -76,7 +68,13 @@
[% END %]
-
+
[% PROCESS select sel = { name => 'type',
list => plan.get_plan_types,
accesskey => 't'
@@ -97,12 +95,9 @@
[% END %]
Plan Document
-
-
+
[% plan.text %]
-
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/plan/list.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/plan/list.html.tmpl
index 2b89119839f..1c8b206edd9 100644
--- a/mozilla/webtools/testopia/template/en/default/testopia/plan/list.html.tmpl
+++ b/mozilla/webtools/testopia/template/en/default/testopia/plan/list.html.tmpl
@@ -42,23 +42,12 @@
[% PROCESS testopia/messages.html.tmpl %]
-
+
-
+
[% PROCESS testopia/style.none.tmpl %]
+[% PROCESS testopia/search/variables.none.tmpl %]
[% PROCESS testopia/messages.html.tmpl %]
+
+[% PROCESS testopia/case/filter.html.tmpl %]
+
Select Test Cases
[% PROCESS testopia/case/table.html.tmpl
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/run/clone.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/run/clone.html.tmpl
index 9767edcf560..9c8523c333e 100644
--- a/mozilla/webtools/testopia/template/en/default/testopia/run/clone.html.tmpl
+++ b/mozilla/webtools/testopia/template/en/default/testopia/run/clone.html.tmpl
@@ -45,6 +45,7 @@
+
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/run/form.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/run/form.html.tmpl
index bb87f0d6608..f12161522f4 100644
--- a/mozilla/webtools/testopia/template/en/default/testopia/run/form.html.tmpl
+++ b/mozilla/webtools/testopia/template/en/default/testopia/run/form.html.tmpl
@@ -44,12 +44,10 @@
Manager
- Environment
-
- [% PROCESS select sel = { name => "environment",
- list => run.get_environments,
- default => run.environment.id}
- %]
+ Environment
+
+
+ New
@@ -70,12 +68,15 @@
list => run.plan.builds,
default => run.build.id }
%]
+ [% IF action == 'Add' %]
+ or New:
+ [% END %]
Summary
-
+
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/run/list.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/run/list.html.tmpl
index e9e2f7b0a5b..a2ca605ca49 100644
--- a/mozilla/webtools/testopia/template/en/default/testopia/run/list.html.tmpl
+++ b/mozilla/webtools/testopia/template/en/default/testopia/run/list.html.tmpl
@@ -42,7 +42,7 @@
[% PROCESS testopia/messages.html.tmpl %]
-
+
[% PROCESS testopia/run/table.html.tmpl %]
@@ -82,11 +82,8 @@ found.
Environment
-
- [% PROCESS select sel = { name => 'environment',
- accesskey => 'e',
- list => env_list
- default => "--Do Not Change--" } %]
+
+
[% IF NOT multiprod %]
@@ -98,11 +95,14 @@ found.
default => "--Do Not Change--" } %]
+[% ELSE %]
+
[% END %]
+
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/run/show.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/run/show.html.tmpl
index 00738260377..0bffbfcae94 100644
--- a/mozilla/webtools/testopia/template/en/default/testopia/run/show.html.tmpl
+++ b/mozilla/webtools/testopia/template/en/default/testopia/run/show.html.tmpl
@@ -42,10 +42,18 @@
[% PROCESS testopia/messages.html.tmpl %]
[% IF run.canedit %]
+
-
+
[% IF dotweak %]
Select:
All ,
@@ -41,6 +44,7 @@ Select:
Started[% table.arrow IF table.last_sort == 'alias' %]
Finished[% table.arrow IF table.last_sort == 'alias' %]
% Completed
+ Out of
[% IF NOT plan %]Plan[% table.arrow IF table.last_sort == 'alias' %] [% END %]
@@ -62,9 +66,9 @@ Select:
[% END %]
- [% percent = run.percent_complete %]
[% PROCESS testopia/percent_bar.html.tmpl %]
+ [% run.case_count FILTER html %]
[% IF NOT plan %][% run.plan.id FILTER html %] [% END %]
[% END %]
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/search/advanced.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/search/advanced.html.tmpl
index ebc25539af1..7090e48b1bf 100644
--- a/mozilla/webtools/testopia/template/en/default/testopia/search/advanced.html.tmpl
+++ b/mozilla/webtools/testopia/template/en/default/testopia/search/advanced.html.tmpl
@@ -40,19 +40,13 @@
[% tabs = [ { name => 'case', description => "Find Test Cases" },
{ name => 'run', description => "Find Test Runs" },
- { name => 'plan', description => "Find Test Plans" } ] %]
-
-
+
+
+
+
+
+[%#######################################%]
+[%############## HTML #################%]
+[%#######################################%]
+Search for Environments
+
+
+
+
+ [% IF Param('useclassification') %]
+ Classification
+ [% PROCESS select sel = {
+ name => 'classification',
+ accesskey => 'c',
+ list => classifications,
+ elements => 5,
+ mult => 1,
+ events => 'onchange="env_class_change(this);"' } %]
+ [% END %]
+
+
+[%###### Bugzilla Products #########%]
+
+ P roducts:
+ [% PROCESS select sel = {
+ name => 'env_products',
+ accesskey => 'c',
+ list => products,
+ elements => 5,
+ mult => 1,
+ events => 'onchange="env_products_change(this);"' } %]
+
+
+[%############ CATEGORY ############%]
+
+ C ategory:
+ [% PROCESS select sel = {
+ name => 'env_categories',
+ accesskey => 'c',
+ list => env.get_all_env_categories(1),
+ elements => 5,
+ mult => 1,
+ events => 'onchange="env_categories_change(this);"' } %]
+
+
+
+
+[%############ ELEMENTS ############%]
+
+ E lement:
+ [% PROCESS select sel = {
+ name => 'env_elements',
+ accesskey => 'c',
+ list => env.get_all_visible_elements(1),
+ elements => 5,
+ mult => 1,
+ events => 'onchange="env_elements_change(this);"' } %]
+
+
+
+[%############ PROPERTIES ############%]
+
+ P roperties:
+ [% PROCESS select sel = {
+ name => 'env_properties',
+ accesskey => 'c',
+ list => env.get_all_element_properties(1),
+ elements => 5,
+ mult => 1,
+ events => 'onchange="env_properties_change(this);"' } %]
+
+
+
+[%######### VALID EXPRESSIONS ##########%]
+
+ Property V alue
+ [% PROCESS select sel = {
+ name => 'env_expressions',
+ accesskey => 'c',
+ list => env.get_distinct_property_values,
+ elements => 5,
+ mult => 1, } %]
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mozilla/webtools/testopia/template/en/default/testopia/search/plan.html.tmpl b/mozilla/webtools/testopia/template/en/default/testopia/search/plan.html.tmpl
index 40b00b49868..984140bc31b 100644
--- a/mozilla/webtools/testopia/template/en/default/testopia/search/plan.html.tmpl
+++ b/mozilla/webtools/testopia/template/en/default/testopia/search/plan.html.tmpl
@@ -84,17 +84,7 @@
[% PROCESS select sel = { name => 'author_type',
list => email_variants } %]
- AND
- OR
-
-
- Editor:
-
- [% PROCESS select sel = { name => 'editor_type',
- list => email_variants } %]
-
-
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
diff --git a/mozilla/webtools/testopia/testopia/img/env_lookup.png b/mozilla/webtools/testopia/testopia/img/env_lookup.png
new file mode 100644
index 0000000000000000000000000000000000000000..c5a9cc77e668264516a26cb315d91da54804d7b6
GIT binary patch
literal 598
zcmV-c0;&CpP)5
zlHW^{aTLZsXUcxqTI=lvtxcKIpn)+aAru86vz)%DFZqXQ^{r8=fwj;pW>9ANXFJ^PKbHIq(%#1wcejb;n|l%wRC!
z@%jAaVsX2$zW#MBm3mkymA>!?iK^1k(=!l?M#pYVPq+Bn+iM4AX?NH0EFO1Q$>c<4
zdHIDMz#$@S-F^y(EqoaDbS%k_mSXHQ*aMI~^@0(p+0Xsme&om8SzuB-L
zXF=|uJ{u075D`OF3k?Z=?Cq89$j$n`u~7rIkDRcT&lhawkEoE%epA(2bKkL<$vl5E
zGvoZ*a%y40l~1Q%*?Erq>gwzHsi}`vGU>7#$XS;2>BvYiyRvdkL@qS@qN<391S63L
zk-@?1SBHmN!u|cF&r3_5w{vs)et!PlS}IirE(4puJylIN0Dc!DhR-wyy@qk6w6pVJ
zE0B)5+Rs6*WK
kBq5-1Y(O2-R)@U&2{_Hz75Tfc3IG5A07*qoM6N<$g7kM1cK`qY
literal 0
HcmV?d00001
diff --git a/mozilla/webtools/testopia/testopia/img/env_lookup.svg b/mozilla/webtools/testopia/testopia/img/env_lookup.svg
new file mode 100644
index 00000000000..93dfb24ab34
--- /dev/null
+++ b/mozilla/webtools/testopia/testopia/img/env_lookup.svg
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
diff --git a/mozilla/webtools/testopia/testopia/img/error.gif b/mozilla/webtools/testopia/testopia/img/error.gif
new file mode 100644
index 0000000000000000000000000000000000000000..87029e3e8edc2288c706fc81452e8b1e1a16c225
GIT binary patch
literal 177
zcmZ?wbhEHb6krfw*v!MQsy^h*xzqm+dHnms@a);MzZWb19dZlu5n^Cq*u9;tp^8UG
zP3-^w|3EGTDE?$&WMJT7&;fBlW-zeCO*rZ4pkVx5OXr%10?%Dmud4rN^~v)&?AGV}2(w_0R=#jVUc!jZUE#5-kopGEH1?%BAfMJ=?#AMr%O^YXBWEMG^o2
literal 0
HcmV?d00001
diff --git a/mozilla/webtools/testopia/testopia/img/folder.gif b/mozilla/webtools/testopia/testopia/img/folder.gif
new file mode 100644
index 0000000000000000000000000000000000000000..346c1e2c8219eb7a9cf264e625e79bb9737fb269
GIT binary patch
literal 165
zcmZ?wbhEHb6lM@+*vtR||NsA=Gl${axnv;y|NpuF=jMF>{v9X?mP98Mf3h$#FmN#F
zfFwX>Ft7v(ob+71*W&fr-S3?wnC29SusU+2sPPoobR?-RsOS@N*1o%H!Gwetfep@!
vE?GFL@wzOx_WjVU1l^8zkh|%k0V6X-NrMWr-
literal 0
HcmV?d00001
diff --git a/mozilla/webtools/testopia/testopia/img/folder_blue.gif b/mozilla/webtools/testopia/testopia/img/folder_blue.gif
new file mode 100644
index 0000000000000000000000000000000000000000..55447c25f1ce4acf96b76949abf4a9a22790b354
GIT binary patch
literal 163
zcmZ?wbhEHb6lM@+IK<8X1nM@qWo@e#tUmVe>Dw2tKfQhb<^TWxivL8Ni&7IyQd1Pl
zGfOfQLNZbn+&z5*7!-f9FfuT(Gw6V{f(&6`_OIA==bynT&(&)c(;b`**&JD7L^v|E
zGNObV3V4K^R_;k!C+vLof%Uu#+Zou{Z64_^akk3b)@=Cwjcae{yszmetk`ca%39MU
O$M9lH)LIV)25SJaYd&)T
literal 0
HcmV?d00001
diff --git a/mozilla/webtools/testopia/testopia/img/folder_red.gif b/mozilla/webtools/testopia/testopia/img/folder_red.gif
new file mode 100644
index 0000000000000000000000000000000000000000..925eddbfde7caad9f666d4b23dc24334291b64e5
GIT binary patch
literal 163
zcmZ?wbhEHb6lM@+IK<8X1SN`!>kA7{&6)H5!Gq7wpMQPx=KufyivL8Ni&7IyQd1Pl
zGfOfQLNZbn+&z5*7!-f9FfuT(Gw6V{f(&6`_OIA==bynT&(&)c(;b`**&JD7L^v|E
zGNObV3V4K^R_;k!C+vLof%Uu#+Zou{Z64_^akk3b)@=Cwjcae{yszmetk`ca%39MU
O$M9lH)LIV)25SI|gFY$%
literal 0
HcmV?d00001
diff --git a/mozilla/webtools/testopia/testopia/img/green_bar.gif b/mozilla/webtools/testopia/testopia/img/green_bar.gif
new file mode 100644
index 0000000000000000000000000000000000000000..319103f30ae1f28a1d73d809d9ce95c74fb7ded7
GIT binary patch
literal 118
zcmZ?wbhEHbWMtrFc+AZZwUjMkJ$L#p!MvlQ#pfhTFG|(kR%m;!*7HfL|EtdQ|Azlj
zf#N?==c3falGGH1^30M9g^-L?1$R&100zaMEQ}%y{0uq_KmanFfrV?rjL9Na%XkDG
HSQxAU4W2GO
literal 0
HcmV?d00001
diff --git a/mozilla/webtools/testopia/testopia/img/orange_bar.gif b/mozilla/webtools/testopia/testopia/img/orange_bar.gif
new file mode 100644
index 0000000000000000000000000000000000000000..5da8da4818c80f717372da9fbf428ff1ab2fa63d
GIT binary patch
literal 118
zcmZ?wbhEHbWMtrFc+Aakxr*gRBm2EhuE!I3pHJt1F;n2(0^u(!#eZy+{Iyx?|1R18
zs6g?bsB=+jVo7R>LV0FMhC)b2s)DUeg+)|AOIQ8z``|Q#$*wzWjulo
HEDY8FWrQp(
literal 0
HcmV?d00001
diff --git a/mozilla/webtools/testopia/testopia/img/selected_value.png b/mozilla/webtools/testopia/testopia/img/selected_value.png
new file mode 100644
index 0000000000000000000000000000000000000000..4b01e526878a204c9cf8f0c08dcfdead0ccdfdc5
GIT binary patch
literal 1104
zcmV-W1h4yvP)k7R9J<@
zmrY1pSro^A_q{|X%_0L@qEd_otG@VAWhl~(FtZe@yIGEff$T(RYamn5&c+dGHraHe
z&{>o&W;VKU*HjnjYQROUX=^PVgiIF!rN-nnzIVDX8LH#g%d5@K
zLbVI%2W+6wS#tm|SxN*ak3k?l?+}&llp@ENX+;HVWBEh-{
zsYHTxH*UCc!8Ca$LdrCG=Ee;ThoO|}7h!l}f~}ew1fZq{nV4WpgkhyrzvG%^d(R!3
zrWS%het7?$zbX&_pFW{NA-?paR6}OMl8nG?b{3iSzT9qy3S^)
zLRY?Rj<5y}4B7x=^D!LV(@wvDf{QCCO4wG}fQrr6d-p`(Mnt}Y6FeH3*4cnAQ?
zLanaiTU*2T>J^tZHt=n3^3A(<)aG*dD>_Z1plNLG@3XFSou`(?_k)AvZrvgqiBRb7
zrr`HupT|lkk|kW<*4a8i0tDMEFsJ
zC($TIGD+|v0Fp_9(I|!pPY(J?3BH&UB4UZ~lL+(U<8;o<(D*e0W@cy{AE#4<`GY=U
zRrU@ID-p3pBnl+saXKa^x$-ptCMUTPkJAApMI4Q*P8Z0m)=|E<#F#)Xokou^4ateu_$A
z0tvtX5=vqE{S;#{-tO$MQpw3=c+SC1{HEjEHW06^#S|e`D6l*=#p^(TJ*6lC4}qp5
z`X=C^Qj`J#_NJzIT_~_DLaMeF6Npy>@wmO6?b%r}_4VXAcys;^w4U)MX)WvQ`g-!S
zvt-)a*#;gvhVXb;E8A!*#h-xT@PTBM^K3vo9@ZQ|0A9e?_dFWKNVV2sn`NWNx2>g1w9pM-dOVyoeOD
XI3KGlJJ2j%HvW#T9&R2{M+R#ETyL%Z
literal 0
HcmV?d00001
diff --git a/mozilla/webtools/testopia/testopia/img/square.png b/mozilla/webtools/testopia/testopia/img/square.png
new file mode 100644
index 0000000000000000000000000000000000000000..ac81e57e9268153e0aeb2652e6d5d97067b22b41
GIT binary patch
literal 27882
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
literal 0
HcmV?d00001
diff --git a/mozilla/webtools/testopia/testopia/img/triangle.gif b/mozilla/webtools/testopia/testopia/img/triangle.gif
new file mode 100644
index 0000000000000000000000000000000000000000..c72960a26d8d350e654b0038816c2672966fa362
GIT binary patch
literal 315
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