Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
bugzilla
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
etersoft
bugzilla
Commits
4f6b75a6
Commit
4f6b75a6
authored
Jun 26, 2003
by
gerv%gerv.net
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Bug 16009 - generic charting. Patch by gerv; r,a=justdave.
parent
da6143f4
Show whitespace changes
Inline
Side-by-side
Showing
23 changed files
with
2128 additions
and
4 deletions
+2128
-4
Chart.pm
Bugzilla/Chart.pm
+351
-0
Series.pm
Bugzilla/Series.pm
+262
-0
Template.pm
Bugzilla/Template.pm
+7
-0
buglist.cgi
buglist.cgi
+18
-0
chart.cgi
chart.cgi
+312
-0
checksetup.pl
checksetup.pl
+142
-0
collectstats.pl
collectstats.pl
+73
-0
editcomponents.cgi
editcomponents.cgi
+30
-0
editproducts.cgi
editproducts.cgi
+36
-1
query.cgi
query.cgi
+8
-1
filterexceptions.pl
template/en/default/filterexceptions.pl
+31
-0
code-error.html.tmpl
template/en/default/global/code-error.html.tmpl
+11
-0
messages.html.tmpl
template/en/default/global/messages.html.tmpl
+23
-0
user-error.html.tmpl
template/en/default/global/user-error.html.tmpl
+39
-1
chart.csv.tmpl
template/en/default/reports/chart.csv.tmpl
+40
-0
chart.html.tmpl
template/en/default/reports/chart.html.tmpl
+66
-0
chart.png.tmpl
template/en/default/reports/chart.png.tmpl
+56
-0
create-chart.html.tmpl
template/en/default/reports/create-chart.html.tmpl
+281
-0
edit-series.html.tmpl
template/en/default/reports/edit-series.html.tmpl
+57
-0
menu.html.tmpl
template/en/default/reports/menu.html.tmpl
+5
-1
series-common.html.tmpl
template/en/default/reports/series-common.html.tmpl
+117
-0
series.html.tmpl
template/en/default/reports/series.html.tmpl
+96
-0
search-create-series.html.tmpl
template/en/default/search/search-create-series.html.tmpl
+67
-0
No files found.
Bugzilla/Chart.pm
0 → 100644
View file @
4f6b75a6
# -*- 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): Gervase Markham <gerv@gerv.net>
use
strict
;
use
lib
"."
;
# This module represents a chart.
#
# Note that it is perfectly legal for the 'lines' member variable of this
# class (which is an array of Bugzilla::Series objects) to have empty members
# in it. If this is true, the 'labels' array will also have empty members at
# the same points.
package
Bugzilla::
Chart
;
use
Bugzilla::
Util
;
use
Bugzilla::
Series
;
sub
new
{
my
$invocant
=
shift
;
my
$class
=
ref
(
$invocant
)
||
$invocant
;
# Create a ref to an empty hash and bless it
my
$self
=
{};
bless
(
$self
,
$class
);
if
(
$#_
==
0
)
{
# Construct from a CGI object.
$self
->
init
(
$_
[
0
]);
}
else
{
die
(
"CGI object not passed in - invalid number of args \($#_\)($_)"
);
}
return
$self
;
}
sub
init
{
my
$self
=
shift
;
my
$cgi
=
shift
;
# The data structure is a list of lists (lines) of Series objects.
# There is a separate list for the labels.
#
# The URL encoding is:
# line0=67&line0=73&line1=81&line2=67...
# &label0=B+/+R+/+NEW&label1=...
# &select0=1&select3=1...
# &cumulate=1&datefrom=2002-02-03&dateto=2002-04-04&ctype=html...
# >=1&labelgt=Grand+Total
foreach
my
$param
(
$cgi
->
param
())
{
# Store all the lines
if
(
$param
=~
/^line(\d+)$/
)
{
foreach
my
$series_id
(
$cgi
->
param
(
$param
))
{
detaint_natural
(
$series_id
)
||
&::
ThrowCodeError
(
"invalid_series_id"
);
push
(
@
{
$self
->
{
'lines'
}[
$1
]},
new
Bugzilla::
Series
(
$series_id
));
}
}
# Store all the labels
if
(
$param
=~
/^label(\d+)$/
)
{
$self
->
{
'labels'
}[
$1
]
=
$cgi
->
param
(
$param
);
}
}
# Store the miscellaneous metadata
$self
->
{
'cumulate'
}
=
$cgi
->
param
(
'cumulate'
)
?
1
:
0
;
$self
->
{
'gt'
}
=
$cgi
->
param
(
'gt'
)
?
1
:
0
;
$self
->
{
'labelgt'
}
=
$cgi
->
param
(
'labelgt'
);
$self
->
{
'datefrom'
}
=
$cgi
->
param
(
'datefrom'
);
$self
->
{
'dateto'
}
=
$cgi
->
param
(
'dateto'
);
# Make sure the dates are ones we are able to interpret
foreach
my
$date
(
'datefrom'
,
'dateto'
)
{
if
(
$self
->
{
$date
})
{
$self
->
{
$date
}
=
&::
str2time
(
$self
->
{
$date
})
||
ThrowUserError
(
"illegal_date"
,
{
date
=>
$self
->
{
$date
}});
}
}
# datefrom can't be after dateto
if
(
$self
->
{
'datefrom'
}
&&
$self
->
{
'dateto'
}
&&
$self
->
{
'datefrom'
}
>
$self
->
{
'dateto'
})
{
&::
ThrowUserError
(
"misarranged_dates"
,
{
'datefrom'
=>
$cgi
->
param
(
'datefrom'
),
'dateto'
=>
$cgi
->
param
(
'dateto'
)});
}
}
# Alter Chart so that the selected series are added to it.
sub
add
{
my
$self
=
shift
;
my
@series_ids
=
@_
;
# If we are going from < 2 to >= 2 series, add the Grand Total line.
if
(
!
$self
->
{
'gt'
})
{
my
$current_size
=
scalar
(
$self
->
getSeriesIDs
());
if
(
$current_size
<
2
&&
$current_size
+
scalar
(
@series_ids
)
>=
2
)
{
$self
->
{
'gt'
}
=
1
;
}
}
# Create new Series and push them on to the list of lines.
# Note that new lines have no label; the display template is responsible
# for inventing something sensible.
foreach
my
$series_id
(
@series_ids
)
{
my
$series
=
new
Bugzilla::
Series
(
$series_id
);
push
(
@
{
$self
->
{
'lines'
}},
[
$series
]);
push
(
@
{
$self
->
{
'labels'
}},
""
);
}
}
# Alter Chart so that the selections are removed from it.
sub
remove
{
my
$self
=
shift
;
my
@line_ids
=
@_
;
foreach
my
$line_id
(
@line_ids
)
{
if
(
$line_id
==
65536
)
{
# Magic value - delete Grand Total.
$self
->
{
'gt'
}
=
0
;
}
else
{
delete
(
$self
->
{
'lines'
}
->
[
$line_id
]);
delete
(
$self
->
{
'labels'
}
->
[
$line_id
]);
}
}
}
# Alter Chart so that the selections are summed.
sub
sum
{
my
$self
=
shift
;
my
@line_ids
=
@_
;
# We can't add the Grand Total to things.
@line_ids
=
grep
(
!
/^65536$/
,
@line_ids
);
# We can't add less than two things.
return
if
scalar
(
@line_ids
)
<
2
;
my
@series
;
my
$label
=
""
;
my
$biggestlength
=
0
;
# We rescue the Series objects of all the series involved in the sum.
foreach
my
$line_id
(
@line_ids
)
{
my
@line
=
@
{
$self
->
{
'lines'
}
->
[
$line_id
]};
foreach
my
$series
(
@line
)
{
push
(
@series
,
$series
);
}
# We keep the label that labels the line with the most series.
if
(
scalar
(
@line
)
>
$biggestlength
)
{
$biggestlength
=
scalar
(
@line
);
$label
=
$self
->
{
'labels'
}
->
[
$line_id
];
}
}
$self
->
remove
(
@line_ids
);
push
(
@
{
$self
->
{
'lines'
}},
\
@series
);
push
(
@
{
$self
->
{
'labels'
}},
$label
);
}
sub
data
{
my
$self
=
shift
;
$self
->
{
'_data'
}
||=
$self
->
readData
();
return
$self
->
{
'_data'
};
}
# Convert the Chart's data into a plottable form in $self->{'_data'}.
sub
readData
{
my
$self
=
shift
;
my
@data
;
my
$series_ids
=
join
(
","
,
$self
->
getSeriesIDs
());
# Work out the date boundaries for our data.
my
$dbh
=
Bugzilla
->
dbh
;
# The date used is the one given if it's in a sensible range; otherwise,
# it's the earliest or latest date in the database as appropriate.
my
$datefrom
=
$dbh
->
selectrow_array
(
"SELECT MIN(date) FROM series_data "
.
"WHERE series_id IN ($series_ids)"
);
$datefrom
=
&::
str2time
(
$datefrom
);
if
(
$self
->
{
'datefrom'
}
&&
$self
->
{
'datefrom'
}
>
$datefrom
)
{
$datefrom
=
$self
->
{
'datefrom'
};
}
my
$dateto
=
$dbh
->
selectrow_array
(
"SELECT MAX(date) FROM series_data "
.
"WHERE series_id IN ($series_ids)"
);
$dateto
=
&::
str2time
(
$dateto
);
if
(
$self
->
{
'dateto'
}
&&
$self
->
{
'dateto'
}
<
$dateto
)
{
$dateto
=
$self
->
{
'dateto'
};
}
# Prepare the query which retrieves the data for each series
my
$query
=
"SELECT TO_DAYS(date) - TO_DAYS(FROM_UNIXTIME($datefrom)), "
.
"value FROM series_data "
.
"WHERE series_id = ? "
.
"AND date >= FROM_UNIXTIME($datefrom)"
;
if
(
$dateto
)
{
$query
.=
" AND date <= FROM_UNIXTIME($dateto)"
;
}
my
$sth
=
$dbh
->
prepare
(
$query
);
my
$gt_index
=
$self
->
{
'gt'
}
?
scalar
(
@
{
$self
->
{
'lines'
}})
:
undef
;
my
$line_index
=
0
;
foreach
my
$line
(
@
{
$self
->
{
'lines'
}})
{
# Even if we end up with no data, we need an empty arrayref to prevent
# errors in the PNG-generating code
$data
[
$line_index
]
=
[]
;
foreach
my
$series
(
@$line
)
{
# Get the data for this series and add it on
$sth
->
execute
(
$series
->
{
'series_id'
});
my
$points
=
$sth
->
fetchall_arrayref
();
foreach
my
$point
(
@$points
)
{
my
(
$datediff
,
$value
)
=
@$point
;
$data
[
$line_index
][
$datediff
]
||=
0
;
$data
[
$line_index
][
$datediff
]
+=
$value
;
# Add to the grand total, if we are doing that
if
(
$gt_index
)
{
$data
[
$gt_index
][
$datediff
]
||=
0
;
$data
[
$gt_index
][
$datediff
]
+=
$value
;
}
}
}
$line_index
++
;
}
# Add the x-axis labels into the data structure
my
$date_progression
=
generateDateProgression
(
$datefrom
,
$dateto
);
unshift
(
@data
,
$date_progression
);
if
(
$self
->
{
'gt'
})
{
# Add Grand Total to label list
push
(
@
{
$self
->
{
'labels'
}},
$self
->
{
'labelgt'
});
$data
[
$gt_index
]
||=
[]
;
}
return
\
@data
;
}
# Flatten the data structure into a list of series_ids
sub
getSeriesIDs
{
my
$self
=
shift
;
my
@series_ids
;
foreach
my
$line
(
@
{
$self
->
{
'lines'
}})
{
foreach
my
$series
(
@$line
)
{
push
(
@series_ids
,
$series
->
{
'series_id'
});
}
}
return
@series_ids
;
}
# Class method to get the data necessary to populate the "select series"
# widgets on various pages.
sub
getVisibleSeries
{
my
%
cats
;
# Get all visible series
my
$dbh
=
Bugzilla
->
dbh
;
my
$serieses
=
$dbh
->
selectall_arrayref
(
"SELECT cc1.name, cc2.name, "
.
"series.name, series.series_id "
.
"FROM series "
.
"LEFT JOIN series_categories AS cc1 "
.
" ON series.category = cc1.category_id "
.
"LEFT JOIN series_categories AS cc2 "
.
" ON series.subcategory = cc2.category_id "
.
"LEFT JOIN user_series_map AS ucm "
.
" ON series.series_id = ucm.series_id "
.
"WHERE ucm.user_id = 0 OR ucm.user_id = $::userid"
);
foreach
my
$series
(
@$serieses
)
{
my
(
$cat
,
$subcat
,
$name
,
$series_id
)
=
@$series
;
$cats
{
$cat
}{
$subcat
}{
$name
}
=
$series_id
;
}
return
\%
cats
;
}
sub
generateDateProgression
{
my
(
$datefrom
,
$dateto
)
=
@_
;
my
@progression
;
$dateto
=
$dateto
||
time
();
my
$oneday
=
60
*
60
*
24
;
# When the from and to dates are converted by str2time(), you end up with
# a time figure representing midnight at the beginning of that day. We
# adjust the times by 1/3 and 2/3 of a day respectively to prevent
# edge conditions in time2str().
$datefrom
+=
$oneday
/
3
;
$dateto
+=
(
2
*
$oneday
)
/
3
;
while
(
$datefrom
<
$dateto
)
{
push
(
@progression
,
&::
time2str
(
"%Y-%m-%d"
,
$datefrom
));
$datefrom
+=
$oneday
;
}
return
\
@progression
;
}
sub
dump
{
my
$self
=
shift
;
# Make sure we've read in our data
my
$data
=
$self
->
data
;
require
Data::
Dumper
;
print
"<pre>Bugzilla::Chart object:\n"
;
print
Data::Dumper::
Dumper
(
$self
);
print
"</pre>"
;
}
1
;
Bugzilla/Series.pm
0 → 100644
View file @
4f6b75a6
# -*- 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): Gervase Markham <gerv@gerv.net>
use
strict
;
use
lib
"."
;
# This module implements a series - a set of data to be plotted on a chart.
package
Bugzilla::
Series
;
use
Bugzilla
;
use
Bugzilla::
Util
;
use
Bugzilla::
User
;
sub
new
{
my
$invocant
=
shift
;
my
$class
=
ref
(
$invocant
)
||
$invocant
;
# Create a ref to an empty hash and bless it
my
$self
=
{};
bless
(
$self
,
$class
);
if
(
$#_
==
0
)
{
if
(
ref
(
$_
[
0
]))
{
# We've been given a CGI object
$self
->
readParametersFromCGI
(
$_
[
0
]);
$self
->
createInDatabase
();
}
else
{
# We've been given a series_id.
$self
->
initFromDatabase
(
$_
[
0
]);
}
}
elsif
(
$#_
>=
3
)
{
$self
->
initFromParameters
(
@_
);
}
else
{
die
(
"Bad parameters passed in - invalid number of args \($#_\)($_)"
);
}
return
$self
->
{
'already_exists'
}
?
$self
->
{
'series_id'
}
:
$self
;
}
sub
initFromDatabase
{
my
$self
=
shift
;
my
$series_id
=
shift
;
&::
detaint_natural
(
$series_id
)
||
&::
ThrowCodeError
(
"invalid_series_id"
,
{
'series_id'
=>
$series_id
});
my
$dbh
=
Bugzilla
->
dbh
;
my
@series
=
$dbh
->
selectrow_array
(
"SELECT series.series_id, cc1.name, "
.
"cc2.name, series.name, series.creator, series.frequency, "
.
"series.query "
.
"FROM series "
.
"LEFT JOIN series_categories AS cc1 "
.
" ON series.category = cc1.category_id "
.
"LEFT JOIN series_categories AS cc2 "
.
" ON series.subcategory = cc2.category_id "
.
"WHERE series.series_id = $series_id"
);
if
(
@series
)
{
$self
->
initFromParameters
(
@series
);
}
else
{
&::
ThrowCodeError
(
"invalid_series_id"
,
{
'series_id'
=>
$series_id
});
}
}
sub
initFromParameters
{
my
$self
=
shift
;
# The first four parameters are compulsory, unless you immediately call
# createInDatabase(), in which case series_id can be left off.
(
$self
->
{
'series_id'
},
$self
->
{
'category'
},
$self
->
{
'subcategory'
},
$self
->
{
'name'
},
$self
->
{
'creator'
},
$self
->
{
'frequency'
},
$self
->
{
'query'
})
=
@_
;
$self
->
{
'public'
}
=
$self
->
isSubscribed
(
0
);
$self
->
{
'subscribed'
}
=
$self
->
isSubscribed
(
$::userid
);
}
sub
createInDatabase
{
my
$self
=
shift
;
# Lock some tables
my
$dbh
=
Bugzilla
->
dbh
;
$dbh
->
do
(
"LOCK TABLES series_categories WRITE, series WRITE, "
.
"user_series_map WRITE"
);
my
$category_id
=
getCategoryID
(
$self
->
{
'category'
});
my
$subcategory_id
=
getCategoryID
(
$self
->
{
'subcategory'
});
$self
->
{
'creator'
}
=
$::userid
;
# Check for the series currently existing
trick_taint
(
$self
->
{
'name'
});
$self
->
{
'series_id'
}
=
$dbh
->
selectrow_array
(
"SELECT series_id "
.
"FROM series WHERE category = $category_id "
.
"AND subcategory = $subcategory_id AND name = "
.
$dbh
->
quote
(
$self
->
{
'name'
}));
if
(
$self
->
{
'series_id'
})
{
$self
->
{
'already_exists'
}
=
1
;
}
else
{
trick_taint
(
$self
->
{
'query'
});
# Insert the new series into the series table
$dbh
->
do
(
"INSERT INTO series (creator, category, subcategory, "
.
"name, frequency, query) VALUES ($self->{'creator'}, "
.
"$category_id, $subcategory_id, "
.
$dbh
->
quote
(
$self
->
{
'name'
})
.
", $self->{'frequency'},"
.
$dbh
->
quote
(
$self
->
{
'query'
})
.
")"
);
# Retrieve series_id
$self
->
{
'series_id'
}
=
$dbh
->
selectrow_array
(
"SELECT MAX(series_id) "
.
"FROM series"
);
$self
->
{
'series_id'
}
||
&::
ThrowCodeError
(
"missing_series_id"
,
{
'series'
=>
$self
});
# Subscribe user to the newly-created series.
$self
->
subscribe
(
$::userid
);
# Public series are subscribed to by userid 0.
$self
->
subscribe
(
0
)
if
(
$self
->
{
'public'
}
&&
$::userid
!=
0
);
}
$dbh
->
do
(
"UNLOCK TABLES"
);
}
# Get a category or subcategory IDs, creating the category if it doesn't exist.
sub
getCategoryID
{
my
(
$category
)
=
@_
;
my
$category_id
;
my
$dbh
=
Bugzilla
->
dbh
;
# This seems for the best idiom for "Do A. Then maybe do B and A again."
while
(
1
)
{
# We are quoting this to put it in the DB, so we can remove taint
trick_taint
(
$category
);
$category_id
=
$dbh
->
selectrow_array
(
"SELECT category_id "
.
"from series_categories "
.
"WHERE name ="
.
$dbh
->
quote
(
$category
));
last
if
$category_id
;
$dbh
->
do
(
"INSERT INTO series_categories (name) "
.
"VALUES ("
.
$dbh
->
quote
(
$category
)
.
")"
);
}
return
$category_id
;
}
sub
readParametersFromCGI
{
my
$self
=
shift
;
my
$cgi
=
shift
;
$self
->
{
'category'
}
=
$cgi
->
param
(
'category'
)
||
$cgi
->
param
(
'newcategory'
)
||
&::
ThrowUserError
(
"missing_category"
);
$self
->
{
'subcategory'
}
=
$cgi
->
param
(
'subcategory'
)
||
$cgi
->
param
(
'newsubcategory'
)
||
&::
ThrowUserError
(
"missing_subcategory"
);
$self
->
{
'name'
}
=
$cgi
->
param
(
'name'
)
||
&::
ThrowUserError
(
"missing_name"
);
$self
->
{
'frequency'
}
=
$cgi
->
param
(
'frequency'
);
detaint_natural
(
$self
->
{
'frequency'
})
||
&::
ThrowUserError
(
"missing_frequency"
);
$self
->
{
'public'
}
=
$cgi
->
param
(
'public'
)
?
1
:
0
;
$self
->
{
'query'
}
=
$cgi
->
canonicalise_query
(
"format"
,
"ctype"
,
"action"
,
"category"
,
"subcategory"
,
"name"
,
"frequency"
,
"public"
,
"query_format"
);
}
sub
alter
{
my
$self
=
shift
;
my
$cgi
=
shift
;
my
$old_public
=
$self
->
{
'public'
};
# Note: $self->{'query'} will be meaningless after this call
$self
->
readParametersFromCGI
(
$cgi
);
my
$category_id
=
getCategoryID
(
$self
->
{
'category'
});
my
$subcategory_id
=
getCategoryID
(
$self
->
{
'subcategory'
});
# Update the entry
trick_taint
(
$self
->
{
'name'
});
my
$dbh
=
Bugzilla
->
dbh
;
$dbh
->
do
(
"UPDATE series SET "
.
"category = $category_id, subcategory = $subcategory_id "
.
", name = "
.
$dbh
->
quote
(
$self
->
{
'name'
})
.
", frequency = $self->{'frequency'} "
.
"WHERE series_id = $self->{'series_id'}"
);
# Update the publicness of this query.
if
(
$old_public
&&
!
$self
->
{
'public'
})
{
$self
->
unsubscribe
(
0
);
}
elsif
(
!
$old_public
&&
$self
->
{
'public'
})
{
$self
->
subscribe
(
0
);
}
}
sub
subscribe
{
my
$self
=
shift
;
my
$userid
=
shift
;
if
(
!
$self
->
isSubscribed
(
$userid
))
{
# Subscribe current user to series_id
my
$dbh
=
Bugzilla
->
dbh
;
$dbh
->
do
(
"INSERT INTO user_series_map "
.
"VALUES($userid, $self->{'series_id'})"
);
}
}
sub
unsubscribe
{
my
$self
=
shift
;
my
$userid
=
shift
;
if
(
$self
->
isSubscribed
(
$userid
))
{
# Remove current user's subscription to series_id
my
$dbh
=
Bugzilla
->
dbh
;
$dbh
->
do
(
"DELETE FROM user_series_map "
.
"WHERE user_id = $userid AND series_id = $self->{'series_id'}"
);
}
}
sub
isSubscribed
{
my
$self
=
shift
;
my
$userid
=
shift
;
my
$dbh
=
Bugzilla
->
dbh
;
my
$issubscribed
=
$dbh
->
selectrow_array
(
"SELECT 1 FROM user_series_map "
.
"WHERE user_id = $userid "
.
"AND series_id = $self->{'series_id'}"
);
return
$issubscribed
;
}
1
;
Bugzilla/Template.pm
View file @
4f6b75a6
...
...
@@ -121,6 +121,13 @@ $Template::Stash::LIST_OPS->{ containsany } =
return
0
;
};
# Allow us to still get the scalar if we use the list operation ".0" on it,
# as we often do for defaults in query.cgi and other places.
$
Template::Stash::
SCALAR_OPS
->
{
0
}
=
sub
{
return
$_
[
0
];
};
# Add a "substr" method to the Template Toolkit's "scalar" object
# that returns a substring of a string.
$
Template::Stash::
SCALAR_OPS
->
{
substr
}
=
...
...
buglist.cgi
View file @
4f6b75a6
...
...
@@ -173,6 +173,18 @@ sub LookupNamedQuery {
return
$result
;
}
sub
LookupSeries
{
my
(
$series_id
)
=
@_
;
detaint_natural
(
$series_id
)
||
ThrowCodeError
(
"invalid_series_id"
);
my
$dbh
=
Bugzilla
->
dbh
;
my
$result
=
$dbh
->
selectrow_array
(
"SELECT query FROM series "
.
"WHERE series_id = $series_id"
);
$result
||
ThrowCodeError
(
"invalid_series_id"
,
{
'series_id'
=>
$series_id
});
return
$result
;
}
sub
GetQuip
{
my
$quip
;
...
...
@@ -256,6 +268,12 @@ if ($::FORM{'cmdtype'} eq "dorem") {
$params
=
new
Bugzilla::
CGI
(
$::buffer
);
$order
=
$params
->
param
(
'order'
)
||
$order
;
}
elsif
(
$::FORM
{
'remaction'
}
eq
"runseries"
)
{
$::buffer
=
LookupSeries
(
$::FORM
{
"series_id"
});
$vars
->
{
'title'
}
=
"Bug List: $::FORM{'namedcmd'}"
;
$params
=
new
Bugzilla::
CGI
(
$::buffer
);
$order
=
$params
->
param
(
'order'
)
||
$order
;
}
elsif
(
$::FORM
{
'remaction'
}
eq
"load"
)
{
my
$url
=
"query.cgi?"
.
LookupNamedQuery
(
$::FORM
{
"namedcmd"
});
print
$cgi
->
redirect
(
-
location
=>
$url
);
...
...
chart.cgi
0 → 100755
View file @
4f6b75a6
#!/usr/bonsaitools/bin/perl -wT
# -*- 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): Gervase Markham <gerv@gerv.net>
# Glossary:
# series: An individual, defined set of data plotted over time.
# line: A set of one or more series, to be summed and drawn as a single
# line when the series is plotted.
# chart: A set of lines
# So when you select rows in the UI, you are selecting one or more lines, not
# series.
# Generic Charting TODO:
#
# JS-less chart creation - hard.
# Broken image on error or no data - need to do much better.
# Centralise permission checking, so UserInGroup('editbugs') not scattered
# everywhere.
# Better protection on collectstats.pl for second run in a day
# User documentation :-)
#
# Bonus:
# Offer subscription when you get a "series already exists" error?
use
strict
;
use
lib
qw(.)
;
require
"CGI.pl"
;
use
Bugzilla::
Chart
;
use
Bugzilla::
Series
;
use
vars
qw($cgi $template $vars)
;
# Go back to query.cgi if we are adding a boolean chart parameter.
if
(
grep
(
/^cmd-/
,
$cgi
->
param
()))
{
my
$params
=
$cgi
->
canonicalise_query
(
"format"
,
"ctype"
,
"action"
);
print
"Location: query.cgi?format="
.
$cgi
->
param
(
'query_format'
)
.
(
$params
?
"&$params"
:
""
)
.
"\n\n"
;
exit
;
}
my
$template
=
Bugzilla
->
template
;
my
$action
=
$cgi
->
param
(
'action'
);
my
$series_id
=
$cgi
->
param
(
'series_id'
);
# Because some actions are chosen by buttons, we can't encode them as the value
# of the action param, because that value is localisation-dependent. So, we
# encode it in the name, as "action-<action>". Some params even contain the
# series_id they apply to (e.g. subscribe, unsubscribe.)
my
@actions
=
grep
(
/^action-/
,
$cgi
->
param
());
if
(
$actions
[
0
]
&&
$actions
[
0
]
=~
/^action-([^\d]+)(\d*)$/
)
{
$action
=
$1
;
$series_id
=
$2
if
$2
;
}
$action
||=
"assemble"
;
# Go to buglist.cgi if we are doing a search.
if
(
$action
eq
"search"
)
{
my
$params
=
$cgi
->
canonicalise_query
(
"format"
,
"ctype"
,
"action"
);
print
"Location: buglist.cgi"
.
(
$params
?
"?$params"
:
""
)
.
"\n\n"
;
exit
;
}
ConnectToDatabase
();
confirm_login
();
# All these actions relate to chart construction.
if
(
$action
=~
/^(assemble|add|remove|sum|subscribe|unsubscribe)$/
)
{
# These two need to be done before the creation of the Chart object, so
# that the changes they make will be reflected in it.
if
(
$action
=~
/^subscribe|unsubscribe$/
)
{
my
$series
=
new
Bugzilla::
Series
(
$series_id
);
$series
->
$action
(
$::userid
);
}
my
$chart
=
new
Bugzilla::
Chart
(
$cgi
);
if
(
$action
=~
/^remove|sum$/
)
{
$chart
->
$action
(
getSelectedLines
());
}
elsif
(
$action
eq
"add"
)
{
my
@series_ids
=
getAndValidateSeriesIDs
();
$chart
->
add
(
@series_ids
);
}
view
(
$chart
);
}
elsif
(
$action
eq
"plot"
)
{
plot
();
}
elsif
(
$action
eq
"wrap"
)
{
# For CSV "wrap", we go straight to "plot".
if
(
$cgi
->
param
(
'ctype'
)
&&
$cgi
->
param
(
'ctype'
)
eq
"csv"
)
{
plot
();
}
else
{
wrap
();
}
}
elsif
(
$action
eq
"create"
)
{
assertCanCreate
(
$cgi
);
my
$series
=
new
Bugzilla::
Series
(
$cgi
);
if
(
ref
(
$series
))
{
$vars
->
{
'message'
}
=
"series_created"
;
}
else
{
$vars
->
{
'message'
}
=
"series_already_exists"
;
$series
=
new
Bugzilla::
Series
(
$series
);
}
$vars
->
{
'series'
}
=
$series
;
print
"Content-Type: text/html\n\n"
;
$template
->
process
(
"global/message.html.tmpl"
,
$vars
)
||
ThrowTemplateError
(
$template
->
error
());
}
elsif
(
$action
eq
"edit"
)
{
$series_id
||
ThrowCodeError
(
"invalid_series_id"
);
assertCanEdit
(
$series_id
);
my
$series
=
new
Bugzilla::
Series
(
$series_id
);
edit
(
$series
);
}
elsif
(
$action
eq
"alter"
)
{
$series_id
||
ThrowCodeError
(
"invalid_series_id"
);
assertCanEdit
(
$series_id
);
my
$series
=
new
Bugzilla::
Series
(
$series_id
);
$series
->
alter
(
$cgi
);
edit
(
$series
);
}
else
{
ThrowCodeError
(
"unknown_action"
);
}
exit
;
# Find any selected series and return either the first or all of them.
sub
getAndValidateSeriesIDs
{
my
@series_ids
=
grep
(
/^\d+$/
,
$cgi
->
param
(
"name"
));
return
wantarray
?
@series_ids
:
$series_ids
[
0
];
}
# Return a list of IDs of all the lines selected in the UI.
sub
getSelectedLines
{
my
@ids
=
map
{
/^select(\d+)$/
?
$1
:
()
}
$cgi
->
param
();
return
@ids
;
}
# Check if the user is the owner of series_id or is an admin.
sub
assertCanEdit
{
my
(
$series_id
)
=
@_
;
return
if
UserInGroup
(
"admin"
);
my
$dbh
=
Bugzilla
->
dbh
;
my
$iscreator
=
$dbh
->
selectrow_array
(
"SELECT creator = ? FROM series "
.
"WHERE series_id = ?"
,
undef
,
$::userid
,
$series_id
);
$iscreator
||
ThrowUserError
(
"illegal_series_edit"
);
}
# Check if the user is permitted to create this series with these parameters.
sub
assertCanCreate
{
my
(
$cgi
)
=
shift
;
UserInGroup
(
"editbugs"
)
||
ThrowUserError
(
"illegal_series_creation"
);
# Only admins may create public queries
UserInGroup
(
'admin'
)
||
$cgi
->
delete
(
'public'
);
# Check permission for frequency
my
$min_freq
=
7
;
if
(
$cgi
->
param
(
'frequency'
)
<
$min_freq
&&
!
UserInGroup
(
"admin"
))
{
ThrowUserError
(
"illegal_frequency"
,
{
'minimum'
=>
$min_freq
});
}
}
sub
validateWidthAndHeight
{
$vars
->
{
'width'
}
=
$cgi
->
param
(
'width'
);
$vars
->
{
'height'
}
=
$cgi
->
param
(
'height'
);
if
(
defined
(
$vars
->
{
'width'
}))
{
(
detaint_natural
(
$vars
->
{
'width'
})
&&
$vars
->
{
'width'
}
>
0
)
||
ThrowCodeError
(
"invalid_dimensions"
);
}
if
(
defined
(
$vars
->
{
'height'
}))
{
(
detaint_natural
(
$vars
->
{
'height'
})
&&
$vars
->
{
'height'
}
>
0
)
||
ThrowCodeError
(
"invalid_dimensions"
);
}
# The equivalent of 2000 square seems like a very reasonable maximum size.
# This is merely meant to prevent accidental or deliberate DOS, and should
# have no effect in practice.
if
(
$vars
->
{
'width'
}
&&
$vars
->
{
'height'
})
{
((
$vars
->
{
'width'
}
*
$vars
->
{
'height'
})
<=
4000000
)
||
ThrowUserError
(
"chart_too_large"
);
}
}
sub
edit
{
my
$series
=
shift
;
$vars
->
{
'category'
}
=
Bugzilla::Chart::
getVisibleSeries
();
$vars
->
{
'creator'
}
=
new
Bugzilla::
User
(
$series
->
{
'creator'
});
# If we've got any parameters, use those in preference to the values
# read from the database. This is a bit ugly, but I can't see a better
# way to make this work in the no-JS situation.
if
(
$cgi
->
param
(
'category'
)
||
$cgi
->
param
(
'subcategory'
)
||
$cgi
->
param
(
'name'
)
||
$cgi
->
param
(
'frequency'
)
||
$cgi
->
param
(
'public'
))
{
$vars
->
{
'default'
}
=
new
Bugzilla::
Series
(
$series
->
{
'series_id'
},
$cgi
->
param
(
'category'
)
||
$series
->
{
'category'
},
$cgi
->
param
(
'subcategory'
)
||
$series
->
{
'subcategory'
},
$cgi
->
param
(
'name'
)
||
$series
->
{
'name'
},
$series
->
{
'creator'
},
$cgi
->
param
(
'frequency'
)
||
$series
->
{
'frequency'
});
$vars
->
{
'default'
}{
'public'
}
=
$cgi
->
param
(
'public'
)
||
$series
->
{
'public'
};
}
else
{
$vars
->
{
'default'
}
=
$series
;
}
print
"Content-Type: text/html\n\n"
;
$template
->
process
(
"reports/edit-series.html.tmpl"
,
$vars
)
||
ThrowTemplateError
(
$template
->
error
());
}
sub
plot
{
validateWidthAndHeight
();
$vars
->
{
'chart'
}
=
new
Bugzilla::
Chart
(
$cgi
);
my
$format
=
&::
GetFormat
(
"reports/chart"
,
""
,
$cgi
->
param
(
'ctype'
));
# Debugging PNGs is a pain; we need to be able to see the error messages
if
(
$cgi
->
param
(
'debug'
))
{
print
"Content-Type: text/html\n\n"
;
$vars
->
{
'chart'
}
->
dump
();
}
print
"Content-Type: $format->{'ctype'}\n\n"
;
$template
->
process
(
$format
->
{
'template'
},
$vars
)
||
ThrowTemplateError
(
$template
->
error
());
}
sub
wrap
{
validateWidthAndHeight
();
# We create a Chart object so we can validate the parameters
my
$chart
=
new
Bugzilla::
Chart
(
$cgi
);
$vars
->
{
'time'
}
=
time
();
$vars
->
{
'imagebase'
}
=
$cgi
->
canonicalise_query
(
"action"
,
"action-wrap"
,
"ctype"
,
"format"
,
"width"
,
"height"
);
print
"Content-Type:text/html\n\n"
;
$template
->
process
(
"reports/chart.html.tmpl"
,
$vars
)
||
ThrowTemplateError
(
$template
->
error
());
}
sub
view
{
my
$chart
=
shift
;
# Set defaults
foreach
my
$field
(
'category'
,
'subcategory'
,
'name'
,
'ctype'
)
{
$vars
->
{
'default'
}{
$field
}
=
$cgi
->
param
(
$field
)
||
0
;
}
# Pass the state object to the display UI.
$vars
->
{
'chart'
}
=
$chart
;
$vars
->
{
'category'
}
=
Bugzilla::Chart::
getVisibleSeries
();
print
"Content-Type: text/html\n\n"
;
# If we have having problems with bad data, we can set debug=1 to dump
# the data structure.
$chart
->
dump
()
if
$cgi
->
param
(
'debug'
);
$template
->
process
(
"reports/create-chart.html.tmpl"
,
$vars
)
||
ThrowTemplateError
(
$template
->
error
());
}
checksetup.pl
View file @
4f6b75a6
...
...
@@ -26,6 +26,7 @@
# Jacob Steenhagen <jake@bugzilla.org>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# Tobias Burnus <burnus@net-b.de>
# Gervase Markham <gerv@gerv.net>
#
#
# Direct any questions on this source code to
...
...
@@ -112,6 +113,8 @@
#
use
strict
;
use
lib
"."
;
use
vars
qw( $db_name %answer )
;
use
Bugzilla::
Constants
;
...
...
@@ -1737,6 +1740,42 @@ $table{group_control_map} =
unique(product_id, group_id),
index(group_id)'
;
# 2003-06-26 gerv@gerv.net, bug 16009
# Generic charting over time of arbitrary queries.
# Queries are disabled when frequency == 0.
$table
{
series
}
=
'series_id mediumint auto_increment primary key,
creator mediumint not null,
category smallint not null,
subcategory smallint not null,
name varchar(64) not null,
frequency smallint not null,
last_viewed datetime default null,
query mediumtext not null,
index(creator),
unique(creator, category, subcategory, name)'
;
$table
{
series_data
}
=
'series_id mediumint not null,
date datetime not null,
value mediumint not null,
unique(series_id, date)'
;
$table
{
user_series_map
}
=
'user_id mediumint not null,
series_id mediumint not null,
index(series_id),
unique(user_id, series_id)'
;
$table
{
series_categories
}
=
'category_id smallint auto_increment primary key,
name varchar(64) not null,
unique(name)'
;
###########################################################################
# Create tables
###########################################################################
...
...
@@ -3530,6 +3569,109 @@ if ($mapcnt == 0) {
}
}
# 2003-06-26 Copy the old charting data into the database, and create the
# queries that will keep it all running. When the old charting system goes
# away, if this code ever runs, it'll just find no files and do nothing.
my
$series_exists
=
$dbh
->
selectrow_array
(
"SELECT 1 FROM series LIMIT 1"
);
if
(
!
$series_exists
)
{
print
"Migrating old chart data into database ...\n"
unless
$silent
;
use
Bugzilla::
Series
;
# We prepare the handle to insert the series data
my
$seriesdatasth
=
$dbh
->
prepare
(
"INSERT INTO series_data "
.
"(series_id, date, value) "
.
"VALUES (?, ?, ?)"
);
# Fields in the data file (matches the current collectstats.pl)
my
@statuses
=
qw(NEW ASSIGNED REOPENED UNCONFIRMED RESOLVED VERIFIED CLOSED)
;
my
@resolutions
=
qw(FIXED INVALID WONTFIX LATER REMIND DUPLICATE WORKSFORME MOVED)
;
my
@fields
=
(
@statuses
,
@resolutions
);
# We have a localisation problem here. Where do we get these values?
my
$all_name
=
"-All-"
;
my
$open_name
=
"All Open"
;
# We can't give the Series we create a meaningful owner; that's not a big
# problem. But we do need to set this global, otherwise Series.pm objects.
$::userid
=
0
;
my
$products
=
$dbh
->
selectall_arrayref
(
"SELECT name FROM products"
);
foreach
my
$product
((
map
{
$_
->
[
0
]
}
@$products
),
"-All-"
)
{
# First, create the series
my
%
queries
;
my
%
seriesids
;
my
$query_prod
=
""
;
if
(
$product
ne
"-All-"
)
{
$query_prod
=
"product="
.
html_quote
(
$product
)
.
"&"
;
}
# The query for statuses is different to that for resolutions.
$queries
{
$_
}
=
(
$query_prod
.
"status=$_"
)
foreach
(
@statuses
);
$queries
{
$_
}
=
(
$query_prod
.
"resolution=$_"
)
foreach
(
@resolutions
);
foreach
my
$field
(
@fields
)
{
# Create a Series for each field in this product
my
$series
=
new
Bugzilla::
Series
(
-
1
,
$product
,
$all_name
,
$field
,
$::userid
,
1
,
$queries
{
$field
});
$series
->
createInDatabase
();
$seriesids
{
$field
}
=
$series
->
{
'series_id'
};
}
# We also add a new query for "Open", so that migrated products get
# the same set as new products (see editproducts.cgi.)
my
@openedstatuses
=
(
"UNCONFIRMED"
,
"NEW"
,
"ASSIGNED"
,
"REOPENED"
);
my
$query
=
join
(
"&"
,
map
{
"bug_status=$_"
}
@openedstatuses
);
my
$series
=
new
Bugzilla::
Series
(
-
1
,
$product
,
$all_name
,
$open_name
,
$::userid
,
1
,
$query_prod
.
$query
);
$series
->
createInDatabase
();
# Now, we attempt to read in historical data, if any
# Convert the name in the same way that collectstats.pl does
my
$product_file
=
$product
;
$product_file
=~
s/\//-/gs
;
$product_file
=
"data/mining/$product_file"
;
# There are many reasons that this might fail (e.g. no stats for this
# product), so we don't worry if it does.
open
(
IN
,
$product_file
)
or
next
;
# The data files should be in a standard format, even for old
# Bugzillas, because of the conversion code further up this file.
my
%
data
;
while
(
<
IN
>
)
{
if
(
/^(\d+\|.*)/
)
{
my
@numbers
=
split
(
/\||\r/
,
$1
);
for
my
$i
(
0
..
$#fields
)
{
# $numbers[0] is the date
$data
{
$fields
[
$i
]}{
$numbers
[
0
]}
=
$numbers
[
$i
+
1
];
}
}
}
close
(
IN
);
foreach
my
$field
(
@fields
)
{
# Insert values into series_data: series_id, date, value
my
%
fielddata
=
%
{
$data
{
$field
}};
foreach
my
$date
(
keys
%
fielddata
)
{
# We prepared this above
$seriesdatasth
->
execute
(
$seriesids
{
$field
},
$dbh
->
quote
(
$date
),
$fielddata
{
$date
});
}
}
}
}
# If you had to change the --TABLE-- definition in any way, then add your
# differential change code *** A B O V E *** this comment.
#
...
...
collectstats.pl
View file @
4f6b75a6
...
...
@@ -32,7 +32,10 @@ use strict;
use
IO::
Handle
;
use
vars
@::legal_product
;
use
lib
"."
;
require
"globals.pl"
;
use
Bugzilla::
Search
;
use
Bugzilla::
User
;
use
Bugzilla
;
...
...
@@ -79,6 +82,8 @@ my $tend = time;
&
calculate_dupes
();
CollectSeriesData
();
# Generate a static RDF file containing the default view of the duplicates data.
open
(
CGI
,
"GATEWAY_INTERFACE=cmdline REQUEST_METHOD=GET QUERY_STRING=ctype=rdf ./duplicates.cgi |"
)
||
die
"can't fork duplicates.cgi: $!"
;
...
...
@@ -421,3 +426,71 @@ sub delta_time {
my
$seconds
=
$delta
-
(
$minutes
*
60
)
-
(
$hours
*
3600
);
return
sprintf
(
"%02d:%02d:%02d"
,
$hours
,
$minutes
,
$seconds
);
}
sub
CollectSeriesData
{
# We need some way of randomising the distribution of series, such that
# all of the series which are to be run every 7 days don't run on the same
# day. This is because this might put the server under severe load if a
# particular frequency, such as once a week, is very common. We achieve
# this by only running queries when:
# (days_since_epoch + series_id) % frequency = 0. So they'll run every
# <frequency> days, but the start date depends on the series_id.
my
$days_since_epoch
=
int
(
time
()
/
(
60
*
60
*
24
));
my
$today
=
today_dash
();
CleanupChartTables
()
if
(
$days_since_epoch
%
7
==
0
);
my
$dbh
=
Bugzilla
->
dbh
;
my
$serieses
=
$dbh
->
selectall_hashref
(
"SELECT series_id, query "
.
"FROM series "
.
"WHERE frequency != 0 AND "
.
"($days_since_epoch + series_id) % frequency = 0"
,
"series_id"
);
# We prepare the insertion into the data table, for efficiency.
my
$sth
=
$dbh
->
prepare
(
"INSERT INTO series_data "
.
"(series_id, date, value) "
.
"VALUES (?, "
.
$dbh
->
quote
(
$today
)
.
", ?)"
);
foreach
my
$series_id
(
keys
%
$serieses
)
{
# We set up the user for Search.pm's permission checking - each series
# runs with the permissions of its creator.
$::vars
->
{
'user'
}
=
new
Bugzilla::
User
(
$serieses
->
{
$series_id
}
->
{
'creator'
});
my
$cgi
=
new
Bugzilla::
CGI
(
$serieses
->
{
$series_id
}
->
{
'query'
});
my
$search
=
new
Bugzilla::
Search
(
'params'
=>
$cgi
,
'fields'
=>
[
"bugs.bug_id"
]);
my
$sql
=
$search
->
getSQL
();
# We need to count the returned rows. Without subselects, we can't
# do this directly in the SQL for all queries. So we do it by hand.
my
$data
=
$dbh
->
selectall_arrayref
(
$sql
);
my
$count
=
scalar
(
@$data
)
||
0
;
$sth
->
execute
(
$series_id
,
$count
);
}
}
sub
CleanupChartTables
{
my
$dbh
=
Bugzilla
->
dbh
;
$dbh
->
do
(
"LOCK TABLES series WRITE, user_series_map AS usm READ"
);
# Find all those that no-one subscribes to
my
$series_data
=
$dbh
->
selectall_arrayref
(
"SELECT series.series_id "
.
"FROM series LEFT JOIN user_series_map AS usm "
.
"ON series.series_id = usm.series_id "
.
"WHERE usm.series_id IS NULL"
);
my
$series_ids
=
join
(
","
,
map
({
$_
->
[
0
]
}
@$series_data
));
# Stop collecting data on all series which no-one is subscribed to.
if
(
$series_ids
)
{
$dbh
->
do
(
"UPDATE series SET frequency = 0 "
.
"WHERE series_id IN($series_ids)"
);
}
$dbh
->
do
(
"UNLOCK TABLES"
);
}
editcomponents.cgi
View file @
4f6b75a6
...
...
@@ -31,6 +31,8 @@ use lib ".";
require
"CGI.pl"
;
require
"globals.pl"
;
use
Bugzilla::
Series
;
# Shut up misguided -w warnings about "used only once". For some reason,
# "use vars" chokes on me when I try it here.
...
...
@@ -352,6 +354,8 @@ if ($action eq 'add') {
print
"</TR></TABLE>\n<HR>\n"
;
print
"<INPUT TYPE=SUBMIT VALUE=\"Add\">\n"
;
print
"<INPUT TYPE=HIDDEN NAME=\"action\" VALUE=\"new\">\n"
;
print
"<INPUT TYPE=HIDDEN NAME='open_name' VALUE='All Open'>\n"
;
print
"<INPUT TYPE=HIDDEN NAME='closed_name' VALUE='All Closed'>\n"
;
print
"</FORM>"
;
my
$other
=
$localtrailer
;
...
...
@@ -440,6 +444,32 @@ if ($action eq 'new') {
SqlQuote
(
$initialownerid
)
.
","
.
SqlQuote
(
$initialqacontactid
)
.
")"
);
# Insert default charting queries for this product.
# If they aren't using charting, this won't do any harm.
GetVersionTable
();
my
@series
;
my
$prodcomp
=
"&product=$product&component=$component"
;
# For localisation reasons, we get the title of the queries from the
# submitted form.
my
@openedstatuses
=
(
"UNCONFIRMED"
,
"NEW"
,
"ASSIGNED"
,
"REOPENED"
);
my
$statuses
=
join
(
"&"
,
map
{
"bug_status=$_"
}
@openedstatuses
);
push
(
@series
,
[
$::FORM
{
'open_name'
},
$statuses
.
$prodcomp
]);
my
$resolved
=
"field0-0-0=resolution&type0-0-0=notequals&value0-0-0=---"
;
push
(
@series
,
[
$::FORM
{
'closed_name'
},
$resolved
.
$prodcomp
]);
foreach
my
$sdata
(
@series
)
{
# We create the series with an nonsensical series_id, which is
# guaranteed not to exist. This is OK, because we immediately call
# createInDatabase().
my
$series
=
new
Bugzilla::
Series
(
-
1
,
$product
,
$component
,
$sdata
->
[
0
],
$::userid
,
1
,
$sdata
->
[
1
]);
$series
->
createInDatabase
();
}
# Make versioncache flush
unlink
"data/versioncache"
;
...
...
editproducts.cgi
View file @
4f6b75a6
...
...
@@ -33,9 +33,11 @@ use vars qw ($template $vars);
use
Bugzilla::
Constants
;
require
"CGI.pl"
;
require
"globals.pl"
;
use
Bugzilla::
Series
;
# Shut up misguided -w warnings about "used only once". "use vars" just
# doesn't work for me.
use
vars
qw(@legal_bug_status @legal_resolution)
;
sub
sillyness
{
my
$zz
;
...
...
@@ -272,6 +274,8 @@ if ($action eq 'add') {
print
"</TABLE>\n<HR>\n"
;
print
"<INPUT TYPE=SUBMIT VALUE=\"Add\">\n"
;
print
"<INPUT TYPE=HIDDEN NAME=\"action\" VALUE=\"new\">\n"
;
print
"<INPUT TYPE=HIDDEN NAME='subcategory' VALUE='-All-'>\n"
;
print
"<INPUT TYPE=HIDDEN NAME='open_name' VALUE='All Open'>\n"
;
print
"</FORM>"
;
my
$other
=
$localtrailer
;
...
...
@@ -349,7 +353,7 @@ if ($action eq 'new') {
# If we're using bug groups, then we need to create a group for this
# product as well. -JMR, 2/16/00
if
(
Param
(
"makeproductgroups"
))
{
if
(
Param
(
"makeproductgroups"
))
{
# Next we insert into the groups table
SendSQL
(
"INSERT INTO groups "
.
"(name, description, isbuggroup, last_changed) "
.
...
...
@@ -390,8 +394,39 @@ if ($action eq 'new') {
PopGlobalSQLState
();
}
}
}
# Insert default charting queries for this product.
# If they aren't using charting, this won't do any harm.
GetVersionTable
();
my
@series
;
# We do every status, every resolution, and an "opened" one as well.
foreach
my
$bug_status
(
@::legal_bug_status
)
{
push
(
@series
,
[
$bug_status
,
"bug_status=$bug_status"
]);
}
foreach
my
$resolution
(
@::legal_resolution
)
{
next
if
!
$resolution
;
push
(
@series
,
[
$resolution
,
"resolution=$resolution"
]);
}
# For localisation reasons, we get the name of the "global" subcategory
# and the title of the "open" query from the submitted form.
my
@openedstatuses
=
(
"UNCONFIRMED"
,
"NEW"
,
"ASSIGNED"
,
"REOPENED"
);
my
$query
=
join
(
"&"
,
map
{
"bug_status=$_"
}
@openedstatuses
);
push
(
@series
,
[
$::FORM
{
'open_name'
},
$query
]);
foreach
my
$sdata
(
@series
)
{
# We create the series with an nonsensical series_id, which is
# guaranteed not to exist. This is OK, because we immediately call
# createInDatabase().
my
$series
=
new
Bugzilla::
Series
(
-
1
,
$product
,
$::FORM
{
'subcategory'
},
$sdata
->
[
0
],
$::userid
,
1
,
$sdata
->
[
1
]
.
"&product=$product"
);
$series
->
createInDatabase
();
}
# Make versioncache flush
...
...
query.cgi
View file @
4f6b75a6
...
...
@@ -137,7 +137,9 @@ sub PrefillForm {
"status_whiteboard_type"
,
"bug_id"
,
"bugidtype"
,
"keywords"
,
"keywords_type"
,
"x_axis_field"
,
"y_axis_field"
,
"z_axis_field"
,
"chart_format"
,
"cumulate"
,
"x_labels_vertical"
)
"chart_format"
,
"cumulate"
,
"x_labels_vertical"
,
"category"
,
"subcategory"
,
"name"
,
"newcategory"
,
"newsubcategory"
,
"public"
,
"frequency"
)
{
# This is a bit of a hack. The default, empty list has
# three entries to accommodate the needs of the email fields -
...
...
@@ -378,6 +380,11 @@ $vars->{'userdefaultquery'} = $userdefaultquery;
$vars
->
{
'orders'
}
=
\
@orders
;
$default
{
'querytype'
}
=
$deforder
||
'Importance'
;
if
((
$::FORM
{
'query_format'
}
||
$::FORM
{
'format'
})
eq
"create-series"
)
{
require
Bugzilla::
Chart
;
$vars
->
{
'category'
}
=
Bugzilla::Chart::
getVisibleSeries
();
}
# Add in the defaults.
$vars
->
{
'default'
}
=
\%
default
;
...
...
template/en/default/filterexceptions.pl
View file @
4f6b75a6
...
...
@@ -197,6 +197,36 @@
'bug.delta'
,
],
'reports/chart.html.tmpl'
=>
[
'width'
,
'height'
,
'imageurl'
,
'sizeurl'
,
'height + 100'
,
'height - 100'
,
'width + 100'
,
'width - 100'
,
],
'reports/series-common.html.tmpl'
=>
[
'sel.name'
,
'sel.accesskey'
,
'"onchange=\'$sel.onchange\'" IF sel.onchange'
,
],
'reports/chart.csv.tmpl'
=>
[
'data.$j.$i'
,
],
'reports/create-chart.html.tmpl'
=>
[
'series.series_id'
,
'newidx'
,
],
'reports/edit-series.html.tmpl'
=>
[
'default.series_id'
,
],
'list/change-columns.html.tmpl'
=>
[
'column'
,
'field_descs.${column} || column'
,
#
...
...
@@ -293,6 +323,7 @@
'old_email'
,
# email address
'new_email'
,
# email address
'message_tag'
,
'series.frequency * 2'
,
],
'global/select-menu.html.tmpl'
=>
[
...
...
template/en/default/global/code-error.html.tmpl
View file @
4f6b75a6
...
...
@@ -132,6 +132,11 @@
[% title = "Invalid Dimensions" %]
The width or height specified is not a positive integer.
[% ELSIF error == "invalid_series_id" %]
[% title = "Invalid Series" %]
The series_id [% series_id FILTER html %] is not valid. It may be that
this series has been deleted.
[% ELSIF error == "mismatched_bug_ids_on_obsolete" %]
Attachment [% attach_id FILTER html %] ([% description FILTER html %])
is attached to bug [% attach_bug_id FILTER html %], but you tried to
...
...
@@ -178,6 +183,12 @@
[% ELSIF error == "missing_bug_id" %]
No bug ID was given.
[% ELSIF error == "missing_series_id" %]
Having inserted a series into the database, no series_id was returned for
it. Series: [% series.category FILTER html %] /
[%+ series.subcategory FILTER html %] /
[%+ series.name FILTER html %].
[% ELSIF error == "no_y_axis_defined" %]
No Y axis was defined when creating report. The X axis is optional,
but the Y axis is compulsory.
...
...
template/en/default/global/messages.html.tmpl
View file @
4f6b75a6
...
...
@@ -131,6 +131,29 @@
<a href="editflagtypes.cgi">Back to flag types.</a>
</p>
[% ELSIF message_tag == "series_already_exists" %]
[% title = "Series Already Exists" %]
A series <em>[% series.category FILTER html %] /
[%+ series.subcategory FILTER html %] /
[%+ series.name FILTER html %]</em>
already exists. If you want to create this series, you will need to give
it a different name. @@@ subscribe?
<br><br>
Go back or
<a href="query.cgi?format=create-series">create another series</a>.
[% ELSIF message_tag == "series_created" %]
[% title = "Series Created" %]
The series <em>[% series.category FILTER html %] /
[%+ series.subcategory FILTER html %] /
[%+ series.name FILTER html %]</em>
has been created. Note that you may need to wait up to
[% series.frequency * 2 %] days before there will be enough data for a
chart of this series to be produced.
<br><br>
Go back or
<a href="query.cgi?format=create-series">create another series</a>.
[% ELSIF message_tag == "shutdown" %]
[% title = "Bugzilla is Down" %]
[% Param("shutdownhtml") %]
...
...
template/en/default/global/user-error.html.tmpl
View file @
4f6b75a6
...
...
@@ -255,7 +255,7 @@
You entered <tt>[% value FILTER html %]</tt>, which isn't.
[% ELSIF error == "illegal_date" %]
[% title = "
Your Query Makes No Sens
e" %]
[% title = "
Illegal Dat
e" %]
'<tt>[% date FILTER html %]</tt>' is not a legal date.
[% ELSIF error == "illegal_email_address" %]
...
...
@@ -266,6 +266,11 @@
It must also not contain any of these special characters:
<tt>\ ( ) & < > , ; : " [ ]</tt>, or any whitespace.
[% ELSIF error == "illegal_frequency" %]
[% title = "Too Frequent" %]
Unless you are an administrator, you may not create series which are
run more often than once every [% minimum FILTER html %] days.
[% ELSIF error == "illegal_group_control_combination" %]
[% title = "Your Group Control Combination Is Illegal" %]
Your group control combination for group "
...
...
@@ -282,6 +287,18 @@
The name of your query cannot contain any of the following characters:
<, >, &.
[% ELSIF error == "illegal_series_creation" %]
You are not authorised to create series.
[% ELSIF error == "illegal_series_edit" %]
You are not authorised to edit this series. To do this, you must either
be its creator, or an administrator.
[% ELSIF error == "insufficient_data" %]
[% title = "Insufficient Data" %]
None of the series you selected have any data associated with them, so a
chart cannot be plotted.
[% ELSIF error == "insufficient_data_points" %]
We don't have enough data points to make a graph (yet).
...
...
@@ -352,10 +369,19 @@
if you are going to accept it. Part of accepting
a bug is giving an estimate of when it will be fixed.
[% ELSIF error == "misarranged_dates" %]
[% title = "Misarranged Dates" %]
Your start date ([% datefrom FILTER html %]) is after
your end date ([% dateto FILTER html %]).
[% ELSIF error == "missing_attachment_description" %]
[% title = "Missing Attachment Description" %]
You must enter a description for the attachment.
[% ELSIF error == "missing_category" %]
[% title = "Missing Category" %]
You did not specify a category for this series.
[% ELSIF error == "missing_content_type" %]
[% title = "Missing Content-Type" %]
You asked Bugzilla to auto-detect the content type, but
...
...
@@ -383,11 +409,23 @@
You must specify one or more fields in which to search for
<tt>[% email FILTER html %]</tt>.
[% ELSIF error == "missing_frequency" %]
[% title = "Missing Frequency" %]
You did not specify a valid frequency for this series.
[% ELSIF error == "missing_name" %]
[% title = "Missing Name" %]
You did not specify a name for this series.
[% ELSIF error == "missing_query" %]
[% title = "Missing Query" %]
The query named <em>[% queryname FILTER html %]</em> does not
exist.
[% ELSIF error == "missing_subcategory" %]
[% title = "Missing Subcategory" %]
You did not specify a subcategory for this series.
[% ELSIF error == "need_component" %]
[% title = "Component Required" %]
You must specify a component to help determine the new owner of these bugs.
...
...
template/en/default/reports/chart.csv.tmpl
0 → 100644
View file @
4f6b75a6
[%# 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 <gerv@gerv.net>
#%]
[% data = chart.data %]
Date\Series,
[% FOREACH label = chart.labels %]
[% label FILTER csv %][% "," UNLESS loop.last %]
[% END %]
[%# The data, which is in the correct format for GD, is conceptually the wrong
# way round for CSV output. So, we need to invert it here, which is why
# these loops aren't just plain FOREACH.
#%]
[% i = 0 %]
[% WHILE i < data.0.size %]
[% j = 0 %]
[% WHILE j < data.size %]
[% data.$j.$i %][% "," UNLESS (j == data.size - 1) %]
[% j = j + 1 %]
[% END %]
[% i = i + 1 %]
[% END %]
template/en/default/reports/chart.html.tmpl
0 → 100644
View file @
4f6b75a6
<!-- 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 <gerv@gerv.net>
#%]
[%# INTERFACE:
#%]
[% DEFAULT width = 600
height = 350
%]
[% PROCESS global/header.html.tmpl
title = "Chart"
h3 = time2str("%Y-%m-%d %H:%M:%S", time)
%]
<div align="center">
[% imageurl = BLOCK %]chart.cgi?
[% imagebase FILTER html %]&ctype=png&action=plot&width=
[% width %]&height=[% height -%]
[% END %]
<img alt="Graphical report results" src="[% imageurl %]"
width="[% width %]" height="[% height %]">
<p>
[% sizeurl = BLOCK %]chart.cgi?
[% imagebase FILTER html %]&action=wrap
[% END %]
<a href="[% sizeurl %]&width=[% width %]&height=
[% height + 100 %]">Taller</a><br>
<a href="[% sizeurl %]&width=[% width - 100 %]&height=
[% height %]">Thinner</a> *
<a href="[% sizeurl %]&width=[% width + 100 %]&height=
[% height %]">Fatter</a> <br>
<a href="[% sizeurl %]&width=[% width %]&height=
[% height - 100 %]">Shorter</a><br>
</p>
<p>
<a href="chart.cgi?
[% imagebase FILTER html %]&ctype=csv&action=plot">CSV</a> |
<a href="chart.cgi?[% imagebase FILTER html %]&action=assemble">Edit
this chart</a>
</p>
</div>
[% PROCESS global/footer.html.tmpl %]
template/en/default/reports/chart.png.tmpl
0 → 100644
View file @
4f6b75a6
[%# 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 <gerv@gerv.net>
#%]
[% y_label = "Bugs" %]
[% x_label = "Time" %]
[% IF cumulate %]
[% USE graph = GD.Graph.area(width, height) %]
[% graph.set(cumulate => "true") %]
[% ELSE %]
[% USE graph = GD.Graph.lines(width, height) %]
[% END %]
[% FILTER null;
x_label_skip = (30 * chart.data.0.size / width);
graph.set(x_label => x_label,
y_label => y_label,
y_tick_number => 8,
x_label_position => 0.5,
x_labels_vertical => 1,
x_label_skip => x_label_skip,
legend_placement => "RT",
line_width => 2);
# Workaround for the fact that set_legend won't take chart.labels directly,
# because chart.labels is an array reference rather than an array.
graph.set_legend(chart.labels.0, chart.labels.1, chart.labels.2,
chart.labels.3, chart.labels.4, chart.labels.5,
chart.labels.6, chart.labels.7, chart.labels.8,
chart.labels.9, chart.labels.10, chart.labels.11,
chart.labels.12, chart.labels.13, chart.labels.14,
chart.labels.15);
graph.plot(chart.data).png | stdout(1);
END;
-%]
template/en/default/reports/create-chart.html.tmpl
0 → 100644
View file @
4f6b75a6
<!-- 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 <gerv@gerv.net>
#%]
[%# INTERFACE:
# chart: Chart object representing the currently assembled chart.
# category: hash (keyed by category) of hashes (keyed by subcategory) of
# hashes (keyed by name), with value being the series_id of the
# series. Contains details of all series the user can see.
#%]
[% PROCESS global/header.html.tmpl
title = "Create Chart"
%]
[% PROCESS "reports/series-common.html.tmpl"
donames = 1
%]
<script>
[%# This function takes necessary action on selection of a subcategory %]
function subcatSelected() {
var cat = document.chartform.category.value;
var subcat = document.chartform.subcategory.value;
var names = series[cat][subcat];
var namewidget = document.chartform.name;
namewidget.options.length = 0;
var i = 0;
for (x in names) {
namewidget.options[i] = new Option(x, names[x]);
i++;
}
namewidget.options[0].selected = true;
checkNewState();
}
</script>
[% gttext = "Grand Total" %]
<h3>Current Data Sets:</h3>
<form method="get" action="chart.cgi" name="chartform">
[% IF chart.lines.size > 0 %]
<table border="0" cellspacing="2" cellpadding="2">
<tr>
<th>Select</th>
<th>As</th>
<th></th>
<th>Data Set</th>
<th>Subs</th>
<th></th>
</tr>
[%# The external loop has two counters; one which keeps track of where we
# are in the old labels array, and one which keeps track of the new
# indexes for the form elements. They are different if chart.lines has
# empty slots in it.
#%]
[% labelidx = 0 %]
[% newidx = 0 %]
[% FOREACH line = chart.lines %]
[% IF NOT line %]
[%# chart.lines has an empty slot, so chart.labels will too. We
# increment labelidx only to keep the labels in sync with the data.
#%]
[% labelidx = labelidx + 1 %]
[% NEXT %]
[% END %]
[% FOREACH series = line %]
<tr>
[% IF loop.first %]
<td align="center" rowspan="[% line.size %]">
<input type="checkbox" value="1" name="select[% newidx %]">
</td>
<td rowspan="[% line.size %]">
<input type="text" size="20" name="label[% newidx %]"
value="[% (chart.labels.$labelidx OR series.name)
FILTER html %]">
</td>
[% END %]
<td>
[% "{" IF line.size > 1 %]
</td>
<td>
<a href="buglist.cgi?cmdtype=dorem&namedcmd=
[% series.category FILTER html %]-
[% series.subcategory FILTER html %]-
[% series.name FILTER html -%]&series_id=
[% series.series_id %]&remaction=runseries">
[% series.category FILTER html %] /
[%+ series.subcategory FILTER html %] /
[%+ series.name FILTER html %]
</a>
<input type="hidden" name="line[% newidx %]"
value="[% series.series_id %]">
</td>
<td>
[% IF series.creator != 0 %]
[% IF series.subscribed %]
<input type="submit" value="Unsubscribe" style="width: 12ex;"
name="action-unsubscribe[% series.series_id %]">
[% ELSE %]
<input type="submit" value="Subscribe" style="width: 12ex;"
name="action-subscribe[% series.series_id %]">
[% END %]
[% END %]
</td>
<td align="center">
[% IF user.userid == series.creator OR UserInGroup("admin") %]
<a href="chart.cgi?action=edit&series_id=
[% series.series_id %]">Edit</a>
[% END %]
</td>
</tr>
[% END %]
[% labelidx = labelidx + 1 %]
[% newidx = newidx + 1 %]
[% END %]
[% IF chart.gt %]
<tr>
<td align="center">
<input type="checkbox" value="1" name="select65536">
<input type="hidden" value="1" name="gt">
</td>
<td>
<input type="text" size="20" name="labelgt"
value="[% (chart.labelgt OR gttext) FILTER html %]">
</td>
<td></td>
<td>
<i>[% gttext FILTER html %]</i>
</td>
<td></td>
<td></td>
</tr>
[% END %]
<tr>
<td colspan="6"> </td>
</tr>
<tr>
<td valign="bottom" style="text-align: center;">
<input type="submit" name="action-sum" value="Sum"
style="width: 5em;"><br>
<input type="submit" name="action-remove" value="Remove"
style="width: 5em;">
</td>
<td style="text-align: right; vertical-align: bottom;">
<b>Cumulate:</b>
<input type="checkbox" name="cumulate" value="1">
</td>
<td></td>
<td valign="bottom">
<b>Date Range:</b>
<input type="text" size="12" name="datefrom"
value="[% time2str("%Y-%m-%d", chart.datefrom) IF chart.datefrom%]">
<b>to</b>
<input type="text" size="12" name="dateto"
value="[% time2str("%Y-%m-%d", chart.dateto) IF chart.dateto %]">
</td>
<td valign="bottom">
</td>
<td style="text-align: right" valign="bottom">
<input type="submit" name="action-wrap" value="Chart"
style="width: 5em;">
</td>
</tr>
</table>
[% ELSE %]
<p><i>None</i></p>
[% END %]
<h3>Select Data Sets:</h3>
<table cellpadding="2" cellspacing="2" border="0">
[% IF NOT category OR category.size == 0 %]
<tr>
<td>
<i>You do not have permissions to see any data sets, or none
exist.</i>
</td>
</tr>
[% ELSE %]
<tr>
<th>Category:</th>
<noscript><th></th></noscript>
<th>Sub-category:</th>
<noscript><th></th></noscript>
<th>Name:</th>
<th><br>
</th>
</tr>
<tr>
[% PROCESS series_select sel = { name => 'category',
size => 5,
onchange = "catSelected();
subcatSelected();" } %]
<noscript>
<td>
<input type="submit" name="action-assemble" value="Update -->">
</td>
</noscript>
[% PROCESS series_select sel = { name => 'subcategory',
size => 5,
onchange = "subcatSelected()" } %]
<noscript>
<td>
<input type="submit" name="action-assemble" value="Update -->">
</td>
</noscript>
<td align="left">
<label for="name" accesskey="N">
<select name="name" id="name" style="width: 15em"
size="5" multiple="multiple"
[% FOREACH x = name.keys.sort %]
<option value="[% name.$x FILTER html %]"
[%# " selected" IF lsearch(default.name, x) != -1 %]>
[% x FILTER html %]</option>
[% END %]
</select>
</label>
</td>
<td style="text-align: center; vertical-align: middle;">
<input type="submit" name="action-add" value="Add"
style="width: 3em;"><br>
</td>
</tr>
[% END %]
</table>
<script>
document.chartform.category[0].selected = true;
catSelected();
subcatSelected();
</script>
</form>
[% IF UserInGroup('editbugs') %]
<h3><a href="query.cgi?format=create-series">New Data Set</a></h3>
[% END %]
[% PROCESS global/footer.html.tmpl %]
template/en/default/reports/edit-series.html.tmpl
0 → 100644
View file @
4f6b75a6
<!-- 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 <gerv@gerv.net>
#%]
[% title = "Edit Series" %]
[% h2 = BLOCK %]
[% default.category FILTER html %] /
[%+ default.subcategory FILTER html %] /
[%+ default.name FILTER html %]
[% END %]
[% PROCESS global/header.html.tmpl %]
<form method="get" action="chart.cgi" name="chartform">
[% button_name = "Change" %]
[% PROCESS reports/series.html.tmpl %]
[% IF default.series_id %]
<input type="hidden" name="series_id" value="[% default.series_id %]">
[% END %]
</form>
<p>
<b>Creator</b>: <a href="mailto:[% creator.email FILTER html %]">
[% creator.email FILTER html %]</a>
</p>
<p>
<a href="query.cgi?[% default.query FILTER html%]">View
series search parameters</a> |
<a href="buglist.cgi?cmdtype=dorem&namedcmd=
[% default.category FILTER html %]-
[% default.subcategory FILTER html %]-
[% default.name FILTER html %]&remaction=runseries&series_id=
[% default.series_id %]">Run series search</a>
</p>
[% PROCESS global/footer.html.tmpl %]
template/en/default/reports/menu.html.tmpl
View file @
4f6b75a6
...
...
@@ -58,10 +58,14 @@
<ul>
<li>
<strong><a href="reports.cgi">Charts</a></strong> -
<strong><a href="reports.cgi">
Old
Charts</a></strong> -
plot the status and/or resolution of bugs against
time, for each product in your database.
</li>
<li>
<strong><a href="chart.cgi">New Charts</a></strong> -
plot any arbitrary search against time. Far more powerful.
</li>
</ul>
[% PROCESS global/footer.html.tmpl %]
template/en/default/reports/series-common.html.tmpl
0 → 100644
View file @
4f6b75a6
<!-- 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 <gerv@gerv.net>
#%]
[%# INTERFACE:
# donames: boolean. True if we have a multi-select for names as well as
# categories and subcategories.
# category: hash (keyed by category) of hashes (keyed by subcategory) of
# hashes (keyed by name), with value being the series_id of the
# series. Contains details of all series the user can see.
#%]
[% subcategory = category.${default.category} %]
[% name = subcategory.${default.subcategory} %]
<script>
[%# This structure holds details of the series the user can select from. %]
var series = {
[% FOREACH c = category.keys.sort %]
"[%+ c FILTER js %]" : {
[% FOREACH s = category.$c.keys.sort %]
"[%+ s FILTER js %]" : {
[% IF donames %]
[% FOREACH n = category.$c.$s.keys.sort %]
"[% n FILTER js %]":
[% category.$c.$s.$n FILTER js %][% ", " UNLESS loop.last %]
[% END %]
[% END %]
}[% ", " UNLESS loop.last %]
[% END %]
}[% ", " UNLESS loop.last %]
[% END %]
};
[%# Should attempt to preserve selection across invocations @@@ %]
[%# This function takes necessary action on selection of a category %]
function catSelected() {
var cat = document.chartform.category.value;
var subcats = series[cat];
var subcatwidget = document.chartform.subcategory;
subcatwidget.options.length = 0;
var i = 0;
for (x in subcats) {
subcatwidget.options[i] = new Option(x, x);
i++;
}
[% IF newtext %]
subcatwidget.options[i] = new Option("[% newtext FILTER js %]", "");
[% END %]
subcatwidget.options[0].selected = true;
if (document.chartform.action[1]) {
[%# On the query form, select the right radio button. %]
document.chartform.action[1].checked = true;
}
checkNewState();
}
[%# This function updates the disabled state of the two "new" textboxes %]
function checkNewState() {
var fm = document.chartform;
if (fm.newcategory) {
fm.newcategory.disabled =
(fm.category.value != "" ||
fm.action[1] && fm.action[1].checked == false);
fm.newsubcategory.disabled =
(fm.subcategory.value != "" ||
fm.action[1] && fm.action[1].checked == false);
}
}
</script>
[%###########################################################################%]
[%# Block for SELECT fields - pinched from search/form.html.tmpl #%]
[%###########################################################################%]
[% BLOCK series_select %]
<td align="left">
<label for="[% sel.name %]" accesskey="[% sel.accesskey %]">
<select name="[% sel.name %]" id="[% sel.name %]"
size="[% sel.size %]" style="width: 15em"
[%+ "onchange='$sel.onchange'" IF sel.onchange %]>
[% FOREACH x = ${sel.name}.keys.sort %]
<option value="[% x FILTER html %]"
[% " selected" IF default.${sel.name} == x %]>
[% x FILTER html %]</option>
[% END %]
[% IF newtext %]
<option value="">[% newtext FILTER html %]</option>
[% END %]
</select>
</label>
</td>
[% END %]
template/en/default/reports/series.html.tmpl
0 → 100644
View file @
4f6b75a6
<!-- 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 <gerv@gerv.net>
#%]
[%# INTERFACE:
# default: hash. Defaults for category, subcategory, name etc.
# button_name: string. What the button will say.
# category: hash (keyed by category) of hashes (keyed by subcategory) of
# hashes (keyed by name), with value being the series_id of the
# series. Contains details of all series the user can see.
#%]
[% PROCESS "reports/series-common.html.tmpl"
newtext = "New (name below)"
%]
<table cellpadding="2" cellspacing="2" border="0"
style="text-align: left; margin-left: 20px">
<tbody>
<tr>
<th>Category:</th>
<noscript><th></th></noscript>
<th>Sub-category:</th>
<th>Name:</th>
<td></td>
</tr>
<tr>
[% PROCESS series_select sel = { name => 'category',
size => 5,
onchange => "catSelected()" } %]
<noscript>
<td>
<input type="submit" name="action-edit" value="Update -->">
</td>
</noscript>
[% PROCESS series_select sel = { name => 'subcategory',
size => 5,
onchange => "checkNewState()" } %]
<td valign="top" name="name">
<input type="text" name="name" maxlength="64"
value="[% default.name.0 FILTER html %]" size="25">
</td>
<td valign="top">
<span style="font-weight: bold;">Run every</span>
<input type="text" size="2" name="frequency"
value="[% (default.frequency.0 OR 7) FILTER html %]">
<span style="font-weight: bold;"> day(s)</span><br>
[% IF UserInGroup('admin') %]
<input type="checkbox" name="public"
[% "checked='checked'" IF default.public.0 %]>
<span style="font-weight: bold;">Visible to all</span>
[% END %]
</td>
</tr>
<tr>
<td>
<input type="text" style="width: 100%" name="newcategory"
maxlength="64" value="[% default.newcategory.0 FILTER html %]">
</td>
<noscript><td></td></noscript>
<td>
<input type="text" style="width: 100%" name="newsubcategory"
maxlength="64"
value="[% default.newsubcategory.0 FILTER html %]">
</td>
<td></td>
<td>
<input type="submit" value="[% button_name FILTER html %]">
</td>
</tbody>
</table>
<script>
checkNewState();
</script>
template/en/default/search/search-create-series.html.tmpl
0 → 100644
View file @
4f6b75a6
<!-- 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 <gerv@gerv.net>
#%]
[%# INTERFACE:
# This template has no interface. However, to use it, you need to fulfill
# the interfaces of search/form.html.tmpl, reports/series.html.tmpl and
# search/boolean-charts.html.tmpl.
#%]
[% PROCESS global/header.html.tmpl
title = "Create New Data Set"
onload = "selectProduct(document.forms['chartform']);"
%]
[% button_name = "I'm Feeling Buggy" %]
<form method="get" action="chart.cgi" name="chartform">
[% PROCESS search/form.html.tmpl %]
<table>
<tr>
<td>
<input type="radio" id="action-search"
name="action" value="search" checked="checked">
<label for="action-search">Run this search</label></td>
</tr>
<tr>
<td>
<input type="radio" id="action-create" name="action" value="create">
<label for="action-create">
Start recording bug count data for this search, as follows:
</label>
<br>
[% INCLUDE reports/series.html.tmpl %]
</td>
</tr>
</table>
<hr>
[% PROCESS "search/boolean-charts.html.tmpl" %]
</form>
[% PROCESS global/footer.html.tmpl %]
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment