diff options
Diffstat (limited to 'extensions/BugModal/lib/ActivityStream.pm')
-rw-r--r-- | extensions/BugModal/lib/ActivityStream.pm | 567 |
1 files changed, 292 insertions, 275 deletions
diff --git a/extensions/BugModal/lib/ActivityStream.pm b/extensions/BugModal/lib/ActivityStream.pm index 098c5df33..a7983e85c 100644 --- a/extensions/BugModal/lib/ActivityStream.pm +++ b/extensions/BugModal/lib/ActivityStream.pm @@ -49,310 +49,324 @@ use Bugzilla::Constants; # ] sub activity_stream { - my ($self) = @_; - if (!$self->{activity_stream}) { - my $stream = []; - _add_comments_to_stream($self, $stream); - _add_activities_to_stream($self, $stream); - _add_duplicates_to_stream($self, $stream); - - my $base_time = date_str_to_time($self->creation_ts); - foreach my $change_set (@$stream) { - $change_set->{id} = $change_set->{comment} - ? 'c' . $change_set->{comment}->count - : 'a' . ($change_set->{time} - $base_time) . '_' . $change_set->{user_id}; - foreach my $activity (@{ $change_set->{activity} }) { - $activity->{changes} = [ - sort { $a->{fieldname} cmp $b->{fieldname} } - @{ $activity->{changes} } - ]; - } - } - my $order = Bugzilla->user->setting('comment_sort_order'); - if ($order eq 'oldest_to_newest') { - $self->{activity_stream} = [ sort { $a->{time} <=> $b->{time} } @$stream ]; - } - elsif ($order eq 'newest_to_oldest') { - $self->{activity_stream} = [ sort { $b->{time} <=> $a->{time} } @$stream ]; - } - elsif ($order eq 'newest_to_oldest_desc_first') { - my $desc = shift @$stream; - $self->{activity_stream} = [ $desc, sort { $b->{time} <=> $a->{time} } @$stream ]; - } + my ($self) = @_; + if (!$self->{activity_stream}) { + my $stream = []; + _add_comments_to_stream($self, $stream); + _add_activities_to_stream($self, $stream); + _add_duplicates_to_stream($self, $stream); + + my $base_time = date_str_to_time($self->creation_ts); + foreach my $change_set (@$stream) { + $change_set->{id} + = $change_set->{comment} + ? 'c' . $change_set->{comment}->count + : 'a' . ($change_set->{time} - $base_time) . '_' . $change_set->{user_id}; + foreach my $activity (@{$change_set->{activity}}) { + $activity->{changes} + = [sort { $a->{fieldname} cmp $b->{fieldname} } @{$activity->{changes}}]; + } + } + my $order = Bugzilla->user->setting('comment_sort_order'); + if ($order eq 'oldest_to_newest') { + $self->{activity_stream} = [sort { $a->{time} <=> $b->{time} } @$stream]; } - return $self->{activity_stream}; + elsif ($order eq 'newest_to_oldest') { + $self->{activity_stream} = [sort { $b->{time} <=> $a->{time} } @$stream]; + } + elsif ($order eq 'newest_to_oldest_desc_first') { + my $desc = shift @$stream; + $self->{activity_stream} = [$desc, sort { $b->{time} <=> $a->{time} } @$stream]; + } + } + return $self->{activity_stream}; } sub find_activity_id_for_attachment { - my ($self, $attachment) = @_; - my $attach_id = $attachment->id; - my $stream = $self->activity_stream; - foreach my $change_set (@$stream) { - next unless exists $change_set->{attach_id}; - return $change_set->{id} if $change_set->{attach_id} == $attach_id; - } - return undef; + my ($self, $attachment) = @_; + my $attach_id = $attachment->id; + my $stream = $self->activity_stream; + foreach my $change_set (@$stream) { + next unless exists $change_set->{attach_id}; + return $change_set->{id} if $change_set->{attach_id} == $attach_id; + } + return undef; } sub find_activity_id_for_flag { - my ($self, $flag) = @_; - my $flagtype_name = $flag->type->name; - my $date = $flag->modification_date; - my $setter_id = $flag->setter->id; - my $stream = $self->activity_stream; - - # unfortunately bugs_activity treats all flag changes as the same field, so - # we don't have an object_id to match on - - if (!exists $self->{activity_cache}->{flag}->{$flag->id}) { - foreach my $change_set (reverse @$stream) { - foreach my $activity (@{ $change_set->{activity} }) { - # match by user, timestamp, and flag-type name - next unless - $activity->{who}->id == $setter_id - && $activity->{when} eq $date; - foreach my $change (@{ $activity->{changes} }) { - next unless - $change->{fieldname} eq 'flagtypes.name' - && $change->{flagtype_name} eq $flagtype_name; - $self->{activity_cache}->{flag}->{$flag->id} = $change_set->{id}; - return $change_set->{id}; - } - } + my ($self, $flag) = @_; + my $flagtype_name = $flag->type->name; + my $date = $flag->modification_date; + my $setter_id = $flag->setter->id; + my $stream = $self->activity_stream; + + # unfortunately bugs_activity treats all flag changes as the same field, so + # we don't have an object_id to match on + + if (!exists $self->{activity_cache}->{flag}->{$flag->id}) { + foreach my $change_set (reverse @$stream) { + foreach my $activity (@{$change_set->{activity}}) { + + # match by user, timestamp, and flag-type name + next unless $activity->{who}->id == $setter_id && $activity->{when} eq $date; + foreach my $change (@{$activity->{changes}}) { + next + unless $change->{fieldname} eq 'flagtypes.name' + && $change->{flagtype_name} eq $flagtype_name; + $self->{activity_cache}->{flag}->{$flag->id} = $change_set->{id}; + return $change_set->{id}; } - # if we couldn't find the flag in bugs_activity it means it was set - # during bug creation - $self->{activity_cache}->{flag}->{$flag->id} = 'c0'; + } } - return $self->{activity_cache}->{flag}->{$flag->id}; + + # if we couldn't find the flag in bugs_activity it means it was set + # during bug creation + $self->{activity_cache}->{flag}->{$flag->id} = 'c0'; + } + return $self->{activity_cache}->{flag}->{$flag->id}; } # comments are processed first, so there's no need to merge into existing entries sub _add_comment_to_stream { - my ($stream, $time, $user_id, $comment) = @_; - my $rh = { - time => $time, - user_id => $user_id, - comment => $comment, - activity => [], - }; - if ($comment->type == CMT_ATTACHMENT_CREATED || $comment->type == CMT_ATTACHMENT_UPDATED) { - $rh->{attach_id} = $comment->extra_data; - } - push @$stream, $rh; + my ($stream, $time, $user_id, $comment) = @_; + my $rh + = {time => $time, user_id => $user_id, comment => $comment, activity => [],}; + if ( $comment->type == CMT_ATTACHMENT_CREATED + || $comment->type == CMT_ATTACHMENT_UPDATED) + { + $rh->{attach_id} = $comment->extra_data; + } + push @$stream, $rh; } sub _add_activity_to_stream { - my ($stream, $time, $user_id, $data) = @_; - foreach my $entry (@$stream) { - next unless $entry->{time} == $time && $entry->{user_id} == $user_id; - $entry->{cc_only} = $entry->{cc_only} && $data->{cc_only}; - push @{ $entry->{activity} }, $data; - return; - } - push @$stream, { - time => $time, - user_id => $user_id, - comment => undef, - cc_only => $data->{cc_only}, - activity => [ $data ], + my ($stream, $time, $user_id, $data) = @_; + foreach my $entry (@$stream) { + next unless $entry->{time} == $time && $entry->{user_id} == $user_id; + $entry->{cc_only} = $entry->{cc_only} && $data->{cc_only}; + push @{$entry->{activity}}, $data; + return; + } + push @$stream, + { + time => $time, + user_id => $user_id, + comment => undef, + cc_only => $data->{cc_only}, + activity => [$data], }; } sub _add_comments_to_stream { - my ($bug, $stream) = @_; - my $user = Bugzilla->user; - my $treeherder_id = Bugzilla->treeherder_user->id; - - my $raw_comments = $bug->comments(); - foreach my $comment (@$raw_comments) { - next if $comment->type == CMT_HAS_DUPE; - my $author_id = $comment->author->id; - next if $comment->is_private && !($user->is_insider || $user->id == $author_id); - next if $comment->body eq '' && ($comment->work_time - 0) != 0 && $user->is_timetracker; - - # treeherder is so spammy we hide its comments by default - if ($author_id == $treeherder_id) { - $comment->{collapsed} = 1; - $comment->{collapsed_reason} = $comment->author->name; - } - if ($comment->type != CMT_ATTACHMENT_CREATED && $comment->count == 0 && length($comment->body) == 0) { - $comment->{collapsed} = 1; - $comment->{collapsed_reason} = 'empty'; - } - # If comment type is resolved as duplicate, do not add '...marked as duplicate...' string to comment body - if ($comment->type == CMT_DUPE_OF) { - $comment->set_type(0); - # Skip if user did not supply comment also - next if $comment->body eq ''; - } - - _add_comment_to_stream($stream, date_str_to_time($comment->creation_ts), $comment->author->id, $comment); + my ($bug, $stream) = @_; + my $user = Bugzilla->user; + my $treeherder_id = Bugzilla->treeherder_user->id; + + my $raw_comments = $bug->comments(); + foreach my $comment (@$raw_comments) { + next if $comment->type == CMT_HAS_DUPE; + my $author_id = $comment->author->id; + next if $comment->is_private && !($user->is_insider || $user->id == $author_id); + next + if $comment->body eq '' + && ($comment->work_time - 0) != 0 + && $user->is_timetracker; + + # treeherder is so spammy we hide its comments by default + if ($author_id == $treeherder_id) { + $comment->{collapsed} = 1; + $comment->{collapsed_reason} = $comment->author->name; } -} - -sub _add_activities_to_stream { - my ($bug, $stream) = @_; - my $dbh = Bugzilla->dbh; - my $user = Bugzilla->user; - - # build bug activity - my ($raw_activity) = $bug->can('get_activity') - ? $bug->get_activity() - : Bugzilla::Bug::GetBugActivity($bug->id); - - # allow other extensions to alter history - Bugzilla::Hook::process('inline_history_activitiy', { activity => $raw_activity }); - - my %attachment_cache; - foreach my $attachment (@{$bug->attachments}) { - $attachment_cache{$attachment->id} = $attachment; + if ( $comment->type != CMT_ATTACHMENT_CREATED + && $comment->count == 0 + && length($comment->body) == 0) + { + $comment->{collapsed} = 1; + $comment->{collapsed_reason} = 'empty'; } - # build a list of bugs we need to check visibility of, so we can check with a single query - my %visible_bug_ids; +# If comment type is resolved as duplicate, do not add '...marked as duplicate...' string to comment body + if ($comment->type == CMT_DUPE_OF) { + $comment->set_type(0); - # envelope, augment and tweak - foreach my $operation (@$raw_activity) { - - # make operation.who an object - $operation->{who} = Bugzilla::User->new({ name => $operation->{who}, cache => 1 }); - - # we need to track operations which are just cc changes - $operation->{cc_only} = 1; - - for (my $i = 0; $i < scalar(@{$operation->{changes}}); $i++) { - my $change = $operation->{changes}->[$i]; - - # make an attachment object - if ($change->{attachid}) { - $change->{attach} = $attachment_cache{$change->{attachid}}; - } - - # empty resolutions are displayed as --- by default - # make it explicit here to enable correct display of the change - if ($change->{fieldname} eq 'resolution') { - $change->{removed} = '---' if $change->{removed} eq ''; - $change->{added} = '---' if $change->{added} eq ''; - } - - # make boolean fields true/false instead of 1/0 - my ($table, $field) = ('bugs', $change->{fieldname}); - if ($field =~ /^([^\.]+)\.(.+)$/) { - ($table, $field) = ($1, $2); - } - my $column = $dbh->bz_column_info($table, $field); - if ($column && $column->{TYPE} eq 'BOOLEAN') { - $change->{removed} = ''; - $change->{added} = $change->{added} ? 'true' : 'false'; - } - - # load field object (only required for custom fields), and set the - # field type for custom fields - my $field_obj; - if ($change->{fieldname} =~ /^cf_/) { - $field_obj = Bugzilla::Field->new({ name => $change->{fieldname}, cache => 1 }); - $change->{fieldtype} = $field_obj->type; - } + # Skip if user did not supply comment also + next if $comment->body eq ''; + } - # identify buglist changes - if ($change->{fieldname} eq 'blocked' || - $change->{fieldname} eq 'dependson' || - $change->{fieldname} eq 'dupe' || - ($field_obj && $field_obj->type == FIELD_TYPE_BUG_ID) - ) { - $change->{buglist} = 1; - foreach my $what (qw(removed added)) { - my @buglist = split(/[\s,]+/, $change->{$what}); - foreach my $id (@buglist) { - if ($id && $id =~ /^\d+$/) { - $visible_bug_ids{$id} = 1; - } - } - } - } + _add_comment_to_stream($stream, date_str_to_time($comment->creation_ts), + $comment->author->id, $comment); + } +} - # split see-also - if ($change->{fieldname} eq 'see_also') { - my $url_base = Bugzilla->localconfig->{urlbase}; - foreach my $f (qw( added removed )) { - my @values; - foreach my $value (split(/, /, $change->{$f})) { - my ($bug_id) = substr($value, 0, length($url_base)) eq $url_base - ? $value =~ /id=(\d+)$/ - : undef; - push @values, { - url => $value, - bug_id => $bug_id, - }; - } - $change->{$f} = \@values; - } +sub _add_activities_to_stream { + my ($bug, $stream) = @_; + my $dbh = Bugzilla->dbh; + my $user = Bugzilla->user; + + # build bug activity + my ($raw_activity) + = $bug->can('get_activity') + ? $bug->get_activity() + : Bugzilla::Bug::GetBugActivity($bug->id); + + # allow other extensions to alter history + Bugzilla::Hook::process('inline_history_activitiy', + {activity => $raw_activity}); + + my %attachment_cache; + foreach my $attachment (@{$bug->attachments}) { + $attachment_cache{$attachment->id} = $attachment; + } + +# build a list of bugs we need to check visibility of, so we can check with a single query + my %visible_bug_ids; + + # envelope, augment and tweak + foreach my $operation (@$raw_activity) { + + # make operation.who an object + $operation->{who} + = Bugzilla::User->new({name => $operation->{who}, cache => 1}); + + # we need to track operations which are just cc changes + $operation->{cc_only} = 1; + + for (my $i = 0; $i < scalar(@{$operation->{changes}}); $i++) { + my $change = $operation->{changes}->[$i]; + + # make an attachment object + if ($change->{attachid}) { + $change->{attach} = $attachment_cache{$change->{attachid}}; + } + + # empty resolutions are displayed as --- by default + # make it explicit here to enable correct display of the change + if ($change->{fieldname} eq 'resolution') { + $change->{removed} = '---' if $change->{removed} eq ''; + $change->{added} = '---' if $change->{added} eq ''; + } + + # make boolean fields true/false instead of 1/0 + my ($table, $field) = ('bugs', $change->{fieldname}); + if ($field =~ /^([^\.]+)\.(.+)$/) { + ($table, $field) = ($1, $2); + } + my $column = $dbh->bz_column_info($table, $field); + if ($column && $column->{TYPE} eq 'BOOLEAN') { + $change->{removed} = ''; + $change->{added} = $change->{added} ? 'true' : 'false'; + } + + # load field object (only required for custom fields), and set the + # field type for custom fields + my $field_obj; + if ($change->{fieldname} =~ /^cf_/) { + $field_obj = Bugzilla::Field->new({name => $change->{fieldname}, cache => 1}); + $change->{fieldtype} = $field_obj->type; + } + + # identify buglist changes + if ( $change->{fieldname} eq 'blocked' + || $change->{fieldname} eq 'dependson' + || $change->{fieldname} eq 'dupe' + || ($field_obj && $field_obj->type == FIELD_TYPE_BUG_ID)) + { + $change->{buglist} = 1; + foreach my $what (qw(removed added)) { + my @buglist = split(/[\s,]+/, $change->{$what}); + foreach my $id (@buglist) { + if ($id && $id =~ /^\d+$/) { + $visible_bug_ids{$id} = 1; } + } + } + } + + # split see-also + if ($change->{fieldname} eq 'see_also') { + my $url_base = Bugzilla->localconfig->{urlbase}; + foreach my $f (qw( added removed )) { + my @values; + foreach my $value (split(/, /, $change->{$f})) { + my ($bug_id) + = substr($value, 0, length($url_base)) eq $url_base + ? $value =~ /id=(\d+)$/ + : undef; + push @values, {url => $value, bug_id => $bug_id,}; + } + $change->{$f} = \@values; + } + } + + # track cc-only + if ($change->{fieldname} ne 'cc') { + $operation->{cc_only} = 0; + } + + # split multiple flag changes (must be processed last) + # set $change->{flagtype_name} to make searching the activity + # stream for flag changes easier and quicker + if ($change->{fieldname} eq 'flagtypes.name') { + my @added = split(/, /, $change->{added}); + my @removed = split(/, /, $change->{removed}); + if (scalar(@added) <= 1 && scalar(@removed) <= 1) { + $change->{flagtype_name} = _extract_flagtype($added[0] || $removed[0]); + next; + } - # track cc-only - if ($change->{fieldname} ne 'cc') { - $operation->{cc_only} = 0; - } + # remove current change + splice(@{$operation->{changes}}, $i, 1); - # split multiple flag changes (must be processed last) - # set $change->{flagtype_name} to make searching the activity - # stream for flag changes easier and quicker - if ($change->{fieldname} eq 'flagtypes.name') { - my @added = split(/, /, $change->{added}); - my @removed = split(/, /, $change->{removed}); - if (scalar(@added) <= 1 && scalar(@removed) <= 1) { - $change->{flagtype_name} = _extract_flagtype($added[0] || $removed[0]); - next; - } - # remove current change - splice(@{$operation->{changes}}, $i, 1); - # restructure into added/removed for each flag - my %flags; - foreach my $flag (@added) { - $flags{$flag}{added} = $flag; - $flags{$flag}{removed} = ''; - } - foreach my $flag (@removed) { - $flags{$flag}{added} = ''; - $flags{$flag}{removed} = $flag; - } - # clone current change, modify and insert - foreach my $flag (sort keys %flags) { - my $flag_change = {}; - foreach my $key (keys %$change) { - $flag_change->{$key} = $change->{$key}; - } - $flag_change->{removed} = $flags{$flag}{removed}; - $flag_change->{added} = $flags{$flag}{added}; - $flag_change->{flagtype_name} = _extract_flagtype($flag); - splice(@{$operation->{changes}}, $i, 0, $flag_change); - } - $i--; - } + # restructure into added/removed for each flag + my %flags; + foreach my $flag (@added) { + $flags{$flag}{added} = $flag; + $flags{$flag}{removed} = ''; + } + foreach my $flag (@removed) { + $flags{$flag}{added} = ''; + $flags{$flag}{removed} = $flag; } - _add_activity_to_stream($stream, date_str_to_time($operation->{when}), $operation->{who}->id, $operation); + # clone current change, modify and insert + foreach my $flag (sort keys %flags) { + my $flag_change = {}; + foreach my $key (keys %$change) { + $flag_change->{$key} = $change->{$key}; + } + $flag_change->{removed} = $flags{$flag}{removed}; + $flag_change->{added} = $flags{$flag}{added}; + $flag_change->{flagtype_name} = _extract_flagtype($flag); + splice(@{$operation->{changes}}, $i, 0, $flag_change); + } + $i--; + } } - # prime the visible-bugs cache - $user->visible_bugs([keys %visible_bug_ids]); + _add_activity_to_stream( + $stream, + date_str_to_time($operation->{when}), + $operation->{who}->id, $operation + ); + } + + # prime the visible-bugs cache + $user->visible_bugs([keys %visible_bug_ids]); } sub _extract_flagtype { - my ($value) = @_; - return $value =~ /^(.+)[\?\-\+]/ ? $1 : undef; + my ($value) = @_; + return $value =~ /^(.+)[\?\-\+]/ ? $1 : undef; } # display 'duplicate of this bug' as an activity entry, not a comment sub _add_duplicates_to_stream { - my ($bug, $stream) = @_; - my $dbh = Bugzilla->dbh; + my ($bug, $stream) = @_; + my $dbh = Bugzilla->dbh; - my $sth = $dbh->prepare(" + my $sth = $dbh->prepare(" SELECT longdescs.who, - UNIX_TIMESTAMP(bug_when), " . - $dbh->sql_date_format('bug_when') . ", + UNIX_TIMESTAMP(bug_when), " . $dbh->sql_date_format('bug_when') . ", type, extra_data FROM longdescs @@ -360,19 +374,22 @@ sub _add_duplicates_to_stream { WHERE bug_id = ? AND (type = ? OR type = ?) ORDER BY bug_when "); - $sth->execute($bug->id, CMT_HAS_DUPE, CMT_DUPE_OF); - - while (my($who, $time, $when, $type, $dupe_id) = $sth->fetchrow_array) { - _add_activity_to_stream($stream, $time, $who, { - who => Bugzilla::User->new({ id => $who, cache => 1 }), - when => $when, - changes => [{ - fieldname => ($type == CMT_HAS_DUPE ? 'has_dupe' : 'dupe_of'), - added => $dupe_id, - buglist => 1, - }], - }); - } + $sth->execute($bug->id, CMT_HAS_DUPE, CMT_DUPE_OF); + + while (my ($who, $time, $when, $type, $dupe_id) = $sth->fetchrow_array) { + _add_activity_to_stream( + $stream, $time, $who, + { + who => Bugzilla::User->new({id => $who, cache => 1}), + when => $when, + changes => [{ + fieldname => ($type == CMT_HAS_DUPE ? 'has_dupe' : 'dupe_of'), + added => $dupe_id, + buglist => 1, + }], + } + ); + } } 1; |