]> xenbits.xensource.com Git - people/aperard/emesinae.git/commitdiff
Support tagging bugs, use to implement affected version tracking
authorIan Campbell <ian.campbell@citrix.com>
Thu, 6 Jun 2013 15:09:59 +0000 (16:09 +0100)
committerIan Campbell <ian.campbell@citrix.com>
Thu, 6 Jun 2013 15:26:39 +0000 (16:26 +0100)
An existing DB can be upgraded with:
8<-------------------------------------------------
create table tags (
       tag_id integer not null primary key autoincrement,
       namespace varchar, -- "component", "affects" etc
       name varchar,
       -- the following refer to the default visibility and setting in the search page.
       active bool default true, -- searched for by default
       advanced bool default false -- only shown in advanced search pane
);

create table bug2tag (
       bug_id integer not null references bugs ( bug_id ),
       tag_id integer not null references tags ( tag_id ),
       primary key ( bug_id, tag_id )
);
8<-------------------------------------------------

Followed by populating the tags table with a suitable set of names in the
"affects" namespace.

14 files changed:
CGI/bug.pl
CGI/bugs.pl
Emesinae/Bug.pm
Emesinae/Common.pm
README
TODO
config/examples/test/control.14.test [new file with mode: 0644]
config/examples/test/control.15.test [new file with mode: 0644]
config/examples/test/dbinit.sql [new file with mode: 0644]
config/examples/test/test.sh
config/examples/xen-bugs.xenproject.org/dbinit.sql [new file with mode: 0644]
control.txt
db/createdb.sql
scripts/control.pl

index 3e812aa9b0f0ed2c77e46b0263b6b7a261d7991a..426cb8609765bd01e556b8b890121e130f1b04d1 100755 (executable)
@@ -69,6 +69,7 @@ print div(
             "Date: " . htmlsanit($creationdate),
             "Last Update: " . htmlsanit($lastchangedate),
             "Severity: " . htmlsanit( $bug->{severity} ),
+            "Affects: " . htmlsanit(join(", ", $bug->tags("affects") ) ),
             "State: " . b( $bug->{open} ? "Open" : "Closed" )
         ]
     )
