Index: openacs-4/packages/bug-tracker/bug-tracker.info =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/bug-tracker.info,v diff -u -r1.4 -r1.5 --- openacs-4/packages/bug-tracker/bug-tracker.info 29 Aug 2002 15:15:09 -0000 1.4 +++ openacs-4/packages/bug-tracker/bug-tracker.info 11 Sep 2002 14:03:19 -0000 1.5 @@ -7,36 +7,42 @@ f f - + oracle postgresql Lars Pind Tracks bugs and features, versions and maintainers, in software projects. Contains the best of SDM, Bugzilla, FogBUGZ, and bughost.com. - 2002-08-29 + 2002-09-11 Musea Technologies - + + + + + + + @@ -64,10 +70,19 @@ + + + + + + + + + Index: openacs-4/packages/bug-tracker/lib/master.adp =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/lib/master.adp,v diff -u -r1.1 -r1.2 --- openacs-4/packages/bug-tracker/lib/master.adp 3 May 2002 16:29:59 -0000 1.1 +++ openacs-4/packages/bug-tracker/lib/master.adp 11 Sep 2002 14:03:20 -0000 1.2 @@ -8,22 +8,20 @@ INPUT.bt_navbar { font-family: tahoma,verdana,arial,helvetica; font-weight: bold; font-size: 70%; color: black; } .bt_summary { font-size: 70%; font-family: verdana,arial,helvetica; } .bt_summary_bold { font-size: 70%; font-family: verdana,arial,helvetica; font-weight: bold; } + pre { font-family: Courier; font-size: 10pt; } @signatory @focus@ +/packages/bug-tracker/lib/version-bar +@context_bar@ - -

@header@

-@context_bar@ -
+ - -

