summaryrefslogtreecommitdiffstats
path: root/extensions/BugModal/lib/ActivityStream.pm
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/BugModal/lib/ActivityStream.pm')
-rw-r--r--extensions/BugModal/lib/ActivityStream.pm567
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;