diff options
author | Robert Haas | 2009-07-12 03:07:05 +0000 |
---|---|---|
committer | Robert Haas | 2009-07-13 02:41:14 +0000 |
commit | ca339f3ffe2cb51e6d8a8f3419f0c9e5733943c0 (patch) | |
tree | 5ccf4fc749dd017b6c5a1b3a371e5229e18096bb | |
parent | a2a8a018eac6bc48731c4cb6970f53a10fa92de2 (diff) |
Basic activity log for changes to patch table.
-rw-r--r-- | etc/audit.sql | 149 | ||||
-rw-r--r-- | etc/table.sql | 4 | ||||
-rw-r--r-- | etc/view.sql | 13 | ||||
-rw-r--r-- | perl-lib/PgCommitFest/CommitFest.pm | 33 | ||||
-rw-r--r-- | perl-lib/PgCommitFest/Handler.pm | 1 | ||||
-rw-r--r-- | perl-lib/PgCommitFest/Patch.pm | 10 | ||||
-rw-r--r-- | template/commitfest_activity.tt2 | 26 | ||||
-rw-r--r-- | template/commitfest_search.tt2 | 9 |
8 files changed, 239 insertions, 6 deletions
diff --git a/etc/audit.sql b/etc/audit.sql new file mode 100644 index 0000000..fc5c814 --- /dev/null +++ b/etc/audit.sql @@ -0,0 +1,149 @@ +CREATE TABLE patch_audit ( + patch_id integer not null, + change_type varchar not null, + changed_fields varchar[] not null, + commitfest_id integer not null, + original_name varchar not null, + from_commitfest_id integer, + to_commitfest_id integer, + commitfest_topic_id integer not null, + name varchar not null, + patch_status_id integer not null, + author varchar not null, + reviewers varchar not null, + date_closed date, + last_updater varchar, + last_updated_time timestamp with time zone not null +); + +CREATE OR REPLACE FUNCTION patch_audit() RETURNS trigger AS $$ +DECLARE + cf varchar[] := '{}'::varchar[]; + acid integer; + cid integer; + oname varchar; +BEGIN + IF (TG_OP = 'DELETE') THEN + NEW := OLD; + NEW.last_updated_time := now(); + END IF; + IF (TG_OP = 'UPDATE') THEN + IF (OLD.last_updated_time = NEW.last_updated_time) THEN + -- Some kind of system update, just ignore it. + RETURN NULL; + END IF; + cf := CASE WHEN OLD.commitfest_topic_id != NEW.commitfest_topic_id + THEN '{commitfest_topic_id}'::varchar[] + ELSE '{}'::varchar[] END + || CASE WHEN OLD.name != NEW.name + THEN '{name}'::varchar[] + ELSE '{}'::varchar[] END + || CASE WHEN OLD.patch_status_id != NEW.patch_status_id + THEN '{patch_status_id}'::varchar[] + ELSE '{}'::varchar[] END + || CASE WHEN OLD.author != NEW.author + THEN '{author}'::varchar[] + ELSE '{}'::varchar[] END + || CASE WHEN OLD.reviewers != NEW.reviewers + THEN '{reviewers}'::varchar[] + ELSE '{}'::varchar[] END + || CASE WHEN OLD.date_closed IS DISTINCT FROM NEW.date_closed + THEN '{date_closed}'::varchar[] + ELSE '{}'::varchar[] END; + SELECT INTO cid + commitfest_id + FROM + commitfest_topic + WHERE + id = NEW.commitfest_topic_id; + SELECT INTO acid + commitfest_id + FROM + commitfest_topic + WHERE + id = OLD.commitfest_topic_id; + oname := OLD.name; + ELSE + SELECT INTO cid, acid + commitfest_id, commitfest_id + FROM + commitfest_topic + WHERE + id = NEW.commitfest_topic_id; + oname := NEW.name; + END IF; + IF (TG_OP = 'INSERT') THEN + cf := ARRAY['commitfest_id', 'commitfest_topic_id', 'name', + 'patch_status_id', 'author', 'reviewers', 'date_closed']; + END IF; + INSERT INTO patch_audit + (patch_id, change_type, changed_fields, commitfest_id, original_name, + to_commitfest_id, commitfest_topic_id, name, + patch_status_id, author, reviewers, date_closed, last_updater, + last_updated_time) + VALUES + (NEW.id, TG_OP, cf, acid, oname, + CASE WHEN cid != acid THEN cid ELSE NULL END, + NEW.commitfest_topic_id, NEW.name, NEW.patch_status_id, NEW.author, + NEW.reviewers, NEW.date_closed, NEW.last_updater, + NEW.last_updated_time); + -- For an update that changes the CommitFest, we enter two audit records, + -- one under each CommitFest. + IF (cid != acid) THEN + INSERT INTO patch_audit + (patch_id, change_type, changed_fields, commitfest_id, + original_name, + from_commitfest_id, commitfest_topic_id, name, + patch_status_id, author, reviewers, date_closed, last_updater, + last_updated_time) + VALUES + (NEW.id, TG_OP, cf, cid, oname, acid, + NEW.commitfest_topic_id, NEW.name, NEW.patch_status_id, NEW.author, + NEW.reviewers, NEW.date_closed, NEW.last_updater, + NEW.last_updated_time); + END IF; + RETURN NULL; +END +$$ LANGUAGE plpgsql; + +CREATE TRIGGER patch_audit + AFTER INSERT OR UPDATE OR DELETE ON patch + FOR EACH ROW EXECUTE PROCEDURE patch_audit(); + +CREATE OR REPLACE FUNCTION patch_audit_details(patch_audit) RETURNS text AS $$ +DECLARE + v varchar[]; +BEGIN + IF ('name' = ANY($1.changed_fields)) THEN + v := v || ('Name = ' || $1.name)::varchar; + END IF; + IF ($1.from_commitfest_id IS NOT NULL) THEN + v := v || ('Moved From CommitFest = ' || COALESCE((SELECT name FROM + commitfest WHERE id = $1.from_commitfest_id), '???'))::varchar; + END IF; + IF ($1.to_commitfest_id IS NOT NULL) THEN + v := v || ('Moved To CommitFest = ' || COALESCE((SELECT name FROM + commitfest WHERE id = $1.to_commitfest_id), '???'))::varchar; + END IF; + IF ('commitfest_topic_id' = ANY($1.changed_fields)) THEN + v := v || ('Topic = ' || COALESCE((SELECT name FROM commitfest_topic + WHERE id = $1.commitfest_topic_id), '???'))::varchar; + END IF; + IF ('patch_status_id' = ANY($1.changed_fields)) THEN + v := v || ('Patch Status = ' || COALESCE((SELECT name FROM patch_status + WHERE id = $1.patch_status_id), '???'))::varchar; + END IF; + IF ('author' = ANY($1.changed_fields)) THEN + v := v || ('Author = ' || $1.author)::varchar; + END IF; + IF ('reviewers' = ANY($1.changed_fields)) THEN + v := v || ('Reviewers = ' || CASE WHEN $1.reviewers = '' THEN 'Nobody' + ELSE $1.reviewers END)::varchar; + END IF; + IF ('date_closed' = ANY($1.changed_fields)) THEN + v := v || ('Date Closed = ' + || COALESCE($1.date_closed::varchar, 'NULL'))::varchar; + END IF; + RETURN array_to_string(v, ', '); +END +$$ LANGUAGE plpgsql; diff --git a/etc/table.sql b/etc/table.sql index f7ce483..7f9f2ab 100644 --- a/etc/table.sql +++ b/etc/table.sql @@ -59,6 +59,10 @@ CREATE TABLE patch ( creation_time timestamp with time zone not null default now(), PRIMARY KEY (id) ); +ALTER TABLE patch + ADD COLUMN last_updater varchar, + ADD COLUMN last_updated_time timestamp with time zone not null + default now(); CREATE TABLE patch_comment_type ( id integer not null, diff --git a/etc/view.sql b/etc/view.sql index c80fd3a..3b85b4b 100644 --- a/etc/view.sql +++ b/etc/view.sql @@ -31,3 +31,16 @@ FROM patch_comment v INNER JOIN patch_comment_type pct ON v.patch_comment_type_id = pct.id INNER JOIN patch p ON v.patch_id = p.id; + +CREATE OR REPLACE VIEW commitfest_activity_log AS +SELECT v.commitfest_id, v.last_updated_time, v.last_updater, + v.original_name AS patch_name, + p.id AS patch_id, + CASE WHEN v.change_type = 'INSERT' THEN 'New Patch' + WHEN v.change_type = 'UPDATE' THEN 'Patch Edited' + WHEN v.change_type = 'DELETE' THEN 'Patch Deleted' + END AS activity_type, + patch_audit_details(v) AS details +FROM + patch_audit v + LEFT JOIN patch p ON v.patch_id = p.id; diff --git a/perl-lib/PgCommitFest/CommitFest.pm b/perl-lib/PgCommitFest/CommitFest.pm index a4115b2..84cee1f 100644 --- a/perl-lib/PgCommitFest/CommitFest.pm +++ b/perl-lib/PgCommitFest/CommitFest.pm @@ -2,6 +2,27 @@ package PgCommitFest::CommitFest; use strict; use warnings; +sub activity { + my ($r, $extrapath) = @_; + my $d = setup($r, $extrapath); + $r->set_title('CommitFest %s: Activity Log', $d->{'name'}); + $r->add_link('/action/commitfest_view?id=' . $d->{'id'}, + 'Back to CommitFest'); + my $activity = $r->db->select(<<EOM, $d->{'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 delete { my ($r) = @_; $r->authenticate('require_login' => 1, 'require_administrator' => 1); @@ -85,9 +106,8 @@ EOM $r->render_template('commitfest_search', { 'list' => $list }); } -sub view { +sub setup { my ($r, $extrapath) = @_; - my $aa = $r->authenticate(); # Target commitfest can be specified either by ID, or we allow special # magic to fetch it by @@ -121,6 +141,14 @@ EOM SELECT id, name, commitfest_status FROM commitfest_view $sqlbit EOM $r->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'}); @@ -165,6 +193,7 @@ EOM # Add links and render template. $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'}) { diff --git a/perl-lib/PgCommitFest/Handler.pm b/perl-lib/PgCommitFest/Handler.pm index d94e042..b269e0f 100644 --- a/perl-lib/PgCommitFest/Handler.pm +++ b/perl-lib/PgCommitFest/Handler.pm @@ -12,6 +12,7 @@ use Template; our %ACTION = ( 'login' => \&PgCommitFest::Handler::login, 'logout' => \&PgCommitFest::Handler::logout, + 'commitfest_activity' => \&PgCommitFest::CommitFest::activity, 'commitfest_delete' => \&PgCommitFest::CommitFest::delete, 'commitfest_form' => \&PgCommitFest::CommitFest::form, 'commitfest_search' => \&PgCommitFest::CommitFest::search, diff --git a/perl-lib/PgCommitFest/Patch.pm b/perl-lib/PgCommitFest/Patch.pm index 5ebeb42..4a769c8 100644 --- a/perl-lib/PgCommitFest/Patch.pm +++ b/perl-lib/PgCommitFest/Patch.pm @@ -28,10 +28,16 @@ EOM sub delete { my ($r) = @_; - $r->authenticate('require_login' => 1); + my $aa = $r->authenticate('require_login' => 1); $r->set_title('Delete Patch'); my $d; eval { + # Don't bump last_updated_time, as that would trigger an activity log + # record. But do change the last_updater, so that the subsequent + # delete picks up the correct user id. This is a pretty ugly kludge, + # but I don't immediately have a better idea. + $r->db->update('patch', { 'id' => $r->cgi_required_id }, + { 'last_updater' => $aa->{'userid'} }); $d = $r->db->select_one(<<EOM, $r->cgi_required_id); DELETE FROM patch AS p USING commitfest_topic t @@ -132,6 +138,8 @@ EOM # Handle commit. if ($r->cgi('go') && ! $r->is_error()) { + $value{'last_updated_time'} = \'now()'; + $value{'last_updater'} = $aa->{'userid'}; if (defined $id) { $r->db->update('patch', { 'id' => $id }, \%value); } diff --git a/template/commitfest_activity.tt2 b/template/commitfest_activity.tt2 new file mode 100644 index 0000000..358f2e9 --- /dev/null +++ b/template/commitfest_activity.tt2 @@ -0,0 +1,26 @@ +<p></p> + +[% IF activity.size %] +<div class='tblBasic'> +<table cellspacing='0' class='tblBasicGrey' style='width: 100%'> +<tr class='firstrow'> + <th class='colFirst'>Time</th> + <th>User</th> + <th>Patch</th> + <th>Activity Type</th> + <th class='colLast'>Details</th> +</tr> +[% FOREACH a = activity %] +<tr[% IF loop.last %] class='lastrow'[% END %]> + <td class='colFirstT'>[% a.last_updated_time %]</td> + <td class='colMidT'>[% a.last_updater %]</td> + <td>[% IF a.patch_id.defined %]<a href='/https/git.postgresql.org/action/patch_view?id=[% a.patch_id %]'>[% END %][% a.patch_name | htmlsafe %][% IF a.patch_id.defined %]</a>[% END %]</td> + <td class='colMidT'>[% a.activity_type | html %]</td> + <td class='colLastT'>[% a.details %]</td> +</tr> +[% END %] +</table> +</div> +[% ELSE %] +<div>No activity for this CommitFest.</div> +[% END %] diff --git a/template/commitfest_search.tt2 b/template/commitfest_search.tt2 index 9a14dd6..ec39ad4 100644 --- a/template/commitfest_search.tt2 +++ b/template/commitfest_search.tt2 @@ -1,13 +1,16 @@ <p>If you have submitted a patch to pgsql-hackers and want it to be reviewed, you should add it to the <a href='/https/git.postgresql.org/action/commitfest_view/open'>Open -CommitFest</a>. If you want to help with the reviewing process for the +CommitFest</a> (<a href='/https/git.postgresql.org/action/commitfest_activity/open'>Activity Log</a>). +If you want to help with the reviewing process for the CommitFest that's currently taking place (if any), please visit the -<a href='/https/git.postgresql.org/action/commitfest_view/inprogress'>CommitFest In Progress</a>. +<a href='/https/git.postgresql.org/action/commitfest_view/inprogress'>CommitFest In Progress</a> +(<a href='/https/git.postgresql.org/action/commitfest_activity/inprogress'>Activity Log</a>). These links are stable and always reference the open or in progress CommitFest, respectively, provided such a CommitFest exists. (If there is no CommitFest in progress, the CommitFest In Progress link will reference the Open CommitFest instead.) There is also a stable link for the -<a href='/https/git.postgresql.org/action/commitfest_view/previous'>Previous CommitFest</a>, meaning +<a href='/https/git.postgresql.org/action/commitfest_view/previous'>Previous CommitFest</a> +(<a href='/https/git.postgresql.org/action/commitfest_activity/previous'>Activity Log</a>), meaning the most recent one that is now marked Closed. Upcoming CommitFests (other than the one to which patches should be submitted) are marked as <b>Future</b>.</p> |