- + Index: openacs-4/packages/bug-tracker/lib/master.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/lib/master.tcl,v diff -u -r1.1 -r1.2 --- openacs-4/packages/bug-tracker/lib/master.tcl 3 May 2002 16:29:59 -0000 1.1 +++ openacs-4/packages/bug-tracker/lib/master.tcl 11 Sep 2002 14:03:20 -0000 1.2 @@ -12,4 +12,8 @@ set header $context_bar } +if { ![info exists notification_link] } { + set notification_link "" +} + ad_return_template Index: openacs-4/packages/bug-tracker/lib/nav-bar.adp =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/lib/nav-bar.adp,v diff -u -r1.1 -r1.2 --- openacs-4/packages/bug-tracker/lib/nav-bar.adp 3 May 2002 16:29:59 -0000 1.1 +++ openacs-4/packages/bug-tracker/lib/nav-bar.adp 11 Sep 2002 14:03:20 -0000 1.2 @@ -1,6 +1,15 @@ +
+ + + + +
+   @notification_label@ +
+
Index: openacs-4/packages/bug-tracker/lib/nav-bar.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/lib/nav-bar.tcl,v diff -u -r1.3 -r1.4 --- openacs-4/packages/bug-tracker/lib/nav-bar.tcl 9 Sep 2002 22:18:50 -0000 1.3 +++ openacs-4/packages/bug-tracker/lib/nav-bar.tcl 11 Sep 2002 14:03:20 -0000 1.4 @@ -13,20 +13,42 @@ set admin_p [ad_permission_p $package_id admin] +set notification_url [lindex $notification_link 0] +set notification_label [lindex $notification_link 1] +set notification_title [lindex $notification_link 2] + +regexp {/([^/]+)/[^/]*$} [ad_conn url] match last_dir + +if { [string equal $last_dir "admin"] } { + set url_prefix [ad_conn package_url] +} else { + set url_prefix "" +} + + multirow create links name url -multirow append links "List" "[ad_conn package_url]" +array set filter [bug_tracker::conn filter] +multirow append links "Bugs" "${url_prefix}.?[export_vars { filter:array }]" + if { [ad_permission_p [ad_conn package_id] create] } { - multirow append links "New Bug" "[ad_conn package_url]bug-add[ad_decode $component_id "" "" "?[export_vars { component_id }]"]" + multirow append links "New Bug" "${url_prefix}bug-add" } if { [ad_conn user_id] != 0 } { - multirow append links "My Bugs" "[ad_conn package_url]?[export_vars -url { { actionby {[ad_conn user_id]} } }]" + multirow append links "My Bugs" "${url_prefix}.?[export_vars -url { { filter.actionby {[ad_conn user_id]} } }]" } + +multirow append links "Patches" "[ad_conn package_url]patch-list" + +if { [ad_permission_p [ad_conn package_id] create] } { + multirow append links "New Patch" "[ad_conn package_url]patch-add" +} + multirow append links "Prefs" "[ad_conn package_url]prefs" if { $admin_p } { - multirow append links "Project Admin" "[ad_conn package_url]admin/" + multirow append links "Admin" "[ad_conn package_url]admin/" } set form_action_url "[ad_conn package_url]bug" Index: openacs-4/packages/bug-tracker/lib/pagination.adp =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/lib/pagination.adp,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/bug-tracker/lib/pagination.adp 11 Sep 2002 14:03:20 -0000 1.1 @@ -0,0 +1,10 @@ + +@pagination_form_export_vars@ +
+ + + +
+Page with @pretty_plural@: [ @pagination_filter@ ]     Show @pretty_plural@ per page +
+ Index: openacs-4/packages/bug-tracker/lib/pagination.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/lib/pagination.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/bug-tracker/lib/pagination.tcl 11 Sep 2002 14:03:20 -0000 1.1 @@ -0,0 +1,36 @@ +# @arg row_count The total number of rows. +# @arg offset The current row offset, i.e. the number of rows to skip. Must be an URL parameter. +# @arg interval_size The number of rows per page. Must be an optional URL parameter +# @arg variable_set_to_export An ns_set that should exclude the offset and interval_size variables. You may +# use ad_tcl_vars_to_ns_set to create this set. +# @arg pretty_plural The plural form of whatever items we are paginating (i.e. in the Bug Tracker patches, or bugs) + +set pagination_filter_list [list] +set interval_high $interval_size +set interval_low "1" + +# Set all the variables to export to this template +set export_var_list [list] +for { set i 0 } { $i < [ns_set size $variable_set_to_export] } { incr i } { + set var_name [ns_set key $variable_set_to_export $i] + set $var_name [ns_set value $variable_set_to_export $i] + lappend export_var_list $var_name +} + +set pagination_filter_base_url "[ad_conn url]?[export_vars -url -override { { offset 0 } } $export_var_list]" +set pagination_form_export_vars "[export_vars -form -override { { offset 0 } } $export_var_list]" + +while { $interval_low <= $row_count } { + + if { $interval_high > $row_count } { + set interval_high $row_count + } + + set interval_label [ad_decode $interval_low $row_count "$interval_high" "$interval_low - $interval_high"] + lappend pagination_filter_list [ad_decode [expr 1 + $offset] $interval_low "$interval_label" "$interval_label"] + + set interval_high [expr $interval_high + $interval_size] + set interval_low [expr $interval_high - [expr $interval_size - 1]] +} + +set pagination_filter [join $pagination_filter_list " | "] Index: openacs-4/packages/bug-tracker/lib/version-bar.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/lib/version-bar.tcl,v diff -u -r1.2 -r1.3 --- openacs-4/packages/bug-tracker/lib/version-bar.tcl 28 Aug 2002 15:44:18 -0000 1.2 +++ openacs-4/packages/bug-tracker/lib/version-bar.tcl 11 Sep 2002 14:03:20 -0000 1.3 @@ -21,7 +21,6 @@ set package_url [ad_conn package_url] -set return_url "[ad_conn url][ad_decode [ad_conn query] "" "" "?[ad_conn query]"]" set user_version_url "[ad_conn package_url]prefs?[export_vars -url { return_url }]" ad_return_template Index: openacs-4/packages/bug-tracker/sql/postgresql/bug-tracker-create.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/sql/postgresql/bug-tracker-create.sql,v diff -u -r1.4 -r1.5 --- openacs-4/packages/bug-tracker/sql/postgresql/bug-tracker-create.sql 29 Aug 2002 15:15:11 -0000 1.4 +++ openacs-4/packages/bug-tracker/sql/postgresql/bug-tracker-create.sql 11 Sep 2002 14:03:22 -0000 1.5 @@ -333,10 +333,6 @@ check (comment_format in ('html', 'plain', 'pre')) ); - - - - -- Create the bt_bug object type create function inline_0 () @@ -548,3 +544,206 @@ primary key (user_id, project_id) ); +-- For stability, URLs contain patch numbers rather than ACS Object ids. +-- This avoids dependence on the ACS kernel and makes upgrades easier. +create sequence t_bt_patch_number_seq; +create view bt_patch_number_seq as +select nextval('t_bt_patch_number_seq') as nextval; + +create table bt_patches ( + patch_id integer + constraint bt_patches_pk + primary key + constraint bt_patches_pid_fk + references acs_objects(object_id), + patch_number integer not null, + project_id integer + constraint bt_patches_projects_fk + references bt_projects(project_id), + component_id integer + constraint bt_patches_components_fk + references bt_components(component_id), + summary text, + content text, + generated_from_version integer + constraint bt_patches_vid_fk + references bt_versions(version_id), + apply_to_version integer + constraint bt_patchs_apply_to_version_fk + references bt_versions(version_id), + applied_to_version integer + constraint bt_patchs_applied_to_version_fk + references bt_versions(version_id), + status varchar(50) not null + constraint bt_patchs_status_ck + check (status in ('open', 'accepted', 'refused', 'deleted')) + default 'open', + constraint bt_patches_un + unique(patch_number, project_id) +); + +create table bt_patch_actions ( + action_id integer not null + constraint bt_patch_actions_pk + primary key, + patch_id integer not null + constraint bt_patch_actions_patch_fk + references bt_patches(patch_id) + on delete cascade, + action varchar(50) + constraint bt_patch_actions_action_ck + check (action in ('open', 'edit', 'comment', 'accept', + 'reopen', 'refuse', 'delete')) + default 'open', + actor integer not null + constraint bt_patch_actions_actor_fk + references users(user_id), + action_date timestamp not null + default now(), + comment text, + comment_format varchar(30) default 'plain' not null + constraint bt_patch_actions_comment_format_ck + check (comment_format in ('html', 'plain', 'pre')) +); + +-- Create the bt_patch object type +create function inline_0 () +returns integer as ' +begin + PERFORM acs_object_type__create_type ( + ''bt_patch'', + ''Patch'', + ''Patches'', + ''acs_object'', + ''bt_patches'', + ''patch_id'', + null, + ''f'', + null, + ''bt_patch__name'' + ); + + return 0; +end;' language 'plpgsql'; + +select inline_0 (); + +drop function inline_0 (); + +create function bt_patch__new( + integer, -- patch_id + integer, -- project_id + integer, -- component_id + text, -- summary + text, -- description + text, -- description_format + text, -- content + integer, -- generated_from_version + integer, -- creation_user + varchar -- creation_ip +) returns int +as ' +declare + p_patch_id alias for $1; + p_project_id alias for $2; + p_component_id alias for $3; + p_summary alias for $4; + p_description alias for $5; + p_description_format alias for $6; + p_content alias for $7; + p_generated_from_version alias for $8; + p_creation_user alias for $9; + p_creation_ip alias for $10; + + v_patch_id integer; + v_patch_number integer; + v_action_id integer; +begin + + v_patch_id := acs_object__new( + p_patch_id, -- object_id + ''bt_patch'', -- object_type + now(), -- creation_date + p_creation_user, -- creation_user + p_creation_ip, -- creation_ip + p_project_id, -- context_id + ''t'' -- security_inherit_p + ); + + select coalesce(max(patch_number),0) + 1 + into v_patch_number + from bt_patches + where project_id = p_project_id; + + insert into bt_patches + (patch_id, + project_id, + component_id, + summary, + content, + generated_from_version, + patch_number) + values + (v_patch_id, + p_project_id, + p_component_id, + p_summary, + p_content, + p_generated_from_version, + v_patch_number); + + select nextval(''t_acs_object_id_seq'') + into v_action_id; + + insert into bt_patch_actions + (action_id, patch_id, action, actor, comment, comment_format) + values + (v_action_id, v_patch_id, ''open'', p_creation_user, p_description, p_description_format); + + return 0; +end; +' language 'plpgsql'; + +create function bt_patch__name( + integer -- patch_id +) returns varchar +as ' +declare + p_patch_id alias for $1; + v_name varchar; +begin + select summary + into v_name + from bt_patches + where patch_id = p_patch_id; + + return v_name; +end; +' language 'plpgsql'; + +create function bt_patch__delete( + integer -- patch_id +) returns integer +as ' +declare + p_patch_id alias for $1; +begin + perform acs_object__delete(p_patch_id); + + return 0; +end; +' language 'plpgsql'; + +-- There is a many to many relationship between patches and bugs +create table bt_patch_bug_map ( + patch_id integer not null + constraint bt_patch_bug_map_pid_fk + references bt_patches(patch_id) + on delete cascade, + bug_id integer not null + constraint bt_patch_bug_map_bid_fk + references bt_bugs(bug_id) + on delete cascade, + constraint bt_patch_bug_map_un + unique (patch_id, bug_id) +); Index: openacs-4/packages/bug-tracker/sql/postgresql/bug-tracker-drop.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/sql/postgresql/bug-tracker-drop.sql,v diff -u -r1.2 -r1.3 --- openacs-4/packages/bug-tracker/sql/postgresql/bug-tracker-drop.sql 21 Aug 2002 07:32:07 -0000 1.2 +++ openacs-4/packages/bug-tracker/sql/postgresql/bug-tracker-drop.sql 11 Sep 2002 14:03:22 -0000 1.3 @@ -44,3 +44,17 @@ delete from acs_objects where object_type = 'bt_bug'; select acs_object_type__drop_type('bt_bug', 't'); + +drop table bt_patch_bug_map; +drop function bt_patch__delete(integer); +drop function bt_patch__name(integer); +drop function bt_patch__new(integer,integer,integer,text,text,text,text,integer,integer,varchar); +drop table bt_patch_actions; +drop table bt_patches; +drop sequence t_bt_patch_number_seq; + +delete from acs_objects where object_type = 'bt_patch'; + +select acs_object_type__drop_type('bt_patch', 't'); + +\i bug-tracker-notifications-drop.sql Index: openacs-4/packages/bug-tracker/sql/postgresql/bug-tracker-notifications-drop.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/sql/postgresql/Attic/bug-tracker-notifications-drop.sql,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/bug-tracker/sql/postgresql/bug-tracker-notifications-drop.sql 11 Sep 2002 14:03:22 -0000 1.1 @@ -0,0 +1,76 @@ +-- +-- This script drops all notifications setup +-- by the Bug Tracker application +-- +-- FIXME TODO: This code was copied and pasted from the forums package +-- and I don't fully understand it. +-- Much of this code should probably be moved into the +-- Notifications package, i.e. there should be a method +-- to fully drop a notification type with all associated +-- service contract data. +-- +-- @author Peter Marklund (peter@collaboraid.biz) + +-- Delete the notification data +create function inline_0 () + returns integer as ' + declare + row record; + begin + for row in select nt.type_id + from notification_types nt + where nt.short_name in (''bug_tracker_project_notif'', ''bug_tracker_bug_notif'') + loop + perform notification_type__delete(row.type_id); + delete from notifications where type_id = row.type_id; + delete from notification_types where type_id = row.type_id; + delete from notification_types_intervals where type_id = row.type_id; + delete from notification_types_del_methods where type_id = row.type_id; + end loop; + + return null; + end;' language 'plpgsql'; + + select inline_0(); + drop function inline_0 (); + +-- Delete the service contract data +create function bt_service_contract_delete(varchar,varchar) +returns integer as ' +declare + p_impl_name alias for $1; + p_impl_short_name alias for $2; + impl_id integer; + v_foo integer; +begin + + -- the notification type impl + impl_id := acs_sc_impl__get_id ( + ''NotificationType'', -- impl_contract_name + p_impl_name -- impl_name + ); + + PERFORM acs_sc_binding__delete ( + ''NotificationType'', + p_impl_name + ); + + v_foo := acs_sc_impl_alias__delete ( + ''NotificationType'', -- impl_contract_name + p_impl_name, -- impl_name + ''GetURL'' -- impl_operation_name + ); + + v_foo := acs_sc_impl_alias__delete ( + ''NotificationType'', -- impl_contract_name + p_impl_name, -- impl_name + ''ProcessReply'' -- impl_operation_name + ); + + return 0; +end; +' language 'plpgsql'; + +select bt_service_contract_delete('bug_tracker_project_notif_type','but_tracker_project_notif'); +select bt_service_contract_delete('bug_tracker_bug_notif_type','but_tracker_bug_notif'); +drop function bt_service_contract_delete(varchar,varchar); Index: openacs-4/packages/bug-tracker/sql/postgresql/bug-tracker-notifications-init.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/sql/postgresql/Attic/bug-tracker-notifications-init.sql,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/bug-tracker/sql/postgresql/bug-tracker-notifications-init.sql 11 Sep 2002 14:03:22 -0000 1.1 @@ -0,0 +1,144 @@ +-- +-- This script registers the notifications of the +-- Bug Tracker with the Notifications service. It also +-- implements the NotificationType service contract. +-- +-- @author Peter Marklund (peter@collaboraid.biz) + +create function inline_0() returns integer as ' +declare + impl_id integer; + v_foo integer; +begin + -- Project level notifications START + + -- Create a project level implementation of the NotificationType + -- service contract + impl_id := acs_sc_impl__new ( + ''NotificationType'', + ''bug_tracker_project_notif_type'', + ''bug_tracker'' + ); + + -- Note: all operations of a service contract *must* be + -- implemented before we can bind the implementation + -- to the service contract + + -- Implement the GetURL operation + v_foo := acs_sc_impl_alias__new ( + ''NotificationType'', + ''bug_tracker_project_notif_type'', + ''GetURL'', + ''bug_tracker::notification::get_url'', + ''TCL'' + ); + + -- Implement the ProcessReply operation + v_foo := acs_sc_impl_alias__new ( + ''NotificationType'', + ''bug_tracker_project_notif_type'', + ''ProcessReply'', + ''bug_tracker::notification::process_reply'', + ''TCL'' + ); + + -- Bind the project level implementation to + -- the NotificationType service contract + PERFORM acs_sc_binding__new ( + ''NotificationType'', + ''bug_tracker_project_notif_type'' + ); + + -- Create the project notification type + v_foo:= notification_type__new ( + NULL, + impl_id, + ''bug_tracker_project_notif'', + ''Bug Tracker Project Notification'', + ''Notifications for entire project (package) in the Bug Tracker'', + now(), + NULL, + NULL, + NULL + ); + + -- Project notification intervals + insert into notification_types_intervals + (type_id, interval_id) + select v_foo, interval_id + from notification_intervals where name in (''instant'',''hourly'',''daily''); + + -- Project delivery type + insert into notification_types_del_methods + (type_id, delivery_method_id) + select v_foo, delivery_method_id + from notification_delivery_methods where short_name in (''email''); + + -- Project level notifications END + + -- Bug level notifications START + + -- The bug service contract implementation + impl_id := acs_sc_impl__new ( + ''NotificationType'', + ''bug_tracker_bug_notif_type'', + ''bug_tracker'' + ); + + -- Bug level implementation of GetURL operation + v_foo := acs_sc_impl_alias__new ( + ''NotificationType'', + ''bug_tracker_bug_notif_type'', + ''GetURL'', + ''bug_tracker::notification::get_url'', + ''TCL'' + ); + + -- Bug level implementation of ProcessReply operation + v_foo := acs_sc_impl_alias__new ( + ''NotificationType'', + ''bug_tracker_bug_notif_type'', + ''ProcessReply'', + ''bug_tracker::notification::process_reply'', + ''TCL'' + ); + + -- Bind the bug level implementation to the NotificationType contract + PERFORM acs_sc_binding__new ( + ''NotificationType'', + ''bug_tracker_bug_notif_type'' + ); + + -- Create the bug notification type + v_foo:= notification_type__new ( + NULL, + impl_id, + ''bug_tracker_bug_notif'', + ''Bug Tracker Bug Notification'', + ''Notifications for a bug in the Bug Tracker'', + now(), + NULL, + NULL, + NULL + ); + + -- Enable all notification intervals for bug notifications + insert into notification_types_intervals + (type_id, interval_id) + select v_foo, interval_id + from notification_intervals where name in (''instant'',''hourly'',''daily''); + + -- Bug notification are per email + insert into notification_types_del_methods + (type_id, delivery_method_id) + select v_foo, delivery_method_id + from notification_delivery_methods where short_name in (''email''); + + -- Bug level notifications END + + return (0); +end; +' language 'plpgsql'; + +select inline_0(); +drop function inline_0(); Index: openacs-4/packages/bug-tracker/sql/postgresql/upgrade-0.7d5-0.8.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/sql/postgresql/upgrade-0.7d5-0.8.sql,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/bug-tracker/sql/postgresql/upgrade-0.7d5-0.8.sql 11 Sep 2002 14:03:22 -0000 1.1 @@ -0,0 +1,205 @@ +-- For stability, URLs contain patch numbers rather than ACS Object ids. +-- This avoids dependence on the ACS kernel and makes upgrades easier. +create sequence t_bt_patch_number_seq; +create view bt_patch_number_seq as +select nextval('t_bt_patch_number_seq') as nextval; + +create table bt_patches ( + patch_id integer + constraint bt_patches_pk + primary key + constraint bt_patches_pid_fk + references acs_objects(object_id), + patch_number integer not null, + project_id integer + constraint bt_patches_projects_fk + references bt_projects(project_id), + component_id integer + constraint bt_patches_components_fk + references bt_components(component_id), + summary text, + content text, + generated_from_version integer + constraint bt_patches_vid_fk + references bt_versions(version_id), + apply_to_version integer + constraint bt_patchs_apply_to_version_fk + references bt_versions(version_id), + applied_to_version integer + constraint bt_patchs_applied_to_version_fk + references bt_versions(version_id), + status varchar(50) not null + constraint bt_patchs_status_ck + check (status in ('open', 'accepted', 'refused', 'deleted')) + default 'open', + constraint bt_patches_un + unique(patch_number, project_id) +); + +create table bt_patch_actions ( + action_id integer not null + constraint bt_patch_actions_pk + primary key, + patch_id integer not null + constraint bt_patch_actions_patch_fk + references bt_patches(patch_id) + on delete cascade, + action varchar(50) + constraint bt_patch_actions_action_ck + check (action in ('open', 'edit', 'comment', 'accept', + 'reopen', 'refuse', 'delete')) + default 'open', + actor integer not null + constraint bt_patch_actions_actor_fk + references users(user_id), + action_date timestamp not null + default now(), + comment text, + comment_format varchar(30) default 'plain' not null + constraint bt_patch_actions_comment_format_ck + check (comment_format in ('html', 'plain', 'pre')) +); + +-- Create the bt_patch object type +create function inline_0 () +returns integer as ' +begin + PERFORM acs_object_type__create_type ( + ''bt_patch'', + ''Patch'', + ''Patches'', + ''acs_object'', + ''bt_patches'', + ''patch_id'', + null, + ''f'', + null, + ''bt_patch__name'' + ); + + return 0; +end;' language 'plpgsql'; + +select inline_0 (); + +drop function inline_0 (); + +create function bt_patch__new( + integer, -- patch_id + integer, -- project_id + integer, -- component_id + text, -- summary + text, -- description + text, -- description_format + text, -- content + integer, -- generated_from_version + integer, -- creation_user + varchar -- creation_ip +) returns int +as ' +declare + p_patch_id alias for $1; + p_project_id alias for $2; + p_component_id alias for $3; + p_summary alias for $4; + p_description alias for $5; + p_description_format alias for $6; + p_content alias for $7; + p_generated_from_version alias for $8; + p_creation_user alias for $9; + p_creation_ip alias for $10; + + v_patch_id integer; + v_patch_number integer; + v_action_id integer; +begin + + v_patch_id := acs_object__new( + p_patch_id, -- object_id + ''bt_patch'', -- object_type + now(), -- creation_date + p_creation_user, -- creation_user + p_creation_ip, -- creation_ip + p_project_id, -- context_id + ''t'' -- security_inherit_p + ); + + select coalesce(max(patch_number),0) + 1 + into v_patch_number + from bt_patches + where project_id = p_project_id; + + insert into bt_patches + (patch_id, + project_id, + component_id, + summary, + content, + generated_from_version, + patch_number) + values + (v_patch_id, + p_project_id, + p_component_id, + p_summary, + p_content, + p_generated_from_version, + v_patch_number); + + select nextval(''t_acs_object_id_seq'') + into v_action_id; + + insert into bt_patch_actions + (action_id, patch_id, action, actor, comment, comment_format) + values + (v_action_id, v_patch_id, ''open'', p_creation_user, p_description, p_description_format); + + return 0; +end; +' language 'plpgsql'; + +create function bt_patch__name( + integer -- patch_id +) returns varchar +as ' +declare + p_patch_id alias for $1; + v_name varchar; +begin + select summary + into v_name + from bt_patches + where patch_id = p_patch_id; + + return v_name; +end; +' language 'plpgsql'; + +create function bt_patch__delete( + integer -- patch_id +) returns integer +as ' +declare + p_patch_id alias for $1; +begin + perform acs_object__delete(p_patch_id); + + return 0; +end; +' language 'plpgsql'; + +-- There is a many to many relationship between patches and bugs +create table bt_patch_bug_map ( + patch_id integer not null + constraint bt_patch_bug_map_pid_fk + references bt_patches(patch_id) + on delete cascade, + bug_id integer not null + constraint bt_patch_bug_map_bid_fk + references bt_bugs(bug_id) + on delete cascade, + constraint bt_patch_bug_map_un + unique (patch_id, bug_id) +); + +\i bug-tracker-notifications-init.sql Index: openacs-4/packages/bug-tracker/tcl/bug-tracker-notification-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/tcl/bug-tracker-notification-procs.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/bug-tracker/tcl/bug-tracker-notification-procs.tcl 11 Sep 2002 14:03:23 -0000 1.1 @@ -0,0 +1,20 @@ +-- +-- Procedures of the Bug Tracker application related +-- to notifications. +-- +-- @author Peter Marklund (peter@collaboraid.biz) + +namespace eval bug_tracker::notification { + + ad_proc -public get_url { + object_id + } { + # Todo: Implement this proc + } + + ad_proc -public process_reply { + reply_id + } { + # Todo: Implement this proc + } +} Index: openacs-4/packages/bug-tracker/tcl/bug-tracker-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/tcl/bug-tracker-procs.tcl,v diff -u -r1.9 -r1.10 --- openacs-4/packages/bug-tracker/tcl/bug-tracker-procs.tcl 8 Sep 2002 18:02:16 -0000 1.9 +++ openacs-4/packages/bug-tracker/tcl/bug-tracker-procs.tcl 11 Sep 2002 14:03:23 -0000 1.10 @@ -50,7 +50,7 @@ return $bt_conn($var) } } - component_id { + component_id - filter - filter_human_readable - filter_where_clauses - filter_order_by_clause { return {} } default { @@ -65,6 +65,13 @@ } } } + + ad_proc get_bug_id { + {-bug_number:required} + {-project_id:required} + } { + return [db_string bug_id { select bug_id from bt_bugs where bug_number = :bug_number and project_id = :project_id }] + } ##### # @@ -249,8 +256,26 @@ } } + ad_proc patch_status_get_options {} { + return { { "Open" open } { "Accepted" accepted } { "Refused" refused } { "Deleted" deleted }} + } + + ad_proc patch_status_pretty { + status + } { + array set status_codes { + open "Open" + accepted "Accepted" + refused "Refused" + deleted "Deleted" + } + if { [info exists status_codes($status)] } { + return $status_codes($status) + } else { + return "" + } + } - ##### # # Resolution @@ -475,6 +500,27 @@ } } + ad_proc patch_action_pretty { + action + } { + + array set action_codes { + open "Opened" + edit "Edited" + comment "Comment" + accept "Accepted" + reopen "Reopened" + refuse "Refused" + delete "Deleted" + } + + if { [info exists action_codes($action)] } { + return $action_codes($action) + } else { + return "" + } + } + ##### # # Users (assignee) @@ -519,20 +565,20 @@ ##### ad_proc bug_notify { - bug_id - action - comment - comment_format - {resolution ""} + {-bug_id:required} + {-action ""} + {-comment ""} + {-comment_format ""} + {-resolution ""} + {-patch_summary ""} } { - ns_log Notice "bug_id = $bug_id" - set package_id [ad_conn package_id] db_1row bug { select b.bug_id, b.bug_number, b.summary, + b.project_id, o.creation_user as submitter_user_id, submitter.first_names as submitter_first_names, submitter.last_name as submitter_last_name, @@ -584,42 +630,441 @@ and sc.severity_id = b.severity and submitter.user_id = o.creation_user } -column_array bug - - set subject "Bug #$bug(bug_number). [string_truncate -len 30 $bug(summary)]: [bug_action_pretty $action $resolution] by [conn user_first_names] [conn user_last_name]" - - set body "Bug #$bug(bug_number). $bug(summary) - - Action: [bug_action_pretty $action $resolution] by [conn user_first_names] [conn user_last_name] - " - if { ![empty_string_p $comment] } { - append body " - Comment: - - [bug_convert_comment_to_text -comment $comment -format $comment_format] - " + + set subject_start "Bug #$bug(bug_number). [string_truncate -len 30 $bug(summary)]" + set body_start "Bug #$bug(bug_number). $bug(summary)" + + if { ![string equal $action "patched"] } { + set subject "$subject_start: [bug_action_pretty $action $resolution] by [conn user_first_names] [conn user_last_name]" + + set body "$body_start + + Action: [bug_action_pretty $action $resolution] by [conn user_first_names] [conn user_last_name] + " + if { ![empty_string_p $comment] } { + append body " + Comment: + + [bug_convert_comment_to_text -comment $comment -format $comment_format] + " + } + + } else { + # The bug was patched - we use different text in this case + set subject "$subject_start was patched by [conn user_first_names] [conn user_last_name]" + + set body "$body_start + + A patch with summary \"$patch_summary\" has been entered for this bug." } - + append body " - [ad_url][ad_conn package_url]bug?[export_vars -url { { bug_number $bug(bug_number) } }] - " - - array set recipient [list] - set recipient(${bug(submitter_email)}) 1 - set recipient(${bug(assignee_email)}) 1 - - # don't spam the current user - set recipient([conn user_email]) 0 - - set component_id $bug(component_id) - set sender_email [db_string maintainer { select email from cc_users where user_id = bt_component__default_assignee(:component_id) } -default [ad_system_owner] ] - - foreach email [array names recipient] { - if { $recipient($email) && ![empty_string_p $email] } { - if {[catch {ns_sendmail $email $sender_email $subject $body} errmsg]} { - # In case we can't send the email - ns_log Notice "\[bug-tracker\] Error sending email: $errmsg" + [ad_url][ad_conn package_url]bug?[export_vars -url { { bug_number $bug(bug_number) } }] + " + + # Use the Notification service to alert (could be immediately, or daily, or weekly) + # people who have signed up for notification on this bug + notification::new \ + -type_id [notification::type::get_type_id -short_name bug_tracker_bug_notif] \ + -object_id $bug(bug_id) \ + -response_id $bug(bug_id) \ + -notif_subject $subject \ + -notif_text $body + + # Use the Notification service to alert people who have signed up for notification + # in this bug tracker package instance + notification::new \ + -type_id [notification::type::get_type_id -short_name bug_tracker_project_notif] \ + -object_id $bug(project_id) \ + -response_id $bug(bug_id) \ + -notif_subject $subject \ + -notif_text $body + } + + ad_proc add_instant_alert { + {-bug_id:required} + {-user_id:required} + } { + notification::request::new \ + -type_id [notification::type::get_type_id -short_name bug_tracker_bug_notif] \ + -user_id $user_id \ + -object_id $bug_id \ + -interval_id [notification::get_interval_id -name "daily"] \ + -delivery_method_id [notification::get_delivery_method_id -name "email"] + } + + ad_proc get_notification_link { + {-type:required} + {-object_id:required} + {-pretty_name:required} + {-url:required} + } { + Returns a list with the url, label, and title for a notifications link (subscribe or unsubscribe). + } { + + set user_id [ad_conn user_id] + + set notification_link [list] + # Only present the link to logged in users. + if { $user_id != "0" } { + set type_id [notification::type::get_type_id -short_name $type] + + set request_id [notification::request::get_request_id -type_id $type_id -object_id $object_id -user_id $user_id] + + if { ![empty_string_p $request_id] } { + # The user is already subscribed + lappend notification_link [notification::display::unsubscribe_url -request_id $request_id -url $url] + lappend notification_link "Unsubscribe" + lappend notification_link "Unsubscribe from notifications for this $pretty_name" + } else { + # The user is not subscribed + lappend notification_link [notification::display::subscribe_url \ + -type bug_tracker_project_notif \ + -object_id $object_id \ + -url $url \ + -user_id $user_id \ + -pretty_name "a $pretty_name" + ] + lappend notification_link "Subscribe" + lappend notification_link "Subscribe to notifications for this $pretty_name" + } + } + + return $notification_link + } + + ad_proc map_patch_to_bug { + {-patch_id:required} + {-bug_id:required} + } { + db_dml map_patch_to_bug { + insert into bt_patch_bug_map (patch_id, bug_id) values (:patch_id, :bug_id) + } + } + + ad_proc unmap_patch_from_bug { + {-patch_number:required} + {-bug_number:required} + } { + set package_id [ad_conn package_id] + db_dml unmap_patch_from_bug { + delete from bt_patch_bug_map + where bug_id = (select bug_id from bt_bugs + where bug_number = :bug_number + and project_id = :package_id) + and patch_id = (select patch_id from bt_patches + where patch_number = :patch_number + and project_id = :package_id) + } + } + + ad_proc get_mapped_bugs { + {-patch_number:required} + {-only_open_p "0"} + } { + Return a list of lists with the bug number in the first element and the bug + summary in the second. + } { + set bug_list [list] + set package_id [ad_conn package_id] + + set open_clause [ad_decode $only_open_p "1" "\n and bt_bugs.status = 'open'" ""] + + db_foreach get_bugs_for_patch "select bt_bugs.bug_number, + bt_bugs.summary + from bt_bugs, bt_patch_bug_map + where bt_bugs.bug_id = bt_patch_bug_map.bug_id + and bt_patch_bug_map.patch_id = (select patch_id + from bt_patches + where patch_number = :patch_number + and project_id = :package_id + ) + $open_clause" { + + lappend bug_list [list "$summary" "$bug_number"] + } + + return $bug_list + } + + ad_proc get_bug_links { + {-patch_id:required} + {-patch_number:required} + {-write_or_submitter_p:required} + } { + set bug_list [get_mapped_bugs -patch_number $patch_number] + set bug_link_list [list] + + if { [llength $bug_list] == "0"} { + return "" + } else { + + foreach bug_item $bug_list { + + set bug_number [lindex $bug_item 1] + set bug_summary [lindex $bug_item 0] + + set unmap_url "unmap-patch-from-bug?[export_vars -url { patch_number bug_number } ]" + if { $write_or_submitter_p } { + set unmap_link "(unmap)" + } else { + set unmap_link "" } + lappend bug_link_list "$bug_summary $unmap_link" + } + + if { [llength $bug_link_list] != 0 } { + set bugs_string [join $bug_link_list ", "] + } else { + set bugs_string "No bugs." } + + return $bugs_string } - } + } + + ad_proc get_patch_links { + {-bug_id:required} + {-show_patch_status "open"} + } { + set patch_list [list] + + switch -- $show_patch_status { + open { + set status_where_clause "and bt_patches.status = :show_patch_status" + } + all { + set status_where_clause "" + } + } + + db_foreach get_patches_for_bug \ + "select bt_patches.patch_number, + bt_patches.summary, + bt_patches.status + from bt_patch_bug_map, bt_patches + where bt_patch_bug_map.bug_id = :bug_id + and bt_patch_bug_map.patch_id = bt_patches.patch_id + $status_where_clause + " { + + set status_indicator [ad_decode $show_patch_status "all" "($status)" ""] + lappend patch_list "$summary $status_indicator" + } if_no_rows { + set patches_string "No patches." + } + + if { [llength $patch_list] != 0 } { + set patches_string [join $patch_list ", "] + } + + return $patches_string + } + + ad_proc get_patch_submitter { + {-patch_number:required} + } { + set package_id [ad_conn package_id] + return [db_string patch_submitter_id "select acs_objects.creation_user + from bt_patches, acs_objects + where bt_patches.patch_number = :patch_number + and bt_patches.project_id = :package_id + and bt_patches.patch_id = acs_objects.object_id"] + } + + ad_proc update_patch_status { + {-patch_number:required} + {-new_status:required} + } { + set package_id [ad_conn package_id] + db_dml update_patch_status "update bt_patches + set status = :new_status + where bt_patches.project_id = :package_id + and bt_patches.patch_number = :patch_number" + } + + ad_proc get_uploaded_patch_file_content { + + } { + set patch_file [ns_queryget patch_file] + + if { [empty_string_p $patch_file] } { + # No patch file was uploaded + return "" + } + + set tmp_file [ns_queryget patch_file.tmpfile] + set tmp_file_channel [open $tmp_file r] + set content [read $tmp_file_channel] + + return $content + } + + ad_proc parse_filters { filter_array_name } { + Parses the array named in 'filter_array_name', setting local + variables for the filter parameters, and constructing a chunk + that can be used in a query, plus a human readable + string. Sets the result in bug_tracker::conn as + 'filter_human_readable', 'filter_where_clauses', and + 'filter_order_by_clause'. + } { + upvar $filter_array_name filter + + set where_clauses [list] + + set valid_filters { + status + bug_type + fix_for_version:integer + severity:integer + priority:integer + assignee:integer + component_id:integer + actionby:integer + {orderby ""} + } + + foreach name $valid_filters { + if { [llength $name] > 1 } { + set default [lindex $name 1] + set name [lindex $name 0] + } else { + if { [info exists default] } { + unset default + } + } + if { [llength [split $name ":"]] > 1 } { + set filters [split [lindex [split $name ":"] 1] ,] + set name [lindex [split $name ":"] 0] + } else { + set filters [list] + } + if { [info exists filter($name)] } { + upvar __filter_$name var + set var $filter($name) + + if { [lsearch -exact $filters "integer"] != -1 && ![empty_string_p $var]} { + validate_integer $name $var + } + } elseif { [info exists default] } { + upvar __filter_$name var + set var $default + } + # also upvar it under its real name + upvar __filter_$name __filter_$name + } + + + if { ![info exists __filter_status] } { + if { [info exists __filter_actionby] } { + set __filter_status "" + } else { + set __filter_status "open" + } + } + + if { ![empty_string_p $__filter_status] } { + lappend where_clauses "b.status = :__filter_status" + set human_readable_filter "All $__filter_status bugs" + } else { + lappend where_clauses "b.status != 'closed'" + set human_readable_filter "All open and resolved bugs" + } + + if { [info exists __filter_bug_type] } { + lappend where_clauses "b.bug_type = :__filter_bug_type" + append human_readable_filter " of type [bug_tracker::bug_type_pretty $__filter_bug_type]" + } + + if { [info exists __filter_assignee] } { + if { [empty_string_p $__filter_assignee] } { + lappend where_clauses "b.assignee is null" + append human_readable_filter " that are unassigned" + } else { + lappend where_clauses "b.assignee = :__filter_assignee" + if { $__filter_assignee == [ad_conn user_id] } { + append human_readable_filter " assigned to me" + } else { + append human_readable_filter " assigned to [db_string assignee_name { select first_names || ' ' || last_name from cc_users where user_id = :__filter_assignee }]" + } + } + } + + if { [info exists __filter_actionby] } { + lappend where_clauses "((b.status = 'open' and b.assignee = :__filter_actionby) or (b.status = 'resolved' and o.creation_user = :__filter_actionby))" + if { $__filter_actionby == [ad_conn user_id] } { + append human_readable_filter " awaiting action by me" + } else { + append human_readable_filter " awaiting action by [db_string actionby_name { select first_names || ' ' || last_name from cc_users where user_id = :__filter_actionby }]" + } + } + + if { [info exists __filter_severity] } { + lappend where_clauses "b.severity = :__filter_severity" + append human_readable_filter " where severity is [db_string severity_name { select severity_name from bt_severity_codes where severity_id = :__filter_severity }]" + } + + if { [info exists __filter_priority] } { + lappend where_clauses "b.priority = :__filter_priority" + append human_readable_filter " with a priority of [db_string priority_name { select priority_name from bt_priority_codes where priority_id = :__filter_priority }]" + } + + if { ![empty_string_p [conn component_id]] } { + set __filter_component_id [conn component_id] + } + + if { [info exists __filter_component_id] } { + lappend where_clauses "b.component_id = :__filter_component_id" + append human_readable_filter " in [db_string component_name { select component_name from bt_components where component_id = :__filter_component_id }]" + conn -set component_id $__filter_component_id + } + + if { [info exists __filter_fix_for_version] } { + if { [empty_string_p $__filter_fix_for_version] } { + lappend where_clauses "b.fix_for_version is null" + append human_readable_filter " where fix for version is undecided" + } else { + lappend where_clauses "b.fix_for_version = :__filter_fix_for_version" + append human_readable_filter " to be fixed in version [db_string version_name { select version_name from bt_versions where version_id = :__filter_fix_for_version }]" + } + } + + switch -exact -- $__filter_orderby { + severity { + set order_by_clause "sc.sort_order, b.bug_number desc" + append human_readable_filter ", most severe bugs first" + } + priority { + set order_by_clause "pc.sort_order, b.bug_number desc" + append human_readable_filter ", highest priority bugs first" + } + default { + set order_by_clause "b.bug_number desc" + } + } + + conn -set filter [array get filter] + conn -set filter_human_readable $human_readable_filter + conn -set filter_where_clauses $where_clauses + conn -set filter_order_by_clause $order_by_clause + } + + ad_proc context_bar { args } { + Context bar that takes the component information into account + } { + set component_id [conn component_id] + if { ![empty_string_p $component_id] } { + db_1row component_name { + select component_name, url_name from bt_components where component_id = :component_id + } + if { [llength $args] == 0 } { + return [eval ad_context_bar [list $component_name]] + } else { + return [eval ad_context_bar [list [list "[ad_conn package_url]com/$url_name/" $component_name]] $args] + } + } else { + return [eval ad_context_bar $args] + } + } + } Index: openacs-4/packages/bug-tracker/www/bug-add.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/www/bug-add.tcl,v diff -u -r1.5 -r1.6 --- openacs-4/packages/bug-tracker/www/bug-add.tcl 10 Sep 2002 22:22:28 -0000 1.5 +++ openacs-4/packages/bug-tracker/www/bug-add.tcl 11 Sep 2002 14:03:25 -0000 1.6 @@ -1,20 +1,16 @@ ad_page_contract { - Bug listing page. The most complicated page in the package. + Bug add page. @author Lars Pind (lars@pinds.com) @creation-date 2002-03-25 @cvs-id $Id$ } { cancel:optional - component_id:optional {return_url ""} } if { [empty_string_p $return_url] } { set return_url "." - if { [info exists component_id] } { - append return_url "?[export_vars { component_id }]" - } } # If the user hit cancel, ignore everything else @@ -35,7 +31,7 @@ set page_title "New Bug" -set context_bar [ad_context_bar $page_title] +set context_bar [bug_tracker::context_bar $page_title] set user_id [ad_conn user_id] @@ -116,8 +112,8 @@ element set_properties bug severity -value [bug_tracker::severity_get_default] element set_properties bug priority -value [bug_tracker::priority_get_default] - if { [info exists component_id] } { - element set_properties bug component_id -value $component_id + if { ![empty_string_p [bug_tracker::conn component_id]] } { + element set_properties bug component_id -value [bug_tracker::conn component_id] } element set_properties bug desc_format -value "plain" @@ -154,11 +150,14 @@ } - bug_tracker::bug_notify $bug_id "open" $description $desc_format + bug_tracker::bug_notify -bug_id $bug_id -action "open" -comment $description -comment_format $desc_format + # Sign up the submitter of the bug for instant alerts. We do this after calling + # the alert procedure so that the submitter isn't alerted about his own submittal of the bug + bug_tracker::add_instant_alert -bug_id $bug_id -user_id $user_id + ad_returnredirect $return_url ad_script_abort } ad_return_template - Index: openacs-4/packages/bug-tracker/www/bug.adp =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/www/bug.adp,v diff -u -r1.2 -r1.3 --- openacs-4/packages/bug-tracker/www/bug.adp 29 Aug 2002 13:28:36 -0000 1.2 +++ openacs-4/packages/bug-tracker/www/bug.adp 11 Sep 2002 14:03:25 -0000 1.3 @@ -1,7 +1,22 @@ @page_title@ @context_bar@ +@notification_link@ + + + + + +
+ +   @navlinks.label@   +   @navlinks.label@   +     + +
+
+

