package PgCommitFest::CommitFest; use strict; use warnings; use XML::RSS; sub activity { my ($r, $extrapath) = @_; my $d = setup($r, $extrapath); my $title = sprintf 'CommitFest %s: Activity Log', $d->{'name'}; my $rss = '/action/commitfest_activity.rss?id=' . $d->{'id'}; $r->set_title($title); $r->set_rss_alternate($title, $rss); $r->add_link($rss, 'RSS'); $r->add_link('/action/commitfest_view?id=' . $d->{'id'}, 'Back to CommitFest'); my $activity = $r->db->select(<{'id'}); SELECT to_char(v.last_updated_time, 'YYYY-MM-DD HH24:MI:SS') AS last_updated_time, v.last_updater, v.patch_name, v.patch_id, v.activity_type, v.details FROM commitfest_activity_log v WHERE v.commitfest_id = ? ORDER BY v.last_updated_time DESC EOM $r->render_template('commitfest_activity', { 'd' => $d, 'activity' => $activity }); } sub activity_rss { my ($r, $extrapath) = @_; my $d = setup($r, $extrapath, 1); # Fetch most recent 50 updates for relevant CommitFest, or all CommitFests. my $sqlbit = ''; if (defined $d) { $sqlbit = "WHERE v.commitfest_id = " . $d->{'id'}; } my $activity = $r->db->select(<{'name'}) : 'PostgreSQL CommitFest Activity Log'; my $rss = XML::RSS->new('version' => '2.0'); $rss->channel( 'title' => $rssname, 'link' => 'http://commitfest.postgresql.org', 'language' => 'en', 'description' => $rssname, ); foreach my $row (@$activity) { my $content; $rss->add_item( 'guid' => 'http://commitfest.postgresql.org/activity/' . $row->{'activity_id'}, 'title' => $row->{'commitfest_name'} . ': ' . $row->{'patch_name'}, 'pubDate' => $row->{'last_updated_time'}, 'description' => $r->eval_template('commitfest_activity_rss', { 'd' => $row }) ); } print "Content-type: application/xml+rss\n\n"; print $rss->as_string; $r->{'response_sent'} = 1; } sub delete { my ($r) = @_; $r->authenticate('require_login' => 1, 'require_administrator' => 1); $r->set_title('Delete CommitFest'); my $d; eval { $d = $r->db->select_one(<cgi_required_id); DELETE FROM commitfest WHERE id = ? RETURNING id EOM }; my $err = $@; if (! $err) { $r->error_exit('CommitFest not found.') if !defined $d; $r->db->commit; $r->redirect('/'); } if ($err =~ /commitfest_topic_commitfest_id_fkey/) { $r->error_exit(<error_exit("Internal error: $@"); } sub form { my ($r) = @_; $r->authenticate('require_login' => 1, 'require_administrator' => 1); # Decide whether this is a new commitfest or an edit of an existing # commitfest, and if editing reload data from database. my $d; my $id = $r->cgi_id(); if (defined $id) { $r->set_title('Edit CommitFest'); $d = $r->db->select_one(<error_exit('CommitFest not found.') if !defined $d; $r->redirect('/action/commitfest_view?id=' . $id) if $r->cgi('cancel'); } else { $r->set_title('New CommitFest'); $r->redirect('/') if $r->cgi('cancel'); } # Add controls. $r->add_control('name', 'text', 'Name', 'required' => 1); $r->add_control('commitfest_status', 'select', 'Status', 'required' => 1); $r->control('commitfest_status')->choice($r->db->select(<initialize_controls($d); # Handle commit. if ($r->cgi('go') && ! $r->is_error()) { if (defined $id) { $r->db->update('commitfest', { 'id' => $id }, \%value); } else { $id = $r->db->insert_returning_id('commitfest', \%value); } $r->db->commit; $r->redirect('/action/commitfest_view?id=' . $id); } # Display template. $r->render_template('commitfest_form', { 'id' => $id }); } sub search { my ($r) = @_; my $aa = $r->authenticate(); $r->set_title('CommitFest Index'); $r->set_rss_alternate('PostgreSQL CommitFest Activity Log', '/action/commitfest_activity.rss'); if (defined $aa && $aa->{'is_administrator'}) { $r->add_link('/action/commitfest_form', 'New CommitFest'); } $r->add_link('/action/commitfest_activity.rss', 'RSS'); my $list = $r->db->select(<render_template('commitfest_search', { 'list' => $list }); } sub setup { my ($r, $extrapath, $is_optional) = @_; # Target commitfest can be specified either by ID, or we allow special # magic to fetch it by my $id = $r->cgi_id(); my $sqlbit; if (defined $id) { $sqlbit = "WHERE id = " . $r->db->quote($id); } elsif (defined $extrapath) { if ($extrapath eq 'open') { $sqlbit = "WHERE commitfest_status_id = 2 ORDER BY name DESC LIMIT 1"; } elsif ($extrapath eq 'inprogress') { $sqlbit = <error_exit("Unable to identify target CommitFest."); } # Fetch target commitfest from database. my $d = $r->db->select_one(<error_exit('CommitFest not found.') if !defined $d; return $d; } sub view { my ($r, $extrapath) = @_; my $aa = $r->authenticate(); my $d = setup($r, $extrapath); my $id = $d->{'id'}; $r->set_title('CommitFest %s (%s)', $d->{'name'}, $d->{'commitfest_status'}); # Filtering. my $filter_sql = ''; my @filter_text; my $status; if (defined $r->cgi_id('status')) { $status = $r->db->select_one(<cgi_id('status')); SELECT id, name FROM patch_status WHERE id = ? EOM $r->error_exit('Unknown status.') if !defined $status; $filter_sql .= sprintf(' AND patch_status_id = %s', $status->{'id'}); push @filter_text, sprintf 'Filtering on status "%s".', $status->{'name'}; } # Load list of patches. my $previous_topic; my %patch_grouping; my %patch_index; my $patch_list = $r->db->select(<{'id'}); SELECT id, name, patch_status_id, patch_status, author, reviewers, commitfest_topic_id, commitfest_topic, commitfest_id, date_closed FROM patch_view WHERE commitfest_id = ? $filter_sql ORDER BY date_closed, commitfest_topic_sortorder, commitfest_topic, id EOM for my $p (@$patch_list) { if (grep { $_ eq $p->{'patch_status_id'} } qw(4 5 6)) { push @{$patch_grouping{$p->{'patch_status_id'}}}, $p; } else { if (!defined $previous_topic || $previous_topic ne $p->{'commitfest_topic'}) { push @{$patch_grouping{'p'}}, { 'topic_header' => 1, 'commitfest_topic' => $p->{'commitfest_topic'} }; $previous_topic = $p->{'commitfest_topic'}; } push @{$patch_grouping{'p'}}, $p; } $patch_index{$p->{'id'}} = $p; } # Load list of comments. my $comment_list = $r->db->select(<{'id'}); SELECT v.id, v.patch_id, v.patch_comment_type, v.message_id, v.content, v.creator, to_char(v.creation_time, 'YYYY-MM-DD') AS creation_time FROM most_recent_comments(?) v EOM for my $c (@$comment_list) { my $p = $patch_index{$c->{'patch_id'}}; unshift @{$p->{'comment_list'}}, $c; } # Load status counts. my $total_count = 0; my $status_count_list = $r->db->select(<{'id'}); SELECT patch_status_id, patch_status, sum(1) AS num_patches from patch_view WHERE commitfest_id = ? GROUP BY 1, 2 ORDER BY 1 EOM for my $scl (@$status_count_list) { $total_count += $scl->{'num_patches'}; } # Add links. $r->add_link('/action/patch_form?commitfest=' . $id, 'New Patch'); $r->add_link('/action/commitfest_activity?id=' . $id, 'Activity Log'); $r->add_link('/action/commitfest_topic_search?id=' . $id, 'CommitFest Topics'); if (defined $aa && $aa->{'is_administrator'}) { $r->add_link('/action/commitfest_form?id=' . $id, 'Edit CommitFest'); $r->add_link('/action/commitfest_delete?id=' . $id, 'Delete CommitFest', 'Are you sure you want to delete this CommitFest?'); } # Render template. $r->render_template('commitfest_view', { 'd' => $d, 'filter_text' => \@filter_text, 'status_count_list' => $status_count_list, 'total_count' => $total_count, 'patch_grouping' => [ { 'name' => 'Pending Patches', 'patch_list' => $patch_grouping{'p'}, 'closed' => 0 }, { 'name' => 'Committed Patches', 'patch_list' => $patch_grouping{'4'}, 'closed' => 1 }, { 'name' => 'Returned with Feedback', 'patch_list' => $patch_grouping{'5'}, 'closed' => 1 }, { 'name' => 'Rejected Patches', 'patch_list' => $patch_grouping{'6'}, 'closed' => 1 }, ]}); } 1;