From a48e37063734dd5748433a0dd8a9c8e2b32e41c0 Mon Sep 17 00:00:00 2001 From: "dmose%mozilla.org" <> Date: Wed, 29 Mar 2000 05:30:23 +0000 Subject: allow users to watch the bugs of other users --- Bugzilla/RelationSet.pm | 211 ++++++++++++++++++++++++++++++++++++++ README | 4 +- RelationSet.pm | 211 ++++++++++++++++++++++++++++++++++++++ bug_form.pl | 10 +- checksetup.pl | 85 +++++++++++++++- defparams.pl | 6 +- globals.pl | 27 ++--- process_bug.cgi | 67 ++++++------ processmail | 266 +++++++++++++++++++++++++----------------------- userprefs.cgi | 67 ++++++++++++ 10 files changed, 763 insertions(+), 191 deletions(-) create mode 100644 Bugzilla/RelationSet.pm create mode 100644 RelationSet.pm diff --git a/Bugzilla/RelationSet.pm b/Bugzilla/RelationSet.pm new file mode 100644 index 000000000..ee402e7a4 --- /dev/null +++ b/Bugzilla/RelationSet.pm @@ -0,0 +1,211 @@ +# +# 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) 2000 Netscape Communications Corporation. All +# Rights Reserved. +# +# Contributor(s): Dan Mosedale +# Terry Weissman + +# This object models a set of relations between one item and a group +# of other items. An example is the set of relations between one bug +# and the users CCed on that bug. Currently, the relation objects are +# expected to be bugzilla userids. However, this could and perhaps +# should be generalized to work with non userid objects, such as +# keywords associated with a bug. That shouldn't be hard to do; it +# might involve turning this into a virtual base class, and having +# UserSet and KeywordSet types that inherit from it. + +use diagnostics; +use strict; + +require "globals.pl"; + +package RelationSet; +use CGI::Carp qw(fatalsToBrowser); + +# create a new empty RelationSet +# +sub new { + my $type = shift(); + + # create a ref to an empty hash and bless it + # + my $self = {}; + bless $self, $type; + + # construct from a comma-delimited string + # + if ($#_ == 0) { + $self->mergeFromString($_[0]); + } + # unless this was a constructor for an empty list, somebody screwed up. + # + elsif ( $#_ != -1 ) { + confess("invalid number of arguments"); + } + + # bless as a RelationSet + # + return $self; +} + +# Assumes that the set of relations "FROM $table WHERE $constantSql and +# $column = $value" is currently represented by $self, and this set should +# be updated to look like $other. +# +# Returns an array of two strings, one INSERT and one DELETE, which will +# make this change. Either or both strings may be the empty string, +# meaning that no INSERT or DELETE or both (respectively) need to be done. +# +# THE CALLER IS RESPONSIBLE FOR ANY DESIRED LOCKING AND/OR CONSISTENCY +# CHECKS (not to mention doing the SendSQL() calls). +# +sub generateSqlDeltas { + ($#_ == 5) || confess("invalid number of arguments"); + my ( $self, # instance ptr to set representing the existing state + $endState, # instance ptr to set representing the desired state + $table, # table where these relations are kept + $invariantName, # column held const for a RelationSet (often "bug_id") + $invariantValue, # what to hold the above column constant at + $columnName # the column which varies (often a userid) + ) = @_; + + # construct the insert list by finding relations which exist in the + # end state but not the current state. + # + my @endStateRelations = keys(%$endState); + my @insertList = (); + foreach ( @endStateRelations ) { + push ( @insertList, $_ ) if ( ! exists $$self{"$_"} ); + } + + # we've built the list. If it's non-null, add required sql chrome. + # + my $sqlInsert=""; + if ( $#insertList > -1 ) { + $sqlInsert = "INSERT INTO $table ($invariantName, $columnName) VALUES " . + join (",", + map ( "($invariantValue, $_)" , @insertList ) + ); + } + + # construct the delete list by seeing which relations exist in the + # current state but not the end state + # + my @selfRelations = keys(%$self); + my @deleteList = (); + foreach ( @selfRelations ) { + push (@deleteList, $_) if ( ! exists $$endState{"$_"} ); + } + + # we've built the list. if it's non-empty, add required sql chrome. + # + my $sqlDelete = ""; + if ( $#deleteList > -1 ) { + $sqlDelete = "DELETE FROM $table WHERE $invariantName = $invariantValue " . + "AND $columnName IN ( " . join (",", @deleteList) . " )"; + } + + return ($sqlInsert, $sqlDelete); +} + +# compare the current object with another. +# +sub isEqual { + ($#_ == 1) || confess("invalid number of arguments"); + my $self = shift(); + my $other = shift(); + + # get arrays of the keys for faster processing + # + my @selfRelations = keys(%$self); + my @otherRelations = keys(%$other); + + # make sure the arrays are the same size + # + return 0 if ( $#selfRelations != $#otherRelations ); + + # bail out if any of the elements are different + # + foreach my $relation ( @selfRelations ) { + return 0 if ( !exists $$other{$relation}) + } + + # we made it! + # + return 1; + +} + +# merge the results of a SQL command into this set +# +sub mergeFromDB { + ( $#_ == 1 ) || confess("invalid number of arguments"); + my $self = shift(); + + &::SendSQL(shift()); + while (my @row = &::FetchSQLData()) { + $$self{$row[0]} = 1; + } + + return; +} + +# merge a set in string form into this set +# +sub mergeFromString { + ($#_ == 1) || confess("invalid number of arguments"); + my $self = shift(); + + # do the merge + # + foreach my $person (split(/[ ,]/, shift())) { + if ($person ne "") { + $$self{&::DBNameToIdAndCheck($person)} = 1; + } + } +} + +# return the number of elements in this set +# +sub size { + my $self = shift(); + + my @k = keys(%$self); + return $#k++; +} + +# return this set in array form +# +sub toArray { + my $self= shift(); + + return keys(%$self); +} + +# return this set in string form (comma-separated and sorted) +# +sub toString { + ($#_ == 0) || confess("invalid number of arguments"); + my $self = shift(); + + my @result = (); + foreach my $i ( keys %$self ) { + push @result, &::DBID_to_name($i); + } + + return join(',', sort(@result)); +} diff --git a/README b/README index 65dfd0d77..017da0686 100644 --- a/README +++ b/README @@ -26,7 +26,7 @@ other necessary ingredient is a web server set up to run cgi scripts. The software packages necessary for the proper running of bugzilla are: - 1. MySQL database server and the mysql client + 1. MySQL database server and the mysql client (3.22.5 or greater) 2. Perl (5.004 or greater) 3. DBI Perl module 4. Data::Dumper Perl module @@ -39,7 +39,7 @@ other necessary ingredient is a web server set up to run cgi scripts. Bugzilla has quite a few prerequisites, but none of them are TCL. Previous versions required TCL, but it no longer needed (or used). -1.1. Getting and setting up MySQL database +1.1. Getting and setting up MySQL database (3.22.5 or greater) Visit MySQL homepage at http://www.mysql.org and grab the latest stable release of the server. Both binaries and source are available and which diff --git a/RelationSet.pm b/RelationSet.pm new file mode 100644 index 000000000..ee402e7a4 --- /dev/null +++ b/RelationSet.pm @@ -0,0 +1,211 @@ +# +# 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) 2000 Netscape Communications Corporation. All +# Rights Reserved. +# +# Contributor(s): Dan Mosedale +# Terry Weissman + +# This object models a set of relations between one item and a group +# of other items. An example is the set of relations between one bug +# and the users CCed on that bug. Currently, the relation objects are +# expected to be bugzilla userids. However, this could and perhaps +# should be generalized to work with non userid objects, such as +# keywords associated with a bug. That shouldn't be hard to do; it +# might involve turning this into a virtual base class, and having +# UserSet and KeywordSet types that inherit from it. + +use diagnostics; +use strict; + +require "globals.pl"; + +package RelationSet; +use CGI::Carp qw(fatalsToBrowser); + +# create a new empty RelationSet +# +sub new { + my $type = shift(); + + # create a ref to an empty hash and bless it + # + my $self = {}; + bless $self, $type; + + # construct from a comma-delimited string + # + if ($#_ == 0) { + $self->mergeFromString($_[0]); + } + # unless this was a constructor for an empty list, somebody screwed up. + # + elsif ( $#_ != -1 ) { + confess("invalid number of arguments"); + } + + # bless as a RelationSet + # + return $self; +} + +# Assumes that the set of relations "FROM $table WHERE $constantSql and +# $column = $value" is currently represented by $self, and this set should +# be updated to look like $other. +# +# Returns an array of two strings, one INSERT and one DELETE, which will +# make this change. Either or both strings may be the empty string, +# meaning that no INSERT or DELETE or both (respectively) need to be done. +# +# THE CALLER IS RESPONSIBLE FOR ANY DESIRED LOCKING AND/OR CONSISTENCY +# CHECKS (not to mention doing the SendSQL() calls). +# +sub generateSqlDeltas { + ($#_ == 5) || confess("invalid number of arguments"); + my ( $self, # instance ptr to set representing the existing state + $endState, # instance ptr to set representing the desired state + $table, # table where these relations are kept + $invariantName, # column held const for a RelationSet (often "bug_id") + $invariantValue, # what to hold the above column constant at + $columnName # the column which varies (often a userid) + ) = @_; + + # construct the insert list by finding relations which exist in the + # end state but not the current state. + # + my @endStateRelations = keys(%$endState); + my @insertList = (); + foreach ( @endStateRelations ) { + push ( @insertList, $_ ) if ( ! exists $$self{"$_"} ); + } + + # we've built the list. If it's non-null, add required sql chrome. + # + my $sqlInsert=""; + if ( $#insertList > -1 ) { + $sqlInsert = "INSERT INTO $table ($invariantName, $columnName) VALUES " . + join (",", + map ( "($invariantValue, $_)" , @insertList ) + ); + } + + # construct the delete list by seeing which relations exist in the + # current state but not the end state + # + my @selfRelations = keys(%$self); + my @deleteList = (); + foreach ( @selfRelations ) { + push (@deleteList, $_) if ( ! exists $$endState{"$_"} ); + } + + # we've built the list. if it's non-empty, add required sql chrome. + # + my $sqlDelete = ""; + if ( $#deleteList > -1 ) { + $sqlDelete = "DELETE FROM $table WHERE $invariantName = $invariantValue " . + "AND $columnName IN ( " . join (",", @deleteList) . " )"; + } + + return ($sqlInsert, $sqlDelete); +} + +# compare the current object with another. +# +sub isEqual { + ($#_ == 1) || confess("invalid number of arguments"); + my $self = shift(); + my $other = shift(); + + # get arrays of the keys for faster processing + # + my @selfRelations = keys(%$self); + my @otherRelations = keys(%$other); + + # make sure the arrays are the same size + # + return 0 if ( $#selfRelations != $#otherRelations ); + + # bail out if any of the elements are different + # + foreach my $relation ( @selfRelations ) { + return 0 if ( !exists $$other{$relation}) + } + + # we made it! + # + return 1; + +} + +# merge the results of a SQL command into this set +# +sub mergeFromDB { + ( $#_ == 1 ) || confess("invalid number of arguments"); + my $self = shift(); + + &::SendSQL(shift()); + while (my @row = &::FetchSQLData()) { + $$self{$row[0]} = 1; + } + + return; +} + +# merge a set in string form into this set +# +sub mergeFromString { + ($#_ == 1) || confess("invalid number of arguments"); + my $self = shift(); + + # do the merge + # + foreach my $person (split(/[ ,]/, shift())) { + if ($person ne "") { + $$self{&::DBNameToIdAndCheck($person)} = 1; + } + } +} + +# return the number of elements in this set +# +sub size { + my $self = shift(); + + my @k = keys(%$self); + return $#k++; +} + +# return this set in array form +# +sub toArray { + my $self= shift(); + + return keys(%$self); +} + +# return this set in string form (comma-separated and sorted) +# +sub toString { + ($#_ == 0) || confess("invalid number of arguments"); + my $self = shift(); + + my @result = (); + foreach my $i ( keys %$self ) { + push @result, &::DBID_to_name($i); + } + + return join(',', sort(@result)); +} diff --git a/bug_form.pl b/bug_form.pl index 4453d7ff1..9b459d66c 100644 --- a/bug_form.pl +++ b/bug_form.pl @@ -22,6 +22,8 @@ use diagnostics; use strict; +use RelationSet; + # Shut up misguided -w warnings about "used only once". For some reason, # "use vars" chokes on me when I try it here. @@ -147,8 +149,10 @@ my $sev_popup = make_options(\@::legal_severity, $bug{'bug_severity'}); my $component_popup = make_options($::components{$bug{'product'}}, $bug{'component'}); +my $ccSet = new RelationSet; +$ccSet->mergeFromDB("select who from cc where bug_id=$id"); my $cc_element = ''; + $ccSet->toString() . '">'; my $URL = $bug{'bug_file_loc'}; @@ -208,7 +212,9 @@ if (Param("usetargetmilestone")) { if ($url eq "") { $url = "notargetmilestone.html"; } - + if ($bug{'target_milestone'} eq "") { + $bug{'target_milestone'} = " "; + } print " Target Milestone: New email tech}); } + + if (Param("supportwatchers")) { + my $watcheduserSet = new RelationSet; + $watcheduserSet->mergeFromDB("SELECT watched FROM watch WHERE" . + " watcher=$userid"); + my $watchedusers = $watcheduserSet->toString(); + + print qq{ +
+New! +If you want to help cover for someone when they're on vacation, or if +you need to do the QA related to all of their bugs, you can tell bugzilla +to send mail related to their bugs to you also. List the email addresses +of any users you wish to watch here, separated by commas. +Note that you MUST have the above "New email tech" +button selected in order to use this feature. + +}; + EmitEntry("Users to watch", + qq{}); + + } + } sub SaveDiffs { @@ -144,6 +170,47 @@ sub SaveDiffs { SendSQL("UPDATE profiles " . "SET emailnotification = " . SqlQuote($::FORM{'emailnotification'}) . ", newemailtech = $newemailtech WHERE userid = $userid"); + + # deal with any watchers + # + if (Param("supportwatchers") ) { + + if (exists $::FORM{'watchedusers'}) { + + Error ('You must have "New email tech" set to watch someone') + if ( $::FORM{'watchedusers'} ne "" && $newemailtech == 0); + + # Just in case. Note that this much locking is actually overkill: + # we don't really care if anyone reads the watch table. So + # some small amount of contention could be gotten rid of by + # using user-defined locks rather than table locking. + # + SendSQL("LOCK TABLES watch WRITE, profiles READ"); + + # what the db looks like now + # + my $origWatchedUsers = new RelationSet; + $origWatchedUsers->mergeFromDB("SELECT watched FROM watch WHERE" . + " watcher=$userid"); + + # update the database to look like the form + # + my $newWatchedUsers = new RelationSet($::FORM{'watchedusers'}); + my @CCDELTAS = $origWatchedUsers->generateSqlDeltas( + $newWatchedUsers, + "watch", + "watcher", + $userid, + "watched"); + $CCDELTAS[0] eq "" || SendSQL($CCDELTAS[0]); + $CCDELTAS[1] eq "" || SendSQL($CCDELTAS[1]); + + # all done + # + SendSQL("UNLOCK TABLES"); + + } + } } -- cgit v1.2.3-24-g4f1b