Index: openacs-4/packages/bug-tracker/www/bug.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/www/bug.tcl,v diff -u -r1.9 -r1.10 --- openacs-4/packages/bug-tracker/www/bug.tcl 10 Sep 2002 22:22:28 -0000 1.9 +++ openacs-4/packages/bug-tracker/www/bug.tcl 11 Sep 2002 14:03:25 -0000 1.10 @@ -14,9 +14,11 @@ cancel:optional close:optional {user_agent_p:boolean 0} + {show_patch_status "open"} + filter:array,optional } -set return_url "bug?[export_vars -url { bug_number }]" +set return_url "[ad_conn url]?[export_vars -url { bug_number filter:array }]" # If the user hit cancel, ignore everything else if { [exists_and_not_null cancel] } { @@ -74,6 +76,54 @@ set user_id [ad_conn user_id] + +# +# Filter management +# + +set filter_parsed [bug_tracker::parse_filters filter] + +if { [string equal $mode view] } { + + set human_readable_filter [bug_tracker::conn filter_human_readable] + set where_clauses [bug_tracker::conn filter_where_clauses] + set order_by_clause [bug_tracker::conn filter_order_by_clause] + + lappend where_clauses "b.project_id = :package_id" + + set filter_bug_numbers [db_list filter_bug_numbers " + select bug_number + from bt_bugs b join + acs_objects o on (object_id = bug_id) join + bt_priority_codes pc on (pc.priority_id = b.priority) join + bt_severity_codes sc on (sc.severity_id = b.severity) + where [join $where_clauses " and "] + order by $order_by_clause + "] + + set filter_bug_index [lsearch -exact $filter_bug_numbers $bug_number] + + multirow create navlinks url label + + if { $filter_bug_index != -1 } { + + if { $filter_bug_index > 0 } { + multirow append navlinks "bug?[export_vars { { bug_number {[lindex $filter_bug_numbers [expr $filter_bug_index -1]]} } filter:array }]" "<" + } else { + multirow append navlinks "" "<" + } + + multirow append navlinks "" "[expr $filter_bug_index+1] of [llength $filter_bug_numbers]" + + if { $filter_bug_index < [expr [llength $filter_bug_numbers]-1] } { + multirow append navlinks "bug?[export_vars { { bug_number {[lindex $filter_bug_numbers [expr $filter_bug_index +1]]} } filter:array }]" ">" + } else { + multirow append navlinks "" ">" + } + } +} + + # Create the form switch -- $mode { @@ -152,6 +202,11 @@ -options [bug_tracker::version_get_options -include_unknown] \ -optional +element create bug patches \ + -datatype text \ + -widget inform \ + -label [ad_decode $show_patch_status "open" "Open Patches (show all)" "all" "All Patches (show only open)" "Patches"] + if { $user_agent_p } { element create bug user_agent \ -datatype text \ @@ -215,6 +270,10 @@ -widget hidden \ -value $mode +foreach name [array names filter] { + element create bug filter.$name -datatype text -widget hidden -value $filter($name) +} + # If nothing else ... set page_title "Bug #$bug_number" @@ -313,6 +372,10 @@ -value [ad_decode [info exists field_editable_p(priority)] 1 $bug(priority) $bug(priority_pretty)] element set_properties bug found_in_version \ -value [ad_decode [info exists field_editable_p(found_in_version)] 1 $bug(found_in_version) $bug(found_in_version_name)] + + element set_properties bug patches \ + -value "[bug_tracker::get_patch_links -bug_id $bug(bug_id) -show_patch_status $show_patch_status]   \[ Upload a patch \]" + if { $user_agent_p } { element set_properties bug user_agent \ -value $bug(user_agent) @@ -378,10 +441,10 @@ set write_p [expr $write_p || ($bug(submitter_user_id) == [ad_conn user_id])] if { [string equal $mode "view"] && $write_p } { - set button_form_export_vars [export_vars -form { bug_number }] + set button_form_export_vars [export_vars -form { bug_number filter:array }] multirow create button name label - multirow append button "edit" "Edit" multirow append button "comment" "Comment" + multirow append button "edit" "Edit" switch -- $bug(status) { open { @@ -398,6 +461,18 @@ } } + # Notifications for a project. Provide a link for logged in users in view mode + if { [string equal $mode "view"] } { + + set notification_link [bug_tracker::get_notification_link \ + -type bug_tracker_project_notif \ + -object_id $bug(bug_id) \ + -url $return_url \ + -pretty_name "bug"] + } else { + set notification_link "" + } + if { ![string equal $mode "view"] && !$write_p } { ns_log notice "$bug(submitter_user_id) doesn't have write on object $bug(bug_id)" ad_return_forbidden "Security Violation" "