index f1dede4719be8af248a5934e19631c1eec67cddc..158e5617627927bda68bc67e338f0a211ce5e5dc 100755 (executable)
@@ -58,9 +58,37 @@ sub buglist ($$) {
 
 print header;
 
+my $jscript = <<'EOS';
+function showOldVersions() {
+    $( 'input.old_version[type=checkbox]' ).parents('td').show();
+    $( '#showOldVersions' ).hide();
+}
+
+$(document).ready(function() {
+    var showOld = false;
+    $( 'input.old_version[type=checkbox]' ).each(function() {
+        if ( $(this).is(':checked') ) { showOld = true; }
+    })
+
+    if ( showOld ) {
+        showOldVersions();
+    } else {
+        $( 'input.old_version[type=checkbox]' ).parents('td').hide();
+    }
+})
+EOS
+
 print start_html(
     -title => $c{TrackerName} . ": $ml - All Bugs",
-    -style => { 'src' => $c{StyleSheet} }
+    -style => { 'src' => $c{StyleSheet} },
+    -script => [
+                { -type => 'text/javascript',
+                  -src  => '/javascript/jquery/jquery.js',
+                },
+                { -code => $jscript,
+                },
+               ]
+        
 ) . "\n";
 
 print h1( $c{TrackerName} );
@@ -89,13 +117,19 @@ print p(
     $dbh,
     State      => param('state'),
     Severities => [ param('severityfilter') ],
+    Affects    => [ param('affectsfilter') ],
 );
 
 foreach my $open ( 1, 0 ) {
     foreach my $sev ( @{ $c{SeverityLevels} } ) { buglist( $open, $sev ); }
 }
 
-print ul (@index) if @index;
+if ( param('affectsfilter') ) {
+    print h2("Bugs affecting: " . join(", ",@{[param('affectsfilter')]}));
+} else {
+    print h2("Bugs affecting: any version");
+}
+print p(ul (@index)) if @index;
 
 if ( !$lists ) {
     $lists = h1("No bugs match the criteria.");
@@ -104,6 +138,23 @@ print hr(), "\n", $lists, hr(), "\n";
 
 print h2("Search");
 
+sub affects_checkboxes() {
+    my @affects = list_tags($dbh, "affects");
+    my (@values, @defaults, %attributes);
+
+    foreach my $vers (@affects) {
+        push @values, $vers->{name};
+        push @defaults, $vers->{name} if $vers->{active} eq "true";
+        $attributes{$vers->{name}} = {};
+
+        $attributes{$vers->{name}}{'class'} = 'old_version' if $vers->{advanced} eq "true"
+    }
+    return checkbox_group(-name => 'affectsfilter',
+                          -values => \@values,
+                          -default => \@defaults,
+                          -attributes => \%attributes);
+}
+
 print
   start_form( -method => "POST" ),
   table(
@@ -119,15 +170,17 @@ print
                 ]
             ),
             td( [ "Title contains:", i("TBD") ] ), #textfield('titlefilter') ] ),
+            td( [ "Only Those Affecting:",
+                  affects_checkboxes(),
+                  small(a({-href=>"javascript:showOldVersions();", -id=>"showOldVersions"},"Show Older Versions"))
+                ]
+            ),
             td(
                 [
                     "Severities:",
-                    join(
-                        "",
-                        checkbox_group(
-                            -name   => 'severityfilter',
-                            -values => $c{SeverityLevels}
-                        )
+                    checkbox_group(
+                        -name   => 'severityfilter',
+                        -values => $c{SeverityLevels}
                     )
                 ]
             ),
index 9079e804225dc69636c4b6a78ecb74500f79b4e7..49e14cb52e1b02466b32fa5adac4a82729301f1d 100644 (file)
@@ -6,7 +6,7 @@ use strict;
 use Emesinae::Message;
 use Emesinae::Common;
 
-my $SELECT_FIELDS = "bug_id,title_raw,creationdate,lastchangedate,owner_raw,severity,open";
+my $SELECT_FIELDS = "bugs.bug_id,title_raw,creationdate,lastchangedate,owner_raw,severity,open";
 
 sub new {
     my $class = shift;
@@ -122,6 +122,71 @@ sub set_severity {
     $self->_set("severity", $sev);
 }
 
+sub _lookup_tag($$$) {
+    my ($self,$ns,$tag) = @_;
+
+    my $sth = $self->{dbh}->prepare( "
+        SELECT tag_id
+          FROM tags
+         WHERE namespace = ?
+           AND name = ?
+    " );
+    $sth->execute ( $ns, $tag ) or die "Unable to find tag $ns:$tag";
+    my $row = $sth->fetchrow_arrayref;
+    $row or die "Unable to find tag $ns:$tag";
+    $sth->finish;
+
+    return $row->[0];
+}
+
+sub set_tag($$$) {
+    my ($self,$ns,$tag) = @_;
+
+    my $tag_id = $self->_lookup_tag($ns,$tag);
+
+    my $sth = $self->{dbh}->prepare( "
+        INSERT OR REPLACE INTO bug2tag(bug_id,tag_id)
+                  VALUES (?,?)
+    " );
+    $sth->execute( $self->{id}, $tag_id ) or die "Setting tag $ns:$tag on Bug #" . $self->{id};
+    $sth->rows == 1 or die "Failed to set tag $ns:$tag on Bug #" . $self->{id};
+
+    $sth->finish;
+}
+
+sub clear_tag($$$) {
+    my ($self,$ns,$tag) = @_;
+
+    my $tag_id = $self->_lookup_tag($ns,$tag);
+
+    my $sth = $self->{dbh}->prepare( "
+        DELETE FROM bug2tag
+              WHERE bug_id = ?
+                AND tag_id = ?
+    " );
+    $sth->execute( $self->{id}, $tag_id ) or die "Clearing tag $ns:$tag on Bug #" . $self->{id};
+
+    $sth->finish;
+}
+
+sub tags($$) {
+    my ($self,$ns) = @_;
+
+    my $sth = $self->{dbh}->prepare( "
+        SELECT tags.name
+          FROM tags,bug2tag
+         WHERE tags.tag_id == bug2tag.tag_id
+           AND tags.namespace == ?
+           AND bug2tag.bug_id == ?
+    ");
+    $sth->execute( $ns, $self->{id} ) or die "Listing $ns tags on Bug #" . $self->{id};
+
+    # An array ref (rows) of array refs (columns)
+    my $tref = $sth->fetchall_arrayref();
+    $sth->finish;
+    return map { $_->[0] } @{$tref};
+}
+
 sub set_status {
     my $self = shift;
     my $open = shift;
@@ -210,23 +275,38 @@ sub listall ($$;%) {
     my $dbh   = shift;
     my %args  = @_;
 
-    my @where;
+    my (@args,@where);
+
+    my $select = "SELECT DISTINCT $SELECT_FIELDS FROM bugs";
 
     if    ( $args{State} eq "Open" )   { push @where, qq(open = "true" ) }
     elsif ( $args{State} eq "Closed" ) { push @where, qq(open = "false" ) }
 
-    if ( $args{Severities} ) {
+    if ( @{$args{Severities}} ) {
+        #print "<p>SEVERITIES: ".@{$args{Severities}}." ".join(" ",@{$args{Severities}})."</p>";
         map { assert_valid_severity($_) } @ { $args{Severities} };
         push @where,
-          join( " OR ", map { qq{severity = "$_"} } @{ $args{Severities} } );
+          join( " OR ", map { push @args,$_; qq{severity = ?} } @{ $args{Severities} } );
+    }
+
+    if ( @{$args{Affects}} ) {
+        #print "<p>AFFECTS: ".@{$args{Affects}}." ".join(" ",@{$args{Affects}})."</p>";
+        $select .= " JOIN bug2tag ON bug2tag.bug_id == bugs.bug_id ";
+        $select .= " JOIN tags ON bug2tag.tag_id == tags.tag_id ";
+
+        push @where, "tags.namespace = \"affects\"";
+        push @where,
+          join( " OR ", map { push @args,$_; qq{tags.name = ?} } @{ $args{Affects} });
     }
-    my $select = "SELECT $SELECT_FIELDS FROM bugs ";
-    $select .= "WHERE " . join( " AND ", map { qq{($_)} } @where ) if @where;
-    $select .= " ORDER BY bug_id";
 
-    #print "<p>$select</p>";
+    $select .= " WHERE " . join( " AND ", map { qq{($_)} } @where ) if @where;
+    $select .= " ORDER BY bugs.bug_id";
+
+    #print "<p>QUERY: $select</p>";
+    #print "<p>ARGS:".join(",", @args)."</p>";
+
     my $sth = $dbh->prepare($select);
-    $sth->execute();
+    $sth->execute(@args);
     my @r;
     while ( my @row = $sth->fetchrow_array ) {
         push @r, $class->new( $dbh, Fields => \@row );
index 97fbd913b7cc03384dfec32a14a8480951d00593..9763cb85c6f2d3cd5b0a01452069fa939d0d1c1e 100644 (file)
@@ -12,6 +12,7 @@ BEGIN {
       %c readconfig
       opendb
       listbugs
+      list_tags
       subsyslock subsysunlock
     );
     %EXPORT_TAGS = ();
@@ -54,6 +55,23 @@ sub opendb () {
     return $dbh;
 }
 
+sub list_tags($$) {
+    my ($dbh,$ns) = @_;
+
+    my $sth = $dbh->prepare( "
+        SELECT name,active,advanced
+          FROM tags
+         WHERE namespace = ?
+    " );
+    $sth->execute( $ns ) or die "Unable to obtain list of tags in namesapce $ns";
+
+    # Gets an array ref of hash refs.
+    my $tags = $sth->fetchall_arrayref({});
+
+    $sth->finish;
+    return @{$tags}
+}
+
 #------- locking -------
 
 sub subsyslock ($) {
diff --git a/README b/README
index 5a0286ed40482faa23e04714b9758364450c6fe7..67a464082bd19cf0d5151bd57c9ccb730cabd629 100644 (file)
--- a/README
+++ b/README
@@ -5,6 +5,8 @@ Dependencies:
   libdbd-sqlite3-perl
   liburi-perl
 
+  libjs-jquery
+
   sqlite3
 
   MTA (currently only exim)
diff --git a/TODO b/TODO
index 8a8be7404e2ce6da17ab3684cba2c2233cd28110..16bdd355ef8b9bfcb7d139c419cbc78f3185e92b 100644 (file)
--- a/TODO
+++ b/TODO
@@ -10,8 +10,6 @@ control.pl:
 
 bugs:
  - components
- - affected branches
- - tags
 
 bug.pl:
  - offer link to followup to a bug, but not reply to a specific message.
diff --git a/config/examples/test/control.14.test b/config/examples/test/control.14.test
new file mode 100644 (file)
index 0000000..33bec90
--- /dev/null
@@ -0,0 +1,17 @@
+From: Ian Campbell <IJC@heLlion.org.uk>
+Subject: Bug affects master and 4.2
+Message-id: <control.14.test@emesinae.example.com>
+To: emesinae-test@list.example.com
+Cc: ijc@hellion.org.uk, test-control@bugs.xenproject.org
+
+create !
+affects it master
+# A second time for good measure
+affects it +master
+affects it stable-4.2
+# Add and remove a tag
+affects it +stable-4.1
+affects it -stable-4.1
+# Remove a non-present tag
+affects it -stable-4.0
+thanks
diff --git a/config/examples/test/control.15.test b/config/examples/test/control.15.test
new file mode 100644 (file)
index 0000000..2a3eccc
--- /dev/null
@@ -0,0 +1,9 @@
+From: Ian Campbell <IJC@heLlion.org.uk>
+Subject: Bug affects unknown branch
+Message-id: <control.15.test@emesinae.example.com>
+To: emesinae-test@list.example.com
+Cc: ijc@hellion.org.uk, test-control@bugs.xenproject.org
+
+create !
+affects it foo
+thanks
diff --git a/config/examples/test/dbinit.sql b/config/examples/test/dbinit.sql
new file mode 100644 (file)
index 0000000..f8a645a
--- /dev/null
@@ -0,0 +1,5 @@
+INSERT INTO tags (namespace,name,active,advanced) VALUES ("affects","master","true","false");
+INSERT INTO tags (namespace,name,active,advanced) VALUES ("affects","stable-4.2","true","false");
+INSERT INTO tags (namespace,name,active,advanced) VALUES ("affects","stable-4.1","false","true");
+INSERT INTO tags (namespace,name,active,advanced) VALUES ("affects","stable-4.0","false","true");
+
index 3bb6656fba9c5abbfeaf91fa87c733d3915a2518..bc5965b72dc2491016dd948a327718b0e91a5db4 100755 (executable)
@@ -39,6 +39,7 @@ if [ "${mode}" = "corpus" -o "${mode}" = "both" ] ; then
 
     sudo ../../../db/createdb.sh -d $db
     sudo chown $user:$user $db
+    sudo sqlite3 $db < ../../../config/examples/test/dbinit.sql
 
     echo Inserting corpus
     asuser ./insertcorpus.sh
diff --git a/config/examples/xen-bugs.xenproject.org/dbinit.sql b/config/examples/xen-bugs.xenproject.org/dbinit.sql
new file mode 100644 (file)
index 0000000..a5da464
--- /dev/null
@@ -0,0 +1,5 @@
+INSERT INTO tags (namespace,name,active,advanced) VALUES ("affects","master","true","false");
+INSERT INTO tags (namespace,name,active,advanced) VALUES ("affects","4.2","true","false");
+INSERT INTO tags (namespace,name,active,advanced) VALUES ("affects","4.1","false","true");
+INSERT INTO tags (namespace,name,active,advanced) VALUES ("affects","4.0","false","true");
+
index 164da276ee45ec37d9a61afe3a76db72bfcaa4b1..9d7bcc9eda19c68a0cedc413bf585da7fa1015dd 100644 (file)
@@ -46,6 +46,11 @@ Certain commands are available only to maintainers. These are tagged
 Available Commands
 ==================
 
+affects <BUG> [+-]<VERSION> (, [+-]<VERSION>)?
+
+       Marks <BUG> as affecting one or more given <VERSIONS> or removes the
+       mark.
+
 create <MSGID>
 
        [MAINTONLY] Create a new bug, rooted at <MSGID>. By default the title
index f3a25b6b39efd2b6413ed4eee9f2ba7870a1c211..dc33547ffe0da2adec28e9e86923c8d324334cd5 100644 (file)
@@ -23,17 +23,14 @@ create table refs (
        primary key ( parent_id, child_id )
 );
 
---create table components {
---       id integer primary key autoincrement,
---       name varchar,
---}
---insert into components (name) VALUES "hypervisor";
---insert into components (name) VALUES "tools/libxc";
---insert into components (name) VALUES "tools/libxl";
---insert into components (name) VALUES "tools/qemu-xen";
---insert into components (name) VALUES "tools/qemu-xen-traditional";
---insert into components (name) VALUES "tools/misc";
---insert into components (name) VALUES "kernel/linux";
+create table tags (
+       tag_id integer not null primary key autoincrement,
+       namespace varchar, -- "component", "affects" etc
+       name varchar,
+       -- the following refer to the default visibility and setting in the search page.
+       active bool default true, -- searched for by default
+       advanced bool default false -- only shown in advanced search pane
+);
 
 create table bugs ( 
        bug_id integer primary key autoincrement,
@@ -54,3 +51,9 @@ create table bug2message (
        bug_id integer not null references bugs ( bug_id ),
        message_id integer not null references messages ( message_id )
 );
+
+create table bug2tag (
+       bug_id integer not null references bugs ( bug_id ),
+       tag_id integer not null references tags ( tag_id ),
+       primary key ( bug_id, tag_id )
+);
index 41f6cd96de9852e974b8cdebd0b0b0aa527e5f2f..d9d37a7516bb3801da5bfe302db7f877ea6c1608 100755 (executable)
@@ -293,6 +293,19 @@ sub cmd_severity ($) {
     return 1;
 }
 
+sub cmd_affects ($) {
+    $_ = shift;
+    m/^(${MATCH_BUGID})\s+([\+-]?)(.*)/ or die "Cannot parse arguments";
+    my $b = lookup_bugid $1;
+    if ( $2 eq "+" or $2 eq "" ) {
+        push @reply, "Bug #" . $b->{id} . " affects `$3'";
+        $b->set_tag( "affects", $3 );
+    } else {
+        push @reply, "Bug #" . $b->{id} . " does not affect `$3'";
+        $b->clear_tag( "affects", $3 );
+    }
+}
+
 sub cmd_prune ($) {
     $_ = shift;
     m/^($MATCH_BUGID)\s(.*)/ or die "Cannot parse arguments";
@@ -357,6 +370,7 @@ our %cmds = (
     title    => { Priv => 1, Cmd => \&cmd_title, },
     owner    => { Priv => 1, Cmd => \&cmd_owner, },
     severity => { Priv => 1, Cmd => \&cmd_severity, },
+    affects  => { Priv => 0, Cmd => \&cmd_affects, },
     prune    => { Priv => 0, Cmd => \&cmd_prune, },
     graft    => { Priv => 0, Cmd => \&cmd_graft, },
     thanks   => { Priv => 0, Cmd => \&cmd_quit, },