[Codestriker-commits] CVS update: codestriker/template/en/default listtopics.html.tmpl
Brought to you by:
sits
From: <si...@us...> - 2004-12-21 23:03:26
|
Created Codestriker topic at: http://codestriker.sourceforge.net/cgi-bin/codestriker.pl?topic=1098312&action=view User: sits Date: 04/12/21 15:03:00 Modified: . CHANGELOG bin checksetup.pl codestriker.pl.base lib Codestriker.pm lib/Codestriker/Action ListTopics.pm lib/Codestriker/DB DBI.pm lib/Codestriker/Http Template.pm UrlBuilder.pm lib/Codestriker/Model MetricStats.pm Metrics.pm Topic.pm template/en/default listtopics.html.tmpl Added: lib/Codestriker/Action ListTopicsRSS.pm ListTopicsRss.pm Log: * Added RSS support, so that the topic list screen now has an equivalent RSS view. From Jason Remillard. Index: CHANGELOG =================================================================== RCS file: /cvsroot/codestriker/codestriker/CHANGELOG,v retrieving revision 1.157 retrieving revision 1.158 diff -u -r1.157 -r1.158 --- CHANGELOG 21 Dec 2004 22:44:22 -0000 1.157 +++ CHANGELOG 21 Dec 2004 23:02:51 -0000 1.158 @@ -11,6 +11,9 @@ is changed. This is all controlled by the $email_send_options configuration item. From Jason Remillard. +* Added RSS support, so that the topic list screen now has an + equivalent RSS view. From Jason Remillard. + Version 1.8.5 * Complete support for VSS repositories. Topics linked to a VSS Index: checksetup.pl =================================================================== RCS file: /cvsroot/codestriker/codestriker/bin/checksetup.pl,v retrieving revision 1.62 retrieving revision 1.63 diff -u -r1.62 -r1.63 --- checksetup.pl 22 Nov 2004 11:06:52 -0000 1.62 +++ checksetup.pl 21 Dec 2004 23:02:53 -0000 1.63 @@ -74,6 +74,11 @@ { name => 'File::Temp', version => '0' + }, + { + name => 'XML::RSS', + version => '1.05', + optional => 1 } ]; @@ -86,11 +91,20 @@ push @{$modules}, $database->get_module_dependencies(); }; +my %missing_optional = (); my %missing = (); foreach my $module (@{$modules}) { - unless (have_vers($module->{name}, $module->{version})) { + + my $optional = exists($module->{optional}) && $module->{optional}; + + unless (have_vers($module->{name}, $module->{version},$optional)) { + if ( $optional == 0) { $missing{$module->{name}} = $module->{version}; } + else { + $missing_optional{$module->{name}} = $module->{version}; + } + } } # vers_cmp is adapted from Sort::Versions 1.3 1996/07/11 13:37:00 kjahds, @@ -126,7 +140,7 @@ # This was originally clipped from the libnet Makefile.PL, adapted here to # use the above vers_cmp routine for accurate version checking. sub have_vers { - my ($pkg, $wanted) = @_; + my ($pkg, $wanted, $optional) = @_; my ($msg, $vnum, $vstr); no strict 'refs'; printf("Checking for %15s %-9s ", $pkg, !$wanted?'(any)':"(v$wanted)"); @@ -137,7 +151,13 @@ $vnum = -1 if $@; if ($vnum eq "-1") { # string compare just in case it's non-numeric - $vstr = "not found"; + if ( $optional ) { + $vstr = "ok: not found, optional"; + } + else { + $vstr = " not found"; + } + } elsif (vers_cmp($vnum,"0") > -1) { $vstr = "found v$vnum"; @@ -216,6 +236,7 @@ exit; } + # Obtain a database connection. my $dbh = $database->get_connection(); @@ -922,37 +943,40 @@ # configuration details set (basically, the location of the lib dir). print "Generating cgi-bin/codestriker.pl file...\n"; mkdir '../cgi-bin', 0755; -open(CODESTRIKER_BASE, "codestriker.pl.base") - || die "Unable to open codestriker.pl.base file: $!"; -open(CODESTRIKER_PL, ">../cgi-bin/codestriker.pl") - || die "Unable to create ../cgi-bin/codestriker.pl file: $!"; + +my $template = Template->new(); + +eval("use Template;"); +die "Unable to load Template module: $@\n" if $@; + +my $template_vars = {}; # For Win32, don't enable tainting mode. There are weird issues with # ActivePerl, and sometimes with IIS as well. if ($windows) { - print CODESTRIKER_PL '#!perl.exe -w' . "\n"; + $template_vars->{hash_ex_line} = '#!perl.exe -w'; } else { - print CODESTRIKER_PL '#!/usr/bin/perl -wT' . "\n"; + $template_vars->{hash_ex_line} = '#!/usr/bin/perl -wT'; } -my $codestriker_lib = 'use lib \'' . cwd() . '/../lib\';'; -for (my $i = 0; <CODESTRIKER_BASE>; $i++) { - # Check if this line requires any config substitutions. - my $line = $_; - $line =~ s/\@CODESTRIKER_LIB_DECLARATION\@/$codestriker_lib/g; - print CODESTRIKER_PL $line; +$template_vars->{codestriker_lib} = 'use lib \'' . cwd() . '/../lib\';'; - if ($i == 0) { - # Print out a message indicating this is an auto-generated file. - print CODESTRIKER_PL "\n# !!!! THIS FILE IS AUTO-GENERATED by bin/checksetup.pl !!!\n"; - print CODESTRIKER_PL "# !!!! DO NOT EDIT !!!\n"; +$template_vars->{codestriker_conf} = '\'' . cwd() . '/..\''; - print CODESTRIKER_PL "# The base source is bin/codestriker.pl.base.\n"; - } -} -close CODESTRIKER_BASE; +$template_vars->{has_rss} = exists($missing_optional{'XML::RSS'}); + +$template_vars->{scary_warning} = + "# !!!! THIS FILE IS AUTO-GENERATED by bin/checksetup.pl !!!\n". + "# !!!! DO NOT EDIT !!!\n". + "# The base source is bin/codestriker.pl.base.\n"; + +open(CODESTRIKER_PL, ">../cgi-bin/codestriker.pl") + || die "Unable to create ../cgi-bin/codestriker.pl file: $!"; + +$template->process("codestriker.pl.base", $template_vars,\*CODESTRIKER_PL); close CODESTRIKER_PL; + # Make sure the generated file is executable. chmod 0755, '../cgi-bin/codestriker.pl'; Index: codestriker.pl.base =================================================================== RCS file: /cvsroot/codestriker/codestriker/bin/codestriker.pl.base,v retrieving revision 1.15 retrieving revision 1.16 diff -u -r1.15 -r1.16 --- codestriker.pl.base 22 Nov 2004 20:49:59 -0000 1.15 +++ codestriker.pl.base 21 Dec 2004 23:02:53 -0000 1.16 @@ -1,4 +1,6 @@ -# -*-perl-*- +[% hash_ex_line %] + +[% scary_warning %] ############################################################################### # Codestriker: Copyright (c) 2001, 2002 David Sitsky. All rights reserved. @@ -21,7 +23,7 @@ # effectively "pre-load" all of the Codestriker modules in the system, as the # modules below load all of their supporting modules. That is why the # template plugins are "pre-loaded" here. -@CODESTRIKER_LIB_DECLARATION@ +[% codestriker_lib %] use strict; @@ -55,6 +57,8 @@ use Codestriker::Action::ViewTopic; use Codestriker::Action::ViewTopicProperties; use Codestriker::Action::ViewTopicComments; +[% IF has_rss %]use Codestriker::Action::ListTopicsRSS; [% END %] + use Codestriker::Template::Plugin::AutomagicLinks; # Set the temp file location, if one has been specified. @@ -80,8 +84,15 @@ sub main() { # Initialise Codestriker, load up the configuration file. - $0 =~ /^(.*)cgi-bin/; - Codestriker->initialise($1); + Codestriker->initialise([% codestriker_conf %]); + +[% IF has_rss %] + # only generated if checksetup.pl found a good version of XML::RSS. + $Codestriker::rss_enabled = 1; +[% ELSE %] + # valid XML::RSS not found + $Codestriker::rss_enabled = 0; +[% END %] # If allow_delete is defined, but topic state 'Delete' is not, add it # in. This accounts for older configuration files. @@ -170,6 +181,13 @@ } elsif ($action eq "metrics_download") { Codestriker::Action::MetricsReport->process_download($http_input, $http_response); +[% IF has_rss %] + # only generated if checksetup.pl found a good version of XML::RSS. + } elsif ($action eq "list_topics_rss") { + Codestriker::Action::ListTopicsRSS->process($http_input, + $http_response); +[% END %] + } else { # Default action is to list topics that are in state open if the # list functionality is enabled, otherwise go to the create topic Index: Codestriker.pm =================================================================== RCS file: /cvsroot/codestriker/codestriker/lib/Codestriker.pm,v retrieving revision 1.67 retrieving revision 1.68 diff -u -r1.67 -r1.68 --- Codestriker.pm 21 Dec 2004 22:44:24 -0000 1.67 +++ Codestriker.pm 21 Dec 2004 23:02:54 -0000 1.68 @@ -23,7 +23,7 @@ $allow_delete $allow_searchlist $default_file_to_view $allow_projects $antispam_email $VERSION $title $BASEDIR $metric_config $tmpdir @metric_schema $comment_state_metrics - $project_states + $project_states $rss_enabled ); # Version of Codestriker. Index: ListTopics.pm =================================================================== RCS file: /cvsroot/codestriker/codestriker/lib/Codestriker/Action/ListTopics.pm,v retrieving revision 1.29 retrieving revision 1.30 diff -u -r1.29 -r1.30 --- ListTopics.pm 27 Sep 2004 11:20:45 -0000 1.29 +++ ListTopics.pm 21 Dec 2004 23:02:55 -0000 1.30 @@ -27,46 +27,22 @@ # Obtain a new URL builder object. my $url_builder = Codestriker::Http::UrlBuilder->new($query); - # Check that the appropriate fields have been filled in. my $mode = $http_input->get('mode'); - my $sauthor = $http_input->get('sauthor') || ""; - my $sreviewer = $http_input->get('sreviewer') || ""; - my $scc = $http_input->get('scc') || ""; - my $sbugid = $http_input->get('sbugid') || ""; - my $stext = $http_input->get('stext') || ""; - my $sstate = $http_input->get('sstate'); - my $sproject = $http_input->get('sproject'); - my $stitle = $http_input->get('stitle') || 0; - my $sdescription = $http_input->get('sdescription') || 0; - my $scomments = $http_input->get('scomments') || 0; - my $sbody = $http_input->get('sbody') || 0; - my $sfilename = $http_input->get('sfilename') || 0; my $feedback = $http_input->get('feedback'); my $projectid = $http_input->get('projectid'); - # If $sproject has been set to -1, then retrieve the value of the projectid - # from the cookie as the project search value. This is done to facilate - # integration with other systems, which jump straight to this URL, and - # set the cookie explicitly. - if ($sproject eq "-1") { - $sproject = (defined $projectid) ? $projectid : ""; - } - - # Only show open topics if codestriker.pl was run without parameters. - if (defined($http_input->{query}->param) == 0 || !defined($sstate)) { - $sstate = 0; - } - - # handle the sort order of the topics. - my @sort_order = _get_topic_sort_order($http_input); + my ( $sauthor, $sreviewer, $scc, $sbugid, + $sstate, $sproject, $stext, + $stitle, $sdescription, + $scomments, $sbody, $sfilename, + $sort_order) = get_topic_list_query_params($http_input); # Query the model for the specified data. - my @topic_query_results; - Codestriker::Model::Topic->query($sauthor, $sreviewer, $scc, $sbugid, + my @topics = Codestriker::Model::Topic->query($sauthor, $sreviewer, $scc, $sbugid, $sstate, $sproject, $stext, $stitle, $sdescription, $scomments, $sbody, $sfilename, - \@sort_order, \@topic_query_results); + $sort_order); # Display the data, with each topic title linked to the view topic screen. # If only a single project id is being searched over, set that id in the @@ -79,7 +55,7 @@ $http_response->generate_header(topic_title=>"Topic List", projectid=>$projectid_cookie, - topicsort=>join(',',@sort_order), + topicsort=>join(',',@$sort_order), reload=>0, cache=>0); # Create the hash for the template variables. @@ -124,108 +100,178 @@ $sbody, $sfilename, [ split ',', $sstate] , \@project_ids); - # The list of topics. - my @topics; + $vars->{'list_sort_url_rss'} = + $url_builder->list_topics_url_rss($sauthor, $sreviewer, $scc, $sbugid, + $stext, $stitle, + $sdescription, $scomments, + $sbody, $sfilename, + [ split ',', $sstate] , \@project_ids); + + # The list of topics in the template toolkit. + my @template_topics; # For each topic, collect all the reviewers, CC, and bugs, and display it - # as a row in the table. Each bug should be linked appropriately. The - # query function will return a row per topic, per reviewer so this loop - # needs to combine rows that are from the same topic. - for (my $index = 0; $index < scalar(@topic_query_results); ++$index) { - my $topic_row = $topic_query_results[$index]; - - my @accum_bugs = (); - my @accum_reviewers = (); - my @accum_reviewers_not_visited = (); - my @accum_cc = (); - my $accum_id = $topic_row->{id}; - - # Onl include the username part of the email address to save space. - $topic_row->{author} =~ s/\@.*$//o; - my $accum_author = $topic_row->{author}; - - # Accumulate the bug ids, reviewers and cc here for the same topic. - # Note these will be only a few elements long, if that. - for (; $index < scalar(@topic_query_results) && - $accum_id == $topic_row->{id}; - $index++, $topic_row = $topic_query_results[$index]) { + # as a row in the table. Each bug should be linked appropriately. + foreach my $topic (@topics) { + + # do the easy stuff first, 1 to 1 mapping into the template. + + my $template_topic = {}; + + $template_topic->{'view_topic_url'} = + $url_builder->view_url($topic->{topicid}, -1, $mode, + $Codestriker::default_topic_br_mode); + + $template_topic->{'description'} = $topic->{description}; + + $template_topic->{'created'} = + Codestriker->format_short_timestamp($topic->{creation_ts}); + + $template_topic->{'id'} = $topic->{topicid}; + $template_topic->{'title'} = $topic->{title}; - if (defined $topic_row->{bugid}) { - _insert_nonduplicate(\@accum_bugs, $topic_row->{bugid}); + $template_topic->{'version'} = $topic->{version}; + + $template_topic->{'state'} = $Codestriker::topic_states[$topic->{topic_state_id}]; + + # Only include the username part of the email address to save space. + my $accum_author = $topic->{author}; + $accum_author =~ s/\@.*$//o; + $template_topic->{'author'} = $accum_author; + + # cc + my $cc = $topic->{cc}; + $cc =~ s/\@.*$//o; + $template_topic->{'cc'} = $cc; + + # bug ids + my @accum_bugs = split /, /, $topic->{bug_ids}; + for ( my $index = 0; $index < scalar(@accum_bugs); ++$index) { + $accum_bugs[$index] = + $query->a({href=>"$Codestriker::bugtracker$accum_bugs[$index]"}, + $accum_bugs[$index]); + } + $template_topic->{'bugids'} = join ', ', @accum_bugs; + + # do the reviewers + my @reviewers_vistited = + $topic->get_metrics()->get_list_of_actual_topic_participants(); + + my @reviewers = split /, /, $topic->{reviewers}; + for ( my $index = 0; $index < scalar(@reviewers); ++$index) { + + my $reviewer = $reviewers[$index]; + + my $is_visted = 0; + foreach my $visted (@reviewers_vistited) { + if ($visted eq $reviewer) { + $is_visted = 1; + last; + } } # Output the accumulated information into the row. Only # include the username part of an email address for now to # save some space. This should be made a dynamic option # in the future. - $topic_row->{email} =~ s/\@.*$//o; + $reviewer =~ s/\@.*$//o; - if (defined $topic_row->{email}) { - if ($topic_row->{type} == $Codestriker::PARTICIPANT_REVIEWER) { + if ( $is_visted == 0) { + $reviewer = "(" . $reviewer . ")"; + } - if (!$topic_row->{visitedtopic}) { - $topic_row->{email} = "(" . $topic_row->{email} . ")"; + $reviewers[$index] = $reviewer; } - _insert_nonduplicate(\@accum_reviewers, - $topic_row->{email}); - } else { - _insert_nonduplicate(\@accum_cc, $topic_row->{email}); + $template_topic->{'reviewer'} = join(", ",@reviewers); + + my @main_page_comment_metrics = (); + foreach my $comment_state_metric (@{$Codestriker::comment_state_metrics}) { + + if ( exists($comment_state_metric->{show_on_mainpage})) { + foreach my $value (@{$comment_state_metric->{show_on_mainpage}}) { + + my $count = $topic->get_comment_metric_count($comment_state_metric->{name},$value); + + my $template_comment_metric = + { + name => $comment_state_metric->{name}, + value => $value, + count => $count + }; + + push @main_page_comment_metrics,$template_comment_metric; } } } - --$index; - $topic_row = $topic_query_results[$index]; - - my $reviewer_text = join ', ', @accum_reviewers; - my $cc_text = ($#accum_cc >= 0) ? (join ', ', @accum_cc) : ""; - - for (my $i = 0; $i <= $#accum_bugs; $i++) { - $accum_bugs[$i] = - $query->a({href=>"$Codestriker::bugtracker$accum_bugs[$i]"}, - $accum_bugs[$i]); - } - my $bugid_text = join ', ', @accum_bugs; + $template_topic->{'commentmetrics'} = \@main_page_comment_metrics; - # Add this row to the list of topics. - my $topic = {}; - $topic->{'view_topic_url'} = - $url_builder->view_url($accum_id, -1, $mode, - $Codestriker::default_topic_br_mode); - $topic->{'id'} = $accum_id; - $topic->{'title'} = $topic_row->{title}; - $topic->{'description'} = $topic_row->{description}; - $topic->{'author'} = $accum_author; - $topic->{'reviewer'} = $reviewer_text; - $topic->{'cc'} = $cc_text; - $topic->{'created'} = - Codestriker->format_short_timestamp($topic_row->{ts}); - $topic->{'bugids'} = $bugid_text; - $topic->{'state'} = $Codestriker::topic_states[$topic_row->{state}]; - $topic->{'version'} = $topic_row->{version}; - $topic->{'commentmetrics'} = $topic_row->{commentmetrics}; - push @topics, $topic; + push @template_topics, $template_topic; } - $vars->{'topics'} = \@topics; + + $vars->{'topics'} = \@template_topics; $vars->{'states'} = \@Codestriker::topic_states; $vars->{'list_projects_url'} = $url_builder->list_projects_url(); $vars->{'view_metrics_url'} = $url_builder->metric_report_url(); - my $template = Codestriker::Http::Template->new("listtopics"); $template->process($vars); $http_response->generate_footer(); } +# Process the input and return the parts that will feed into the topic +# list query. Returns in the same order that the topic query function +# takes them. +sub get_topic_list_query_params { + my ($http_input) = @_; + + # Check that the appropriate fields have been filled in. + my $sauthor = $http_input->get('sauthor') || ""; + my $sreviewer = $http_input->get('sreviewer') || ""; + my $scc = $http_input->get('scc') || ""; + my $sbugid = $http_input->get('sbugid') || ""; + my $stext = $http_input->get('stext') || ""; + my $sstate = $http_input->get('sstate'); + my $sproject = $http_input->get('sproject'); + my $stitle = $http_input->get('stitle') || 0; + my $sdescription = $http_input->get('sdescription') || 0; + my $scomments = $http_input->get('scomments') || 0; + my $sbody = $http_input->get('sbody') || 0; + my $sfilename = $http_input->get('sfilename') || 0; + my $projectid = $http_input->get('projectid'); + + # If $sproject has been set to -1, then retrieve the value of the projectid + # from the cookie as the project search value. This is done to facilate + # integration with other systems, which jump straight to this URL, and + # set the cookie explicitly. + if ($sproject eq "-1") { + $sproject = (defined $projectid) ? $projectid : ""; + } + + # Only show open topics if codestriker.pl was run without parameters. + if (defined($http_input->{query}->param) == 0 || !defined($sstate)) { + $sstate = 0; + } + + # handle the sort order of the topics. + my @sort_order = get_topic_sort_order($http_input); + + return ( $sauthor, $sreviewer, $scc, $sbugid, + $sstate, $sproject, $stext, + $stitle, $sdescription, + $scomments, $sbody, $sfilename, + \@sort_order); +} + # Process the topic_sort_change input request (if any), and the current sort # cookie (topicsort), and returns a list that defines the topic sort order # that should be used for this request. The function will ensure that # column types are not repeated, and will sort in the opposite direction # if the user clicks on the same column twice. -sub _get_topic_sort_order { +sub get_topic_sort_order { my ($http_input) = @_; my $topic_sort_change = $http_input->get('topic_sort_change'); @@ -274,7 +320,6 @@ } } - # Pull out any elements that are not valid (from a bad cookie or from a bad # input. @@ -293,16 +338,4 @@ return @sort_order; } -# Append an element into an array if it doesn't exist already. Note this is -# only called for arrays of very small sizes (ie typically 1-2 elements). -sub _insert_nonduplicate(\@$) { - my ($array_ref, $value) = @_; - my @array = @$array_ref; - my $i; - for ($i = 0; $i <= $#array; $i++) { - last if ($array[$i] eq $value); - } - push @$array_ref, $value if ($i > $#array); -} - 1; Index: ListTopicsRSS.pm =================================================================== RCS file: ListTopicsRSS.pm diff -N ListTopicsRSS.pm --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ ListTopicsRSS.pm 21 Dec 2004 23:02:55 -0000 1.1 @@ -0,0 +1,141 @@ +############################################################################### +# Codestriker: Copyright (c) 2001, 2002 David Sitsky. All rights reserved. +# si...@us... +# +# This program is free software; you can redistribute it and modify it under +# the terms of the GPL. + +# Action object for displaying a list of topics. + +package Codestriker::Action::ListTopicsRSS; + +use strict; +use Codestriker::Http::Template; +use Codestriker::Model::Topic; +use XML::RSS; + +# If the input is valid, list the appropriate topics. +sub process($$$) { + my ($type, $http_input, $http_response) = @_; + + my $query = $http_response->get_query(); + + # Check if this action is allowed. + if ($Codestriker::allow_searchlist == 0) { + $http_response->error("This function has been disabled"); + } + + # Obtain a new URL builder object. + my $url_builder = Codestriker::Http::UrlBuilder->new($query); + + # Check that the appropriate fields have been filled in. + my $mode = $http_input->get('mode'); + my $sauthor = $http_input->get('sauthor') || ""; + my $sreviewer = $http_input->get('sreviewer') || ""; + my $scc = $http_input->get('scc') || ""; + my $sbugid = $http_input->get('sbugid') || ""; + my $stext = $http_input->get('stext') || ""; + my $sstate = $http_input->get('sstate'); + my $sproject = $http_input->get('sproject'); + my $stitle = $http_input->get('stitle') || 0; + my $sdescription = $http_input->get('sdescription') || 0; + my $scomments = $http_input->get('scomments') || 0; + my $sbody = $http_input->get('sbody') || 0; + my $sfilename = $http_input->get('sfilename') || 0; + my $feedback = $http_input->get('feedback'); + my $projectid = $http_input->get('projectid'); + + # If $sproject has been set to -1, then retrieve the value of the projectid + # from the cookie as the project search value. This is done to facilate + # integration with other systems, which jump straight to this URL, and + # set the cookie explicitly. + if ($sproject eq "-1") { + $sproject = (defined $projectid) ? $projectid : ""; + } + + # Only show open topics if codestriker.pl was run without parameters. + if (defined($http_input->{query}->param) == 0 || !defined($sstate)) { + $sstate = 0; + } + + # handle the sort order of the topics. + my @sort_order = Codestriker::Action::ListTopics::get_topic_sort_order($http_input); + + # Query the model for the specified data. + my @topics = Codestriker::Model::Topic->query($sauthor, $sreviewer, $scc, $sbugid, + $sstate, $sproject, $stext, + $stitle, $sdescription, + $scomments, $sbody, $sfilename, + \@sort_order); + + # Display the data, with each topic title linked to the view topic screen. + # If only a single project id is being searched over, set that id in the + # cookie. + my @project_ids = (); + if ($sproject ne "") { + @project_ids = split ',', $sproject; + } + + # Dump the raw topic data as text/plain. + print $query->header(); + + my $rss = new XML::RSS(version => '2.0'); + + my $this_url = + $url_builder->list_topics_url_rss($sauthor, $sreviewer, $scc, $sbugid, + $stext, $stitle, + $sdescription, $scomments, + $sbody, $sfilename, + [ split ',', $sstate] , \@project_ids); + + + $rss->channel(title=>$Codestriker::title, language=>"en",link=>$this_url); + + # For each topic, collect all the reviewers, CC, and bugs, and display it + # as a row in the table. Each bug should be linked appropriately. + foreach my $topic (@topics) { + + # do the easy stuff first, 1 to 1 mapping into the template. + my $link = + $url_builder->view_url($topic->{topicid}, -1, $mode, + $Codestriker::default_topic_br_mode); + + my $comment_link = $url_builder->view_comments_url($topic->{topicid}); + + my $description = $topic->{description}; + my $title = $topic->{title}; + + if (0) { + # Send out the list of files changes in the RSS description. + my (@filenames, @revisions, @offsets, @binary); + $topic->get_filestable( + \@filenames, + \@revisions, + \@offsets, + \@binary); + + $description .= "<p>" . join( "\n",@filenames); + } + + my @comments = $topic->read_comments(); + + $description .= "<p>Comments: " . scalar( @comments ) . ", "; + $description .= "State: " . $topic->{topic_state} . ", "; + $description .= "Author: " . Codestriker->filter_email($topic->{author}); + + $rss->add_item( + title=>$title, + permaLink=>$link, + description=>$description, + author=> Codestriker->filter_email($topic->{author}), + pubDate=>Codestriker->format_short_timestamp($topic->{creation_ts}), + category=>$topic->{project_name}, + comments=>$comment_link + ); + + } + + print $rss->as_string(); +} + +1; Index: ListTopicsRss.pm =================================================================== RCS file: ListTopicsRss.pm diff -N ListTopicsRss.pm --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ ListTopicsRss.pm 21 Dec 2004 23:02:55 -0000 1.1 @@ -0,0 +1,141 @@ +############################################################################### +# Codestriker: Copyright (c) 2001, 2002 David Sitsky. All rights reserved. +# si...@us... +# +# This program is free software; you can redistribute it and modify it under +# the terms of the GPL. + +# Action object for displaying a list of topics. + +package Codestriker::Action::ListTopicsRSS; + +use strict; +use Codestriker::Http::Template; +use Codestriker::Model::Topic; +use XML::RSS; + +# If the input is valid, list the appropriate topics. +sub process($$$) { + my ($type, $http_input, $http_response) = @_; + + my $query = $http_response->get_query(); + + # Check if this action is allowed. + if ($Codestriker::allow_searchlist == 0) { + $http_response->error("This function has been disabled"); + } + + # Obtain a new URL builder object. + my $url_builder = Codestriker::Http::UrlBuilder->new($query); + + # Check that the appropriate fields have been filled in. + my $mode = $http_input->get('mode'); + my $sauthor = $http_input->get('sauthor') || ""; + my $sreviewer = $http_input->get('sreviewer') || ""; + my $scc = $http_input->get('scc') || ""; + my $sbugid = $http_input->get('sbugid') || ""; + my $stext = $http_input->get('stext') || ""; + my $sstate = $http_input->get('sstate'); + my $sproject = $http_input->get('sproject'); + my $stitle = $http_input->get('stitle') || 0; + my $sdescription = $http_input->get('sdescription') || 0; + my $scomments = $http_input->get('scomments') || 0; + my $sbody = $http_input->get('sbody') || 0; + my $sfilename = $http_input->get('sfilename') || 0; + my $feedback = $http_input->get('feedback'); + my $projectid = $http_input->get('projectid'); + + # If $sproject has been set to -1, then retrieve the value of the projectid + # from the cookie as the project search value. This is done to facilate + # integration with other systems, which jump straight to this URL, and + # set the cookie explicitly. + if ($sproject eq "-1") { + $sproject = (defined $projectid) ? $projectid : ""; + } + + # Only show open topics if codestriker.pl was run without parameters. + if (defined($http_input->{query}->param) == 0 || !defined($sstate)) { + $sstate = 0; + } + + # handle the sort order of the topics. + my @sort_order = Codestriker::Action::ListTopics::get_topic_sort_order($http_input); + + # Query the model for the specified data. + my @topics = Codestriker::Model::Topic->query($sauthor, $sreviewer, $scc, $sbugid, + $sstate, $sproject, $stext, + $stitle, $sdescription, + $scomments, $sbody, $sfilename, + \@sort_order); + + # Display the data, with each topic title linked to the view topic screen. + # If only a single project id is being searched over, set that id in the + # cookie. + my @project_ids = (); + if ($sproject ne "") { + @project_ids = split ',', $sproject; + } + + # Dump the raw topic data as text/plain. + print $query->header(); + + my $rss = new XML::RSS(version => '2.0'); + + my $this_url = + $url_builder->list_topics_url_rss($sauthor, $sreviewer, $scc, $sbugid, + $stext, $stitle, + $sdescription, $scomments, + $sbody, $sfilename, + [ split ',', $sstate] , \@project_ids); + + + $rss->channel(title=>$Codestriker::title, language=>"en",link=>$this_url); + + # For each topic, collect all the reviewers, CC, and bugs, and display it + # as a row in the table. Each bug should be linked appropriately. + foreach my $topic (@topics) { + + # do the easy stuff first, 1 to 1 mapping into the template. + my $link = + $url_builder->view_url($topic->{topicid}, -1, $mode, + $Codestriker::default_topic_br_mode); + + my $comment_link = $url_builder->view_comments_url($topic->{topicid}); + + my $description = $topic->{description}; + my $title = $topic->{title}; + + if (0) { + # Send out the list of files changes in the RSS description. + my (@filenames, @revisions, @offsets, @binary); + $topic->get_filestable( + \@filenames, + \@revisions, + \@offsets, + \@binary); + + $description .= "<p>" . join( "\n",@filenames); + } + + my @comments = $topic->read_comments(); + + $description .= "<p>Comments: " . scalar( @comments ) . ", "; + $description .= "State: " . $topic->{topic_state} . ", "; + $description .= "Author: " . Codestriker->filter_email($topic->{author}); + + $rss->add_item( + title=>$title, + permaLink=>$link, + description=>$description, + author=> Codestriker->filter_email($topic->{author}), + pubDate=>Codestriker->format_short_timestamp($topic->{creation_ts}), + category=>$topic->{project_name}, + comments=>$comment_link + ); + + } + + print $rss->as_string(); +} + +1; Index: DBI.pm =================================================================== RCS file: /cvsroot/codestriker/codestriker/lib/Codestriker/DB/DBI.pm,v retrieving revision 1.6 retrieving revision 1.7 diff -u -r1.6 -r1.7 --- DBI.pm 17 Feb 2004 11:38:09 -0000 1.6 +++ DBI.pm 21 Dec 2004 23:02:55 -0000 1.7 @@ -14,12 +14,22 @@ use Codestriker; use Codestriker::DB::Database; +# DBI connections are expensive to make, only have one per process, and when +# the code asks for a connection, just keep returning the same one. +our $connection; + # Retrieve a connection to the codestriker database for the specified sub get_connection($) { my ($type) = @_; + # makeing a connection is expensive, cache it, + if ( !defined($connection) ) { my $database = Codestriker::DB::Database->get_database(); - return $database->get_connection(); + + $connection = $database->get_connection(); + } + + return $connection; } # Release a connection, and if $success is true and this is a transaction @@ -32,8 +42,6 @@ if ($connection->{AutoCommit} == 0) { $success ? $connection->commit : $connection->rollback; } - - $connection->disconnect; } 1; Index: Template.pm =================================================================== RCS file: /cvsroot/codestriker/codestriker/lib/Codestriker/Http/Template.pm,v retrieving revision 1.10 retrieving revision 1.11 diff -u -r1.10 -r1.11 --- Template.pm 30 Sep 2004 10:27:09 -0000 1.10 +++ Template.pm 21 Dec 2004 23:02:56 -0000 1.11 @@ -84,6 +84,8 @@ $vars->{'main_title'} = $Codestriker::title; + $vars->{'rss_enabled'} = $Codestriker::rss_enabled; + # Indicate if the repository field should be displayed. $vars->{'allow_repositories'} = scalar(@Codestriker::valid_repositories) ? 1 : 0; Index: UrlBuilder.pm =================================================================== RCS file: /cvsroot/codestriker/codestriker/lib/Codestriker/Http/UrlBuilder.pm,v retrieving revision 1.25 retrieving revision 1.26 diff -u -r1.25 -r1.26 --- UrlBuilder.pm 3 Nov 2004 20:48:33 -0000 1.25 +++ UrlBuilder.pm 21 Dec 2004 23:02:57 -0000 1.26 @@ -115,16 +115,36 @@ return $self->{htmldir}; } -# Create the URL for listing the topics. +# Create the URL for listing the topics (and topic search). See +# _list_topics_url for true param list. sub list_topics_url ($$$$$$$$$$$\@\@$) { - my ($self, $sauthor, $sreviewer, $scc, $sbugid, $stext, + my ($self) = @_; + + shift @_; # peal off self. + + return $self->_list_topics_url("list_topics",@_); +} + +# Create the URL for listing the topics (and topic search) via RSS. See +# _list_topics_url for true param list. +sub list_topics_url_rss ($$$$$$$$$$$\@\@$) { + my ($self) = @_; + + shift @_; # peal off self. + + return $self->_list_topics_url("list_topics_rss",@_); +} + +# Create the URL for listing the topics. +sub _list_topics_url ($$$$$$$$$$$$\@\@$) { + my ($self, $action,$sauthor, $sreviewer, $scc, $sbugid, $stext, $stitle, $sdescription, $scomments, $sbody, $sfilename, $state_array_ref, $project_array_ref, $content) = @_; my $sstate = defined $state_array_ref ? (join ',', @$state_array_ref) : ""; my $sproject = defined $project_array_ref ? (join ',', @$project_array_ref) : ""; - return $self->{query}->url() . "?action=list_topics" . + return $self->{query}->url() . "?action=$action" . ($sauthor ne "" ? "&sauthor=$sauthor" : "") . ($sreviewer ne "" ? "&sreviewer=$sreviewer" : "") . ($scc ne "" ? "&scc=$scc" : "") . @@ -140,6 +160,7 @@ (defined $content && $content ne "" ? "&content=$content" : ""); } + # Construct a URL for editing a specific project. sub edit_project_url ($$) { my ($self, $projectid) = @_; Index: MetricStats.pm =================================================================== RCS file: /cvsroot/codestriker/codestriker/lib/Codestriker/Model/MetricStats.pm,v retrieving revision 1.10 retrieving revision 1.11 diff -u -r1.10 -r1.11 --- MetricStats.pm 22 Aug 2004 12:09:29 -0000 1.10 +++ MetricStats.pm 21 Dec 2004 23:02:58 -0000 1.11 @@ -330,7 +330,7 @@ # Get the list of users for this review. my @users = $metrics->get_complete_list_of_topic_participants(); - my @user_metrics = $metrics->get_user_metrics_totals( @users ); + my @user_metrics = $metrics->get_user_metrics_totals(@users); for (my $index = 0; $index < scalar(@{$headers->{user}}); ++$index) { my $count = ""; Index: Metrics.pm =================================================================== RCS file: /cvsroot/codestriker/codestriker/lib/Codestriker/Model/Metrics.pm,v retrieving revision 1.10 retrieving revision 1.11 diff -u -r1.10 -r1.11 --- Metrics.pm 17 Aug 2004 22:29:02 -0000 1.10 +++ Metrics.pm 21 Dec 2004 23:02:58 -0000 1.11 @@ -169,6 +169,29 @@ return @topic_metrics; } +# Get just the list of users that have actually looked at the review. This is +# used on the main page to out users that are not doing the reviews when invited. +sub get_list_of_actual_topic_participants { + my ($self) = @_; + + my $dbh = Codestriker::DB::DBI->get_connection(); + + my $actual_user_list_ref = + $dbh->selectall_arrayref( + 'SELECT DISTINCT email FROM topicviewhistory ' . + 'WHERE topicid = ?',{}, $self->{topicid}); + + my @actual_user_list = (); + foreach my $user ( @$actual_user_list_ref ) { + push @actual_user_list,$user->[0] if $user->[0] ne ""; + } + + # Close the connection, and check for any database errors. + Codestriker::DB::DBI->release_connection($dbh, 1); + + return @actual_user_list; +} + # Get a list of users that have metric data for this topic. People can # look at the topic even if they were not invited, so if somebody touches the # topic, they will appear in this list. Using this function rather than the @@ -433,7 +456,7 @@ } else { # Add them up! - for (my $index = 0; $index < scalar( @total_metrics) ; ++$index) { + for (my $index = 0; $index < scalar(@total_metrics) ; ++$index) { if ($metrics[$index]->{value} ne '') { if ($total_metrics[$index]->{value} eq '') { $total_metrics[$index]->{value} = 0; @@ -690,7 +713,7 @@ $select_topic->execute($self->{topicid}, $username); - my $total_time = $self->calculate_topic_view_time( $select_topic); + my $total_time = $self->calculate_topic_view_time($select_topic); Codestriker::DB::DBI->release_connection($dbh); Index: Topic.pm =================================================================== RCS file: /cvsroot/codestriker/codestriker/lib/Codestriker/Model/Topic.pm,v retrieving revision 1.41 retrieving revision 1.42 diff -u -r1.41 -r1.42 --- Topic.pm 17 Oct 2004 22:32:41 -0000 1.41 +++ Topic.pm 21 Dec 2004 23:02:58 -0000 1.42 @@ -368,6 +368,46 @@ return @{$self->{comments}}; } +# returns a count of the comments that have the given comment +# metric (commentmetrictype) set to value. Used +# on the search page. +sub get_comment_metric_count { + my ($self,$commentmetrictype,$value) = @_; + + my $count = 0; + + if (scalar(@{$self->{comments}}) == 0) { + + # Obtain a database connection. + my $dbh = Codestriker::DB::DBI->get_connection(); + + # the comments have not been read into memory yet. + $count = $dbh->selectrow_array(' + SELECT COUNT(commentstatemetric.value) + FROM commentstatemetric, commentstate + WHERE commentstate.topicid = ? AND + commentstate.id = commentstatemetric.id AND + commentstatemetric.name = ? AND + commentstatemetric.value = ?', + {}, $self->{topicid}, + $commentmetrictype, $value); + + Codestriker::DB::DBI->release_connection($dbh, 1); + + } else { + # already read into memory, don't hit the database. + + foreach my $comment (@{$self->{comments}}) { + if ( exists( $comment->{$commentmetrictype} ) && + $comment->{$commentmetrictype} eq $value ) { + ++$count; + } + } + } + + return $count; +} + # Retrieve the changed files which are a part of this review. It will only pull them # from the database once. @@ -669,8 +709,7 @@ # Return back the list of topics which match the specified parameters. sub query($$$$$$$$$$$$$$\@\@\@) { my ($type, $sauthor, $sreviewer, $scc, $sbugid, $sstate, $sproject, $stext, - $stitle, $sdescription, $scomments, $sbody, $sfilename, $sort_order, - $topic_query_results_ref) = @_; + $stitle, $sdescription, $scomments, $sbody, $sfilename, $sort_order) = @_; # Obtain a database connection. my $database = Codestriker::DB::Database->get_database(); @@ -872,6 +911,8 @@ my $select_topic = $dbh->prepare_cached($query); my $success = defined $select_topic; $success &&= $select_topic->execute(); + my $lastid; + my @topic_list; if ($success) { my ($id, $title, $author, $description, $creation_ts, $state, $bugid, $email, $type, $version); @@ -880,79 +921,29 @@ $bugid, $email, $type, $version) = $select_topic->fetchrow_array()) { - my $topic_query_row = { - id => $id, - title => $title, - description => $description, - author => $author, - ts => $creation_ts, - state => $state, - bugid => $bugid, - email => $email, - type => $type, - version => $version, - }; + # This is a bit heavy, but the search screen does need much + # of the information in the topic object, it is much cleaner + # to just return a fully formed topic object, rathe than a + # array tunned. If performace is an issue, then the topic + # object should use lazy instatation to don't pull data from + # the database unless it is needed. + if ( !defined($lastid) || $id ne $lastid ) { - push @$topic_query_results_ref, $topic_query_row; - } - $select_topic->finish(); - } + my $new_topic = Codestriker::Model::Topic->new($id); - # get the visited flag and the comment state metric. - my $comment_metric_counts = []; - my $lastid; - - foreach my $topicrow (@$topic_query_results_ref) { - # If they configured the comment metrics to be on the main - # page then do the queries here. Because we have a row per - # topic per reviewer, it will make the page load faster if the - # query is only done once per topic. - if ( !defined($lastid) || $topicrow->{id} ne $lastid ) { - $comment_metric_counts = []; - $lastid = $topicrow->{id}; - - foreach my $comment_state_metric - (@{$Codestriker::comment_state_metrics}) { - if ( exists($comment_state_metric->{show_on_mainpage}) ) { - foreach my $value - (@{$comment_state_metric->{show_on_mainpage}}) { - - my $count = $dbh->selectrow_array(' - SELECT count(commentstatemetric.value) - FROM commentstatemetric, commentstate - WHERE commentstate.topicid = ? and - commentstate.id = commentstatemetric.id and - commentstatemetric.name = ? and - commentstatemetric.value = ?', - {}, $topicrow->{id}, - $comment_state_metric->{name}, $value); - - push @$comment_metric_counts, - { name => $comment_state_metric->{name}, - value => $value, - count => $count }; - } - } + push @topic_list,$new_topic; } - } - $topicrow->{commentmetrics} = $comment_metric_counts; + $lastid = $id; - # See if the specified user has hit the topic yet. - # TODO: This should be in the HistoryRecorder module, called - # From ListTopics.pm, not from here. - my $visited = $dbh->selectrow_array(' - SELECT count(creation_ts) FROM topicviewhistory - WHERE topicid = ? and - email = ?', - {}, $topicrow->{id}, $topicrow->{email}); - - $topicrow->{visitedtopic} = $visited; } - + $select_topic->finish(); + } $database->release_connection(); die $dbh->errstr unless $success; + + return @topic_list; } # Add the condition to the specified query string, returning the new query. Index: listtopics.html.tmpl =================================================================== RCS file: /cvsroot/codestriker/codestriker/template/en/default/listtopics.html.tmpl,v retrieving revision 1.31 retrieving revision 1.32 diff -u -r1.31 -r1.32 --- listtopics.html.tmpl 23 Nov 2004 02:47:09 -0000 1.31 +++ listtopics.html.tmpl 21 Dec 2004 23:02:59 -0000 1.32 @@ -125,6 +125,11 @@ <a href="[% view_metrics_url | uri | html %]">Metrics Report</a> +[% IF rss_enabled %] + +| <a href="[% list_sort_url_rss | uri | html %]">RSS</a> + <link rel="alternate" type="application/rdf+xml" title="RSS" href="[% list_sort_url_rss | uri | html %]"> +[% END %] </p> |