@@ -407,7 +482,6 @@
" ad_script_abort } - } set context_bar [ad_context_bar $page_title] @@ -471,7 +545,7 @@ } db_transaction { - set bug_id [db_string bug_id { select bug_id from bt_bugs where bug_number = :bug_number and project_id = :package_id }] + set bug_id [bug_tracker::get_bug_id -bug_number $bug_number -project_id $package_id] if { [llength $update_exprs] > 0 } { db_dml update_bug "update bt_bugs \n set [join $update_exprs ",\n "] \n where bug_id = :bug_id" @@ -490,9 +564,18 @@ values (:action_id, :bug_id, :mode, :resolution, :user_id, :description, :desc_format) } + + # Setup any assignee for alerts on the bug + if { [info exists assignee] && ![empty_string_p $assignee] } { + bug_tracker::add_instant_alert -bug_id $bug_id -user_id $assignee + } } - bug_tracker::bug_notify $bug_id $mode $description $desc_format $resolution + bug_tracker::bug_notify -bug_id $bug_id \ + -action $mode \ + -comment $description \ + -comment_format $desc_format \ + -resolution $resolution ad_returnredirect $return_url ad_script_abort Index: openacs-4/packages/bug-tracker/www/index.adp =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/www/index.adp,v diff -u -r1.1 -r1.2 --- openacs-4/packages/bug-tracker/www/index.adp 3 May 2002 16:29:59 -0000 1.1 +++ openacs-4/packages/bug-tracker/www/index.adp 11 Sep 2002 14:03:25 -0000 1.2 @@ -1,16 +1,17 @@ @project_name@ @context_bar@ +@notification_link@ + + + + @displaymode_form_export_vars@ + + + + + Index: openacs-4/packages/bug-tracker/www/index.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/www/index.tcl,v diff -u -r1.9 -r1.10 --- openacs-4/packages/bug-tracker/www/index.tcl 10 Sep 2002 22:22:28 -0000 1.9 +++ openacs-4/packages/bug-tracker/www/index.tcl 11 Sep 2002 14:03:25 -0000 1.10 @@ -5,15 +5,7 @@ @creation-date 2002-03-20 @cvs-id $Id$ } { - status:optional - bug_type:optional - fix_for_version:integer,optional - severity:integer,optional - priority:integer,optional - assignee:integer,optional - component_id:integer,optional - actionby:integer,optional - {orderby ""} + filter:optional,array } ad_require_permission [ad_conn package_id] read @@ -22,10 +14,20 @@ set package_id [ad_conn package_id] set package_key [ad_conn package_key] -set context_bar [ad_context_bar] +if { [info exists filter] } { + if { [array names filter] == [list "actionby"] && $filter(actionby) == [ad_conn user_id] } { + set context_bar [bug_tracker::context_bar "My bugs"] + } else { + set context_bar [bug_tracker::context_bar "Filtered bug list"] + } +} else { + set context_bar [bug_tracker::context_bar] +} set admin_p [ad_permission_p [ad_conn package_id] admin] +set return_url "[ad_conn url][ad_decode [ad_conn query] "" "" "?[ad_conn query]"]" + set num_components [db_string num_components { select count(component_id) from bt_components where project_id = :package_id }] if { $num_components == 0 } { @@ -40,92 +42,58 @@ return } +set user_id [ad_conn user_id] + +# Notifications for a project. Provide a link for logged in users +set notification_link [bug_tracker::get_notification_link \ + -type bug_tracker_project_notif \ + -object_id $package_id \ + -url $return_url \ + -pretty_name "project"] + # # Filter management # -set where_clauses [list] +set filter_parsed [bug_tracker::parse_filters filter] -set filter_vars { status fix_for_version assignee component_id } +set human_readable_filter [bug_tracker::conn filter_human_readable] +set where_clauses [bug_tracker::conn filter_where_clauses] +set order_by_clause [bug_tracker::conn filter_order_by_clause] -if { ![info exists status] } { - if { [info exists actionby] } { - set status "" - } else { - set status "open" - } -} +lappend where_clauses "b.project_id = :package_id" -lappend where_clauses "b.status = :status" -set human_readable_filter "All $status bugs" - -if { [info exists bug_type] } { - lappend where_clauses "b.bug_type = :bug_type" - append human_readable_filter " of type [bug_tracker::bug_type_pretty $bug_type]" +if { [llength [array names filter]] > 0 } { + set clear_url [ad_conn package_url] } -if { [info exists assignee] } { - if { [empty_string_p $assignee] } { - lappend where_clauses "b.assignee is null" - append human_readable_filter " that are unassigned" - } else { - lappend where_clauses "b.assignee = :assignee" - if { $assignee == [ad_conn user_id] } { - append human_readable_filter " assigned to me" - } else { - append human_readable_filter " assigned to [db_string assignee_name { select first_names || ' ' || last_name from cc_users where user_id = :assignee }]" - } - } -} +# +# Order by +# -if { [info exists actionby] } { - lappend where_clauses "((b.status = 'open' and b.assignee = :actionby) or (b.status = 'resolved' and o.creation_user = :actionby))" - if { $actionby == [ad_conn user_id] } { - append human_readable_filter " awaiting action by me" - } else { - append human_readable_filter " awaiting action by [db_string actionby_name { select first_names || ' ' || last_name from cc_users where user_id = :actionby }]" - } +if { [info exists filter(orderby)] } { + set save_orderby $filter(orderby) + unset filter(orderby) } - -if { [info exists severity] } { - lappend where_clauses "b.severity = :severity" - append human_readable_filter " where severity is [db_string severity_name { select severity_name from bt_severity_codes where severity_id = :severity }]" +set displaymode_form_export_vars [export_vars -form { filter:array }] +if { [info exists save_orderby] } { + set filter(orderby) $save_orderby + unset save_orderby } -if { [info exists priority] } { - lappend where_clauses "b.priority = :priority" - append human_readable_filter " with a priority of [db_string priority_name { select priority_name from bt_priority_codes where priority_id = :priority }]" -} - -if { [info exists component_id] } { - lappend where_clauses "b.component_id = :component_id" - append human_readable_filter " in [db_string component_name { select component_name from bt_components where component_id = :component_id }]" - bug_tracker::conn -set component_id $component_id -} - -if { [info exists fix_for_version] } { - if { [empty_string_p $fix_for_version] } { - lappend where_clauses "b.fix_for_version is null" - append human_readable_filter " where fix for version is undecided" +multirow create orderby value label selected_p +foreach value { "" severity priority } label { "Bug number" "Severity" "Priority" } { + if { [info exists filter(orderby)] && [string equal $filter(orderby) $value] } { + set selected_p 1 } else { - lappend where_clauses "b.fix_for_version = :fix_for_version" - append human_readable_filter " to be fixed in version [db_string version_name { select version_name from bt_versions where version_id = :fix_for_version }]" + set selected_p 0 } + multirow append orderby $value $label $selected_p } -switch -exact -- $orderby { - severity { - set order_by_clause "sc.sort_order, b.bug_number desc" - append human_readable_filter ", most severe bugs first" - } - priority { - set order_by_clause "pc.sort_order, b.bug_number desc" - append human_readable_filter ", highest priority bugs first" - } - default { - set order_by_clause "b.bug_number desc" - } -} +# +# Get bug list +# set truncate_len [ad_parameter "TruncateDescriptionLength" -default 200] @@ -181,9 +149,8 @@ and o.object_id = b.bug_id and pc.priority_id = b.priority and sc.severity_id = b.severity - and b.project_id = :package_id and submitter.user_id = o.creation_user - [ad_decode $where_clauses "" "" "and [join $where_clauses " and "]"] + and [join $where_clauses " and "] order by $order_by_clause " { set description_short [string_truncate -len $truncate_len [bug_tracker::bug_convert_comment_to_text -comment $description -format $desc_format]] @@ -195,9 +162,13 @@ set latest_estimate_pretty [ad_decode $latest_estimate_minutes "" "" 0 "" "$latest_estimate_minutes minutes"] set elapsed_time_pretty [ad_decode $elapsed_time_minutes "" "" 0 "" "$elapsed_time_minutes minutes"] set assignee_url [acs_community_member_url -user_id $assignee_user_id] - set bug_url "[ad_conn package_url]bug?[export_vars -url { bug_number }]" + set bug_url "bug?[export_vars { bug_number filter:array }]" } +# +# Get stats +# + db_multirow -extend { name name_url } by_status by_status { select b.status as unique_id, count(b.bug_id) as num_bugs @@ -207,7 +178,7 @@ order by bt_bug__status_sort_order(b.status) } { set name "[bug_tracker::status_pretty $unique_id] Bugs" - set name_url "[ad_conn package_url]?[export_vars -url { { status $unique_id } }]" + set name_url "?[export_vars { { filter.status $unique_id } }]" } db_multirow -extend { name name_url stat_name } stats stats_by_bug_type { @@ -221,7 +192,7 @@ } { set stat_name "Type of bug" set name [bug_tracker::bug_type_pretty $unique_id] - set name_url "[ad_conn package_url]?[export_vars -url { { bug_type $unique_id } }]" + set name_url "?[export_vars { { filter.bug_type $unique_id } }]" } db_multirow -extend { name_url stat_name } -append stats stats_by_fix_for_version { @@ -239,15 +210,10 @@ if { [empty_string_p $unique_id] } { set name "Undecided" } - set name_url "[ad_conn package_url]?[export_vars -url { { fix_for_version $unique_id } }]" + set name_url "?[export_vars { { filter.fix_for_version $unique_id } }]" } set stat_name_val "Severity" -if { ![string equal $orderby "severity"] } { - append stat_name_val " (order)" -} else { - append stat_name_val " (*)" -} db_multirow -extend { name_url stat_name } -append stats stats_by_severity { select b.severity as unique_id, @@ -261,15 +227,10 @@ order by name } { set stat_name $stat_name_val - set name_url "[ad_conn package_url]?[export_vars { { severity $unique_id } }]" + set name_url "?[export_vars { { filter.severity $unique_id } }]" } set stat_name_val "Priority" -if { ![string equal $orderby "priority"] } { - append stat_name_val " (order)" -} else { - append stat_name_val " (*)" -} db_multirow -extend { name_url stat_name } -append stats stats_by_priority { select b.priority as unique_id, @@ -283,7 +244,7 @@ order by name } { set stat_name $stat_name_val - set name_url "[ad_conn package_url]?[export_vars { { priority $unique_id } }]" + set name_url "?[export_vars { { filter.priority $unique_id } }]" } db_multirow -extend { name_url stat_name } -append stats stats_by_assignee { @@ -301,7 +262,7 @@ if { [empty_string_p $unique_id] } { set name "Unassigned" } - set name_url "[ad_conn package_url]?[export_vars -url { { assignee $unique_id } }]" + set name_url "?[export_vars -url { { filter.assignee $unique_id } }]" } db_multirow -extend { name_url stat_name } -append stats stats_by_actionby { @@ -317,11 +278,11 @@ order by name } { set stat_name "To Be Verified By" - set name_url "?[export_vars -url { { status resolved } { actionby $unique_id } }]" + set name_url "?[export_vars -url { { filter.status resolved } { filter.actionby $unique_id } }]" } db_multirow -extend { name_url stat_name } -append stats stats_by_component { - select b.component_id as unique_id, + select coalesce('com/'||c.url_name||'/', to_char(c.component_id,'99999999')) as unique_id, c.component_name as name, count(b.bug_id) as num_bugs from bt_bugs b left join @@ -332,7 +293,11 @@ order by name } { set stat_name "Components" - set name_url "[ad_conn package_url]?[export_vars -url { { component_id $unique_id } }]" + if { [string match "com/*" $unique_id] } { + set name_url "[ad_conn package_url]$unique_id" + } else { + set name_url "[ad_conn package_url]?[export_vars -url { { filter.component_id $unique_id } }]" + } } ad_return_template Index: openacs-4/packages/bug-tracker/www/map-patch-to-bugs.adp =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/www/map-patch-to-bugs.adp,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/bug-tracker/www/map-patch-to-bugs.adp 11 Sep 2002 14:03:25 -0000 1.1 @@ -0,0 +1,63 @@ + +@page_title@ +@context_bar@ + + +Select one or more of the following bugs for patch "@patch_summary@" (you may select more bugs later): + + +

+Components: @component_filter@ +

+ +

+Bug status: [ @open_filter@ ] +

+ +

+ +

+ +
+ +
+ +
- - @by_status.num_bugs@ @by_status.name@
-
-

- Open bugs summary: -

+

+ + @by_status.num_bugs@ @by_status.name@
+
+

Open bugs summary:

+
@@ -40,13 +41,39 @@
- Showing: @human_readable_filter@ + Showing: @human_readable_filter@ + (none1 bug@bugs:rowcount@ bugs) + (clear filters)
+
+
+ Order by: + + +
+ + + + + + + + + + + + + + + + + +
 Bug NumberSummaryCreation Date
@open_bugs.bug_number@@open_bugs.summary@@open_bugs.creation_date_pretty@
+ + + There are no open bugs to map the patch to. Try changing the component filter above. + +

+

+ +
+

+
+ +

+

+     + +
+

+
+ + Index: openacs-4/packages/bug-tracker/www/map-patch-to-bugs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/www/map-patch-to-bugs.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/bug-tracker/www/map-patch-to-bugs.tcl 11 Sep 2002 14:03:25 -0000 1.1 @@ -0,0 +1,114 @@ +ad_page_contract { + Page for viewing and editing one patch. + + @author Peter Marklund (peter@collaboraid.biz) + @date 2002-09-04 + @cvs-id $Id: map-patch-to-bugs.tcl,v 1.1 2002/09/11 14:03:25 lars Exp $ +} { + patch_number:integer,notnull + bug_number:integer,optional,multiple + component_id:integer,optional + {show_all_components_p "0"} + {show_only_open_p "1"} + {offset:integer "0"} + {interval_size "50"} + cancel:optional + {return_url ""} +} + +set package_id [ad_conn package_id] +set user_id [ad_conn user_id] +set redirect_url [ad_decode $return_url "" "patch?patch_number=$patch_number" $return_url] + +if { [exists_and_not_null cancel] } { + # The user chose to abort the mapping so redirect without further processing + ad_returnredirect $redirect_url + ad_script_abort +} + +set write_p [ad_permission_p $package_id write] +set user_is_submitter_p [expr $user_id == [bug_tracker::get_patch_submitter -patch_number $patch_number]] + +if { ![expr $user_is_submitter_p || $write_p] } { + ad_return_forbidden "Security Violation" "You do not have permission to map this patch to a bug. Only the submitter of the patch and users with write permission on this Bug Tracker project (package instance) may do so." + ad_script_abort +} + + +if { [exists_and_not_null bug_number] } { + # Do the mapping + foreach one_bug_number $bug_number { + set bug_id [db_string get_bug_id_for_number "select bug_id from bt_bugs where bug_number = :one_bug_number and project_id = :package_id"] + set patch_id [db_string get_patch_id_for_number "select patch_id from bt_patches where patch_number = :patch_number and project_id = :package_id"] + + bug_tracker::map_patch_to_bug -patch_id $patch_id -bug_id $bug_id + } + + ad_returnredirect $redirect_url + ad_script_abort +} + +set patch_summary [db_string get_patch_summary "select summary from bt_patches where patch_number = :patch_number and project_id = :package_id"] +set page_title "Mapping Patch #$patch_number \"$patch_summary\" to a Bug" +set context_bar [ad_context_bar "$page_title"] + +# Build the component filter +if { ![exists_and_not_null component_id] } { + set component_id [db_string component_id_for_patch "select component_id from bt_patches where patch_number = :patch_number and project_id = :package_id"] +} +set component_where_clause "" +set component_filter "" +if { ![empty_string_p component_id] } { + set component_name [db_string component_name "select component_name from bt_components where component_id = :component_id"] + set component_filter_url "map-patch-to-bugs?[export_vars -url {patch_number component_id return_url offset show_only_open_p interval_size}]" + if { $show_all_components_p } { + set component_filter "\[ Only Component \"$component_name\" | All Components \]" + } else { + set component_where_clause "\n and bt_bugs.component_id = :component_id" + + set component_filter "\[ Only Component \"$component_name\" | All Components \]" + } +} + +# Build the bug status filter +set open_filter_url "map-patch-to-bugs?[export_vars -url {patch_number component_id return_url offset show_all_components_p interval_size}]" +set only_open_label "Only Open Bugs" +set any_status_label "Bugs of any Status" +if { $show_only_open_p } { + set open_where_clause "and bt_bugs.status = 'open'" + set open_filter "$only_open_label | $any_status_label" +} else { + set open_where_clause "" + set open_filter "$only_open_label | $any_status_label" +} + +set sql_where_clause "bt_bugs.project_id = :package_id + $open_where_clause + $component_where_clause + and bt_bugs.bug_id not in (select bug_id + from bt_patch_bug_map + where patch_id = (select patch_id + from bt_patches + where patch_number = :patch_number + and project_id = :package_id + ) + )" + +# Build the pagination filter +set bug_count [db_string bug_count_for_mapping \ + "select count(*) + from bt_bugs + where $sql_where_clause"] +set pagination_export_var_set [ad_tcl_vars_to_ns_set patch_number component_id return_url show_all_components_p show_only_open_p] + +db_multirow open_bugs select_open_bugs \ + "select bt_bugs.bug_number, + bt_bugs.summary, + to_char(acs_objects.creation_date, 'fmMM/DDfm/YYYY') as creation_date_pretty + from bt_bugs, acs_objects + where bt_bugs.bug_id = acs_objects.object_id + and $sql_where_clause + order by acs_objects.creation_date desc + limit $interval_size offset $offset" + +ad_return_template Index: openacs-4/packages/bug-tracker/www/patch-add.adp =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/www/patch-add.adp,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/bug-tracker/www/patch-add.adp 11 Sep 2002 14:03:25 -0000 1.1 @@ -0,0 +1,5 @@ + +@page_title@ +@context_bar@ + + Index: openacs-4/packages/bug-tracker/www/patch-add.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/www/patch-add.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/bug-tracker/www/patch-add.tcl 11 Sep 2002 14:03:25 -0000 1.1 @@ -0,0 +1,182 @@ +ad_page_contract { + Page with a form for adding a patch. If the page + is requested without a bug number then the user + will optionally be taken to a page where bugs + that the patch covers can be chosen. + + @author Peter Marklund (peter@collaboraid.biz) + @date 2002-09-10 + @cvs-id $Id: patch-add.tcl,v 1.1 2002/09/11 14:03:25 lars Exp $ +} { + bug_number:integer,optional + cancel:optional + component_id:optional + {return_url ""} +} + +# If the user hit cancel, ignore everything else +if { [exists_and_not_null cancel] } { + set bug_view_url "bug?[export_vars { bug_number }]" + ad_returnredirect $bug_view_url + ad_script_abort +} + +ad_require_permission [ad_conn package_id] create + +# User needs to be logged in here +ad_maybe_redirect_for_registration + +# Set some common bug-tracker variables +set project_name [bug_tracker::conn project_name] +set package_id [ad_conn package_id] +set package_key [ad_conn package_key] +set page_title "New Patch" +set context_bar [ad_context_bar $page_title] +set user_id [ad_conn user_id] + +# Create the form +form create patch -html { enctype multipart/form-data } + +element create patch patch_id \ + -datatype integer \ + -widget hidden + +element create patch component_id \ + -datatype integer \ + -widget select \ + -label "Component" \ + -options [bug_tracker::components_get_options] + +element create patch summary \ + -datatype text \ + -label "Summary" \ + -html { size 50 } + +element create patch description \ + -datatype text \ + -widget textarea \ + -label "Description" \ + -html { cols 50 rows 10 } \ + -optional + +element create patch description_format \ + -datatype text \ + -widget select \ + -label "Description format" \ + -options { { "Plain" plain } { "HTML" html } { "Preformatted" pre } } + +element create patch version_id \ + -datatype text \ + -widget select \ + -label "Generated from Version" \ + -options [bug_tracker::version_get_options -include_unknown] \ + -optional + +element create patch patch_file \ + -datatype filename \ + -widget file \ + -label "Patch file:" \ + +if { [exists_and_not_null bug_number] } { + # Export the bug number + element create patch bug_number \ + -datatype integer \ + -widget hidden + +} else { + # There is no bug number. + # Let the user indicate if he wants to select bugs that this + # patch covers if no bug number was supplied + element create patch select_bugs_p \ + -datatype text \ + -widget radio \ + -label "Choose Bugs for this Patch" \ + -options { {Yes 1} {No 0} } \ + -values { 1 } +} + + +if { [form is_request patch] } { + # Form requested + + if { [exists_and_not_null bug_number] } { + element set_properties patch bug_number -value $bug_number + } + + element set_properties patch patch_id -value [db_nextval "acs_object_id_seq"] + + element set_properties patch version_id \ + -value [db_string user_version { select user_version from bt_user_prefs where user_id = :user_id and project_id = :package_id }] + + if { [info exists component_id] } { + element set_properties patch component_id -value $component_id + } +} + +if { [form is_valid patch] } { + # Form submitted + + db_transaction { + + form get_values patch patch_id component_id summary description description_format version_id patch_file + + # Get the file contents as a string + set content [bug_tracker::get_uploaded_patch_file_content] + + set ip_address [ns_conn peeraddr] + + db_exec_plsql new_patch { + select bt_patch__new( + :patch_id, + :package_id, + :component_id, + :summary, + :description, + :description_format, + :content, + :version_id, + :user_id, + :ip_address + ) + } + + # Redirect to the view page for the created patch by default + if { [empty_string_p $return_url] } { + set patch_number [db_string patch_number_for_id "select patch_number + from bt_patches + where patch_id = :patch_id"] + + set redirect_url "patch?[export_vars { patch_number }]" + } else { + set redirect_url $return_url + } + + # Fetch any provided bug id to map the patch to + catch {set bug_number [element get_value patch bug_number]} + if { [info exists bug_number] } { + # There is a bug id provided - map it to the patch + set bug_id [bug_tracker::get_bug_id -bug_number $bug_number -project_id $package_id] + bug_tracker::map_patch_to_bug -patch_id $patch_id -bug_id $bug_id + + # Trigger notifications for the bug that we are mapping to + bug_tracker::bug_notify \ + -bug_id $bug_id \ + -action "patched" \ + -patch_summary $summary + + } else { + # No bug id provided so redirect to page for selecting bugs if the + # user wishes to go there + set select_bugs_p [element get_value patch select_bugs_p] + + if { $select_bugs_p } { + set redirect_url "map-patch-to-bugs?[export_vars -url { return_url patch_number component_id }]" + } + } + } + + ad_returnredirect $redirect_url + ad_script_abort +} + +ad_return_template Index: openacs-4/packages/bug-tracker/www/patch-list.adp =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/www/patch-list.adp,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/bug-tracker/www/patch-list.adp 11 Sep 2002 14:03:25 -0000 1.1 @@ -0,0 +1,40 @@ + +@page_title@ +@context_bar@ + +

+Component: [ @component_filter@ ] +

+ +

+Apply to version: [ @version_filter@ ] +

+ +

+ +

+ +
+ + + + + + + + + + + + + + + + +
Patch SummaryStatusCreation Date
@patch_list.summary@@patch_list.status@@patch_list.creation_date_pretty@
+ + +No patches match these criteria. + + +
Index: openacs-4/packages/bug-tracker/www/patch-list.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/www/patch-list.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/bug-tracker/www/patch-list.tcl 11 Sep 2002 14:03:25 -0000 1.1 @@ -0,0 +1,100 @@ +ad_page_contract { + Page that lists patches in this Bug Tracker + project. + + @author Peter Marklund (peter@collaboraid.biz) + @date 2002-09-10 + @cvs-id $Id: patch-list.tcl,v 1.1 2002/09/11 14:03:25 lars Exp $ +} { + {component_id:integer ""} + {version_id:integer ""} + {offset:integer "0"} + {interval_size "50"} +} + +set package_id [ad_conn package_id] +set user_id [ad_conn user_id] + +set page_title "Patch List" +set context_bar [ad_context_bar $page_title] + +# Create the component filter +set component_filter_list [list] +if { [empty_string_p $component_id] } { + lappend component_filter_list "All Components" +} else { + lappend component_filter_list "All Components" +} +db_foreach components_for_patches { + select bt_components.component_id as loop_component_id, + bt_components.component_name + from bt_components + where exists (select 1 from bt_patches + where bt_patches.component_id = bt_components.component_id) +} { + + if { $component_id == $loop_component_id } { + lappend component_filter_list "$component_name" + } else { + lappend component_filter_list "$component_name" + } +} +set component_filter [join $component_filter_list " | "] +if { ![empty_string_p $component_id] } { + set component_where_clause "and bt_patches.component_id = :component_id" +} else { + set component_where_clause "" +} + + +# Create the apply to version filter +set version_filter_list [list] +if { [empty_string_p $version_id] } { + lappend version_filter_list "All Versions" +} else { + lappend version_filter_list "All Versions" +} +db_foreach versions_for_patches { + select version_id as loop_version_id, + version_name + from bt_versions + where exists (select 1 from bt_patches + where apply_to_version = bt_versions.version_id) +} { + + if { $version_id == $loop_version_id } { + lappend version_filter_list "$version_name" + } else { + lappend version_filter_list "$version_name" + } +} +set version_filter [join $version_filter_list " | "] +if { ![empty_string_p $version_id] } { + set version_where_clause " and bt_patches.apply_to_version = :version_id" +} else { + set version_where_clause "" +} + + +# Create the pagination filter +set where_clause "bt_patches.project_id = :package_id + $component_where_clause + $version_where_clause" +set patch_count [db_string patch_count "select count(*) + from bt_patches + where $where_clause"] +set pagination_export_var_set [ad_tcl_vars_to_ns_set component_id version_id] + +db_multirow patch_list patch_list " + select bt_patches.patch_number, + bt_patches.summary, + bt_patches.status, + to_char(acs_objects.creation_date, 'fmMM/DDfm/YYYY') as creation_date_pretty + from bt_patches, + acs_objects + where bt_patches.patch_id = acs_objects.object_id + and $where_clause + order by acs_objects.creation_date desc +" + +ad_return_template Index: openacs-4/packages/bug-tracker/www/patch.adp =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/www/patch.adp,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/bug-tracker/www/patch.adp 11 Sep 2002 14:03:25 -0000 1.1 @@ -0,0 +1,45 @@ + +@page_title@ +@context_bar@ + + + +

+ +

+
+ @button_form_export_vars@ + + + +
+
+ +

+ + + +

+ + + + +
+
<%= [template::util::quote_html "$patch(content)"] %>
+
+

+ +
+

+Download patch content +

+
+ +
+ + + + + + + Index: openacs-4/packages/bug-tracker/www/patch.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/www/patch.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/bug-tracker/www/patch.tcl 11 Sep 2002 14:03:25 -0000 1.1 @@ -0,0 +1,529 @@ +ad_page_contract { + Page for viewing and editing one patch. + + @author Peter Marklund (peter@collaboraid.biz) + @date 2002-09-04 + @cvs-id $Id: patch.tcl,v 1.1 2002/09/11 14:03:25 lars Exp $ +} { + patch_number:integer,notnull + mode:optional + cancel_edit:optional + edit:optional + accept:optional + refuse:optional + delete:optional + reopen:optional + comment:optional + download:optional +} + +# Assert read permission (should this check be in the request processor?) +ad_require_permission [ad_conn package_id] read + +# Initialize variables related to the request that we'll need +set package_id [ad_conn package_id] +set user_id [ad_conn user_id] +# Does the user have write privilege on the project? +set write_p [ad_permission_p $package_id write] +set user_is_submitter_p [expr $user_id == [bug_tracker::get_patch_submitter -patch_number $patch_number]] +set write_or_submitter_p [expr $write_p || $user_is_submitter_p] +set project_name [bug_tracker::conn project_name] +set package_key [ad_conn package_key] +set view_patch_url "[ad_conn url]?[export_vars -url { patch_number }]" +set patch_status [db_string patch_status "select status from bt_patches where patch_number = :patch_number and project_id = :package_id"] + +# Abort editing and return to view mode if the user hit cancel on the edit form +if { [exists_and_not_null cancel_edit] } { + ad_returnredirect $view_patch_url + ad_script_abort +} + +# If the download link was clicked - return the text content of the patch +if { [exists_and_not_null download] } { + + set patch_content [db_string get_patch_content "select content from bt_patches where patch_number = :patch_number and project_id = :package_id"] + + doc_return 200 "text/plain" $patch_content + ad_script_abort +} + +# Initialize the page mode variable +# We are in view mode per default +if { ![info exists mode] } { + if { [exists_and_not_null edit] } { + set mode "edit" + } elseif { [exists_and_not_null accept] } { + set mode "accept" + } elseif { [exists_and_not_null refuse] } { + set mode "refuse" + } elseif { [exists_and_not_null delete] } { + set mode "delete" + } elseif { [exists_and_not_null reopen] } { + set mode "reopen" + } elseif { [exists_and_not_null comment] } { + set mode "comment" + } else { + set mode "view" + } +} + +# Specify which fields in the form are editable +# And check that the user is permitted to take the chosen action +switch -- $mode { + edit { + if { ![expr $write_p || $user_is_submitter_p] } { + ad_return_forbidden "Serurity Violation" "You do not have permission to edit this patch. Only the submitter of the patch and users with write permission on the Bug Tracker project (package instance) may do so." + ad_script_abort + } + + set edit_fields {component_id summary generated_from_version apply_to_version} + } + accept { + ad_require_permission $package_id write + + # The user should indicate which version the patch is applied to + set edit_fields { applied_to_version } + } + refuse { + ad_require_permission $package_id write + + set edit_fields {} + } + reopen { + # User must have write permission to reopen a refused patch + if { [string equal $patch_status "refused"] && !$write_p } { + ad_return_forbidden "Serurity Violation" "You do not have permission to reopen this refused patch, only users with write permission on the Bug Tracker package instance (project) may do so." + ad_script_abort + } elseif { [string equal $patch_status "deleted"] && !($user_is_submitter_p || $write_p)} { + ad_return_forbidden "Serurity Violation" "You do not have permission to reopen this deleted patch, only users with write permission on the Bug Tracker package instance (project) and the submitter of the patch may do so." + ad_script_abort + } + + set edit_fields {} + } + delete { + # Only the submitter can delete a patch (admins can refuse it) + if { !$user_is_submitter_p } { + ad_return_forbidden "Serurity Violation" "You do not have permission to cancel this patch - only the submitter of the patch may do so. If you are an administrator you can however refuse the patch." + ad_script_abort + } + set edit_fields {} + } + comment { + set edit_fields {} + } + view { + set edit_fields {} + } +} + +foreach field $edit_fields { + set field_editable_p($field) 1 +} + +if { ![string equal $mode "view"] } { + ad_maybe_redirect_for_registration +} + +# Create the form +switch -- $mode { + view { + form create patch -has_submit 1 + } + default { + form create patch -html { enctype multipart/form-data } + } +} + +# Create the elements of the form +element create patch patch_number \ + -datatype integer \ + -widget hidden + +element create patch patch_number_i \ + -datatype integer \ + -widget inform \ + -label "Patch #" + +element create patch component_id \ + -datatype integer \ + -widget [ad_decode [info exists field_editable_p(component_id)] 1 select inform] \ + -label "Component" \ + -options [bug_tracker::components_get_options] + +if { [string equal $mode "view"] } { + element create patch fixes_bugs \ + -datatype text \ + -widget inform \ + -label "Fix for Bugs" +} + +element create patch summary \ + -datatype text \ + -widget [ad_decode [info exists field_editable_p(summary)] 1 text inform] \ + -label "Summary" \ + -html { size 50 } + +element create patch submitter \ + -datatype text \ + -widget inform \ + -label "Submitted by" + +element create patch status \ + -widget inform \ + -datatype text \ + -label "Status" + +element create patch generated_from_version \ + -datatype text \ + -widget [ad_decode [info exists field_editable_p(generated_from_version)] 1 select inform] \ + -label "Generated from Version" \ + -options [bug_tracker::version_get_options -include_unknown] \ + -optional + +element create patch apply_to_version \ + -datatype text \ + -widget [ad_decode [info exists field_editable_p(apply_to_version)] 1 select inform] \ + -label "Apply to Version" \ + -options [bug_tracker::version_get_options -include_undecided] \ + -optional + +element create patch applied_to_version \ + -datatype text \ + -widget [ad_decode [info exists field_editable_p(applied_to_version)] 1 select inform] \ + -label "Applied to Version" \ + -options [bug_tracker::version_get_options -include_undecided] \ + -optional + +switch -- $mode { + edit - comment - accept - refuse - reopen - delete { + element create patch description \ + -datatype text \ + -widget comment \ + -label "Description" \ + -html { cols 60 rows 13 } \ + -optional + + element create patch desc_format \ + -datatype text \ + -widget select \ + -label "Description format" \ + -options { { "Plain" plain } { "HTML" html } { "Preformatted" pre } } + + } + default { + # View mode + element create patch description \ + -datatype text \ + -widget inform \ + -label "Description" + } +} + +# In accept mode - give the user the ability to select associated +# bugs to be resolved +if { [string equal $mode "accept"] } { + + element create patch resolve_bugs \ + -datatype integer \ + -widget checkbox \ + -label "Resolve Bugs" \ + -options [bug_tracker::get_mapped_bugs -patch_number $patch_number -only_open_p 1] \ + -optional +} + +if { [string equal $mode "edit"] } { + # Edit mode - display the file upload widget for patch content + element create patch content \ + -datatype filename \ + -widget file \ + -label "Patch file (leave blank to keep current file):" \ + -optional +} + +element create patch mode \ + -datatype text \ + -widget hidden \ + -value $mode + +set page_title "Patch #$patch_number" +set context_bar [ad_context_bar $page_title] + +if { [form is_request patch] } { + # The form was requested + + db_1row patch { + select bt_patches.patch_id, + bt_patches.patch_number, + bt_patches.project_id, + bt_patches.component_id, + bt_patches.summary, + bt_patches.content, + bt_patches.generated_from_version, + bt_patches.apply_to_version, + bt_patches.applied_to_version, + bt_patches.status, + bt_components.component_name, + acs_objects.creation_user as submitter_user_id, + submitter.first_names as submitter_first_names, + submitter.last_name as submitter_last_name, + submitter.email as submitter_email, + acs_objects.creation_date, + to_char(acs_objects.creation_date, 'fmMM/DDfm/YYYY') as creation_date_pretty, + coalesce((select version_name + from bt_versions + where bt_versions.version_id = bt_patches.generated_from_version), 'Unknown') as generated_from_version_name, + coalesce((select version_name + from bt_versions + where bt_versions.version_id = bt_patches.apply_to_version), 'Unknown') as apply_to_version_name, + coalesce((select version_name + from bt_versions + where bt_versions.version_id = bt_patches.applied_to_version), 'Unknown') as applied_to_version_name, + to_char(now(), 'fmMM/DDfm/YYYY') as now_pretty + from bt_patches, + acs_objects, + cc_users submitter, + bt_components + + where bt_patches.patch_number = :patch_number + and bt_patches.project_id = :package_id + and bt_patches.patch_id = acs_objects.object_id + and bt_patches.component_id = bt_components.component_id + and submitter.user_id = acs_objects.creation_user + + } -column_array patch + + # When the user is taking an action that should change the status of the patch + # - update the status (the new status will show up in the form) + switch -- $mode { + accept { + set patch(status) "accepted" + } + refuse { + set patch(status) "refused" + } + delete { + set patch(status) "deleted" + } + reopen { + set patch(status) "open" + } + } + + element set_properties patch patch_number \ + -value $patch(patch_number) + element set_properties patch patch_number_i \ + -value $patch(patch_number) + element set_properties patch component_id \ + -value [ad_decode [info exists field_editable_p(component_id)] 1 $patch(component_id) $patch(component_name)] + if { [string equal $mode "view"] } { + set map_new_bug_link [ad_decode $write_or_submitter_p "1" "\[ Map to bugs \]" ""] + element set_properties patch fixes_bugs \ + -value "[bug_tracker::get_bug_links -patch_id $patch(patch_id) -patch_number $patch(patch_number) -write_or_submitter_p $write_or_submitter_p] $map_new_bug_link" + } + element set_properties patch summary \ + -value [ad_decode [info exists field_editable_p(summary)] 1 $patch(summary) "$patch(summary)"] + element set_properties patch submitter \ + -value " + [acs_community_member_link -user_id $patch(submitter_user_id) \ + -label "$patch(submitter_first_names) $patch(submitter_last_name)"] + ($patch(submitter_email))" + + element set_properties patch status \ + -value [ad_decode [info exists field_editable_p(status)] 1 $patch(status) [bug_tracker::patch_status_pretty $patch(status)]] + + element set_properties patch generated_from_version \ + -value [ad_decode [info exists field_editable_p(generated_from_version)] 1 $patch(generated_from_version) $patch(generated_from_version_name)] + element set_properties patch apply_to_version \ + -value [ad_decode [info exists field_editable_p(apply_to_version)] 1 $patch(apply_to_version) $patch(apply_to_version_name)] + element set_properties patch applied_to_version \ + -value [ad_decode [info exists field_editable_p(applied_to_version)] 1 $patch(applied_to_version) $patch(applied_to_version_name)] + + if { ( [string equal $patch(status) open] && ![string equal $mode "accept"]) || [string equal $patch(status) "refused"] } { + element set_properties patch applied_to_version -widget hidden + } + + # Description/Actions/History + set patch_id $patch(patch_id) + set action_html "" + db_foreach actions { + select bt_patch_actions.action_id, + bt_patch_actions.action, + bt_patch_actions.actor as actor_user_id, + actor.first_names as actor_first_names, + actor.last_name as actor_last_name, + actor.email as actor_email, + bt_patch_actions.action_date, + to_char(bt_patch_actions.action_date, 'fmMM/DDfm/YYYY') as action_date_pretty, + bt_patch_actions.comment, + bt_patch_actions.comment_format + from bt_patch_actions, + cc_users actor + where bt_patch_actions.patch_id = :patch_id + and actor.user_id = bt_patch_actions.actor + order by action_date + } { + append action_html "$action_date_pretty [bug_tracker::patch_action_pretty $action] by $actor_first_names $actor_last_name +
[bug_tracker::bug_convert_comment_to_html -comment $comment -format $comment_format]
" + } + + if { [string equal $mode "view"] } { + element set_properties patch description -value $action_html + } else { + element set_properties patch description \ + -history $action_html \ + -header "$patch(now_pretty) [bug_tracker::patch_action_pretty $mode] by [bug_tracker::conn user_first_names] [bug_tracker::conn user_last_name]" \ + -value "" + } + + # Now that we have the patch summary we can make the page title more informative + set page_title "Patch #$patch_number: $patch(summary)" + + # Create the buttons + # If the user has submitted the patch he gets full write access on the patch + set user_is_submitter_p [expr $patch(submitter_user_id) == [ad_conn user_id]] + if { [string equal $mode "view"] } { + set button_form_export_vars [export_vars -form { patch_number }] + multirow create button name label + + if { $write_p || $user_is_submitter_p } { + multirow append button "comment" "Comment" + multirow append button "edit" "Edit" + } + + switch -- $patch(status) { + open { + if { $write_p } { + multirow append button "accept" "Accept" + multirow append button "refuse" "Refuse" + } + + # Only the submitter can cancel the patch + if { $user_is_submitter_p } { + multirow append button "delete" "Delete" + } + } + accepted { + if { $write_p } { + multirow append button "reopen" "Reopen" + } + } + refused { + if { $write_p } { + multirow append button "reopen" "Reopen" + } + } + deleted { + if { $write_p || $user_is_submitter_p } { + multirow append button "reopen" "Reopen" + } + } + } + } + + # Check that the user is permitted to change the patch + if { ![string equal $mode "view"] && !$write_p && !$user_is_submitter_p } { + ns_log notice "$patch(submitter_user_id) doesn't have write on object $patch(patch_id)" + ad_return_forbidden "Security Violation" "
+ You don't have permission to edit this patch. +
+ This incident has been logged. +
" + ad_script_abort + } +} + +if { [form is_valid patch] } { + # A valid submit of the form + + set update_exprs [list] + + form get_values patch patch_number + + foreach column $edit_fields { + set $column [element get_value patch $column] + lappend update_exprs "$column = :$column" + } + + switch -- $mode { + accept { + set status "accepted" + lappend update_exprs "status = :status" + } + refuse { + set status "refused" + lappend update_exprs "status = :status" + } + reopen { + set status "open" + lappend update_exprs "status = :status" + } + edit { + # Get the contents of any new uploaded patch file + set content [bug_tracker::get_uploaded_patch_file_content] + + if { ![empty_string_p $content] } { + lappend update_exprs "content = :content" + } + } + delete { + set status "deleted" + lappend update_exprs "status = :status" + } + } + + db_transaction { + set patch_id [db_string patch_id "select patch_id from bt_patches where patch_number = :patch_number and project_id = :package_id"] + + if { [llength $update_exprs] > 0 } { + db_dml update_patch "update bt_patches \n set [join $update_exprs ",\n "] \n where patch_id = :patch_id" + } + + set action_id [db_nextval "acs_object_id_seq"] + + foreach column { description desc_format } { + set $column [element get_value patch $column] + } + + db_dml patch_action { + insert into bt_patch_actions + (action_id, patch_id, action, actor, comment, comment_format) + values + (:action_id, :patch_id, :mode, :user_id, :description, :desc_format) + } + + if { [string equal $mode "accept"] } { + # Resolve any bugs that the user selected + set resolve_bugs [element get_values patch resolve_bugs] + + foreach bug_number $resolve_bugs { + + db_transaction { + set bug_id [bug_tracker::get_bug_id -bug_number $bug_number -project_id $package_id] + + db_dml resolve_bug { + update bt_bugs + set status = 'resolved' + where bug_id = :bug_id + } + + set bug_action_id [db_nextval "acs_object_id_seq"] + + set resolve_description "Fixed by patch #$patch_number" + + db_dml bug_action_resolve { + insert into bt_bug_actions + (action_id, bug_id, action, resolution, actor, comment, comment_format) + values + (:bug_action_id, :bug_id, 'resolve', 'fixed', :user_id, :resolve_description, 'html') + } + } + } + } + } + + ad_returnredirect $view_patch_url + ad_script_abort +} + +ad_return_template Index: openacs-4/packages/bug-tracker/www/unmap-patch-from-bug.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/www/unmap-patch-from-bug.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/bug-tracker/www/unmap-patch-from-bug.tcl 11 Sep 2002 14:03:25 -0000 1.1 @@ -0,0 +1,25 @@ +ad_page_contract { + Unmapping a patch from a bug. + + @author Peter Marklund (peter@collaboraid.biz) + @date 2002-09-06 + @cvs-id $Id: unmap-patch-from-bug.tcl,v 1.1 2002/09/11 14:03:25 lars Exp $ +} { + patch_number:integer,notnull + bug_number:integer,notnull +} + +set package_id [ad_conn package_id] +set user_id [ad_conn user_id] + +set write_p [ad_permission_p $package_id write] +set user_is_submitter_p [expr $user_id == [bug_tracker::get_patch_submitter -patch_number $patch_number]] + +if { ![expr $user_is_submitter_p || $write_p] } { + ad_return_forbidden "Security Violation" "You do not have permission to unmap a bug from this patch. Only the submitter of the patch and users with write permission on the Bug Tracker package instance (project) may do so." + ad_script_abort +} + +bug_tracker::unmap_patch_from_bug -patch_number $patch_number -bug_number $bug_number + +ad_returnredirect "patch?patch_number=$patch_number" Index: openacs-4/packages/bug-tracker/www/com/index.vuh =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/bug-tracker/www/com/index.vuh,v diff -u -r1.1 -r1.2 --- openacs-4/packages/bug-tracker/www/com/index.vuh 29 Aug 2002 15:15:13 -0000 1.1 +++ openacs-4/packages/bug-tracker/www/com/index.vuh 11 Sep 2002 14:03:26 -0000 1.2 @@ -1,14 +1,24 @@ -set extra_url_list [split [ad_conn extra_url] "/"] +if { ![regexp {com/([^/]+)/(.*)$} [ad_conn extra_url] match url_name rest] } { + # Try adding a slash + ad_returnredirect "[ad_conn url]/" + return +} -set url_name [lindex $extra_url_list 1] set package_id [ad_conn package_id] set found_p [db_0or1row component { select component_id from bt_components where project_id = :package_id and url_name = :url_name }] if { $found_p } { - rp_form_put component_id $component_id - rp_internal_redirect [file join / packages bug-tracker www index] + + if { [empty_string_p $rest] } { + set rest "index" + } + bug_tracker::conn -set component_id $component_id + + set redirect_to [file join / packages bug-tracker www $rest] + + rp_internal_redirect $redirect_to } else { ns_returnnotfound }