Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Bugzilla/Comment.pm
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,8 @@ sub body_full {
}
else {
$body = $self->body;
Bugzilla::Hook::process('comment_body_full',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with this hook the rest text field (and show.xml thetext, webhook/pulse body) can now omit part of the stored text. docs/en/md/api/core/v1/comment.md still says text is the full body. worth a line there saying extensions may hide parts of text and raw_text always has the stored text

{comment => $self, body => \$body});
}
if ($params->{wrap} and !$self->already_wrapped) {
$body = wrap_comment($body);
Expand Down
19 changes: 19 additions & 0 deletions Bugzilla/Hook.pm
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,25 @@ spaces.
=back


=head2 comment_body_full

Called by L<Bugzilla::Comment/body_full> for a plain comment, before any
wrapping, so that an extension can change the text people read without
changing the stored text. Bugmail, webhook and Pulse payloads use the changed
text, while L<Bugzilla::Comment/body> and the C<raw_text> REST field keep
returning the stored text.

Params:

=over

=item C<comment> - The L<Bugzilla::Comment> being read.

=item C<body> - A reference to the comment text, which you can modify.

=back


=head2 config_add_panels

If you want to add new panels to the Parameters administrative interface,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@
[% Hook.process('user', 'bug/comments.html.tmpl') %]
</td>
[% END %]

[% Hook.process('before_comment_actions', 'bug_modal/activity_stream.html.tmpl') %]
<td rowspan="2" class="comment-actions"><div role="group">
[% IF user.is_insider && bug.check_can_change_field('longdesc', 0, 1).allowed %]
[% IF comment.is_private %]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@
# defined by the Mozilla Public License, v. 2.0.
#%]

[% hidden_tags = [] %]
[% Hook.process('before_tags', 'bug_modal/comment_tags.html.tmpl') %]

[% FOREACH tag IN comment.tags ~%]
<span class="comment-tag" data-tag="[% tag FILTER html %]">
<span class="comment-tag" data-tag="[% tag FILTER html %]"
[%= 'hidden' IF hidden_tags.contains(tag.lower) %]>
[%~ '<a role="button" aria-label="Remove" class="remove">×</a>' IF user.can_tag_comments %]
[% IF comment.tag_url(tag) %]
<a class="comment-tag-url" aria-label="More Information about this tag"
Expand Down
3 changes: 1 addition & 2 deletions extensions/BugModal/web/bug_modal.css
Original file line number Diff line number Diff line change
Expand Up @@ -1869,8 +1869,7 @@ a.lightbox-icon.markdown {
.comment .comment-actions > [role="group"] {
justify-content: flex-end;
flex-wrap: wrap;
gap: 4px 0;
width: 128px;
width: 64px;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this narrows the action group and drops the row gap for every comment on small screens, not just hackbot ones. tag/reply/spinner (and private for insiders) now wrap two per row with no spacing. could this move to hackbot.css scoped to hackbot comments, or is the global change intended

}

.comment .comment-actions > [role="group"] .comment-private {
Expand Down
8 changes: 6 additions & 2 deletions extensions/EditComments/lib/WebService.pm
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,14 @@ sub update_comment {
sync_fulltext => 1,
});

# Render what the bug page shows, which can differ from the stored text: the
# comment_body_full hook lets extensions hide part of it (eg. Hackbot's front
# matter).
my $body = $comment->body_full({exclude_attachment => 1});
my $html
= $comment->is_markdown && Bugzilla->params->{use_markdown}
? Bugzilla->markdown->render_html($new_comment, $bug)
: Bugzilla::Template::quoteUrls($new_comment, $bug);
? Bugzilla->markdown->render_html($body, $bug)
: Bugzilla::Template::quoteUrls($body, $bug);

# Respond with the updated comment and number of revisions
return {
Expand Down
20 changes: 20 additions & 0 deletions extensions/Hackbot/Config.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.

package Bugzilla::Extension::Hackbot;

use 5.10.1;
use strict;
use warnings;

use constant NAME => 'Hackbot';

use constant REQUIRED_MODULES => [];

use constant OPTIONAL_MODULES => [];

__PACKAGE__->NAME;
70 changes: 70 additions & 0 deletions extensions/Hackbot/Extension.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.

package Bugzilla::Extension::Hackbot;

use 5.10.1;
use strict;
use warnings;

use base qw(Bugzilla::Extension);

use Bugzilla::Extension::Hackbot::Metadata qw(parse_front_matter);
use Bugzilla::User;

our $VERSION = '1';

BEGIN {
*Bugzilla::Comment::hackbot = \&_comment_hackbot;
}

# The ID of the account named by the hackbot_user param, or 0 when it is unset
# or names nobody. Bugzilla::User::login_to_id doesn't cache a miss, and this
# runs for every comment that is read.
sub _hackbot_user_id {
my $login = Bugzilla->params->{hackbot_user} or return 0;
my $cache = Bugzilla->request_cache->{hackbot_user_ids} ||= {};
return $cache->{$login} //= Bugzilla::User::login_to_id($login) || 0;
}

# Anyone can type a front matter block into a comment, so only the Hackbot
# account's comments are parsed, and only plain ones: the text of other types
# (attachments, duplicates) is formatted without the comment_body_full hook, so
# their block would stay visible. Returns the same list as parse_front_matter.
# A page reads each comment several times (header, badge, tags, text), so the
# result is kept on the comment for as long as its text is unchanged.
sub _parse_comment {
my ($comment) = @_;
my $user_id = _hackbot_user_id() or return;
return if ($comment->{who} // 0) != $user_id || $comment->type;

my $body = $comment->body;
my $parsed = $comment->{hackbot_front_matter};
if (!$parsed || $parsed->[0] ne $body) {
$parsed = $comment->{hackbot_front_matter} = [$body, parse_front_matter($body)];
}
return @$parsed[1 .. $#$parsed];
}

sub _comment_hackbot {
my ($comment) = @_;
my ($metadata) = _parse_comment($comment);
return $metadata;
}

sub comment_body_full {
my ($self, $args) = @_;
my (undef, $body) = _parse_comment($args->{comment});
${$args->{body}} = $body if defined $body;
Comment thread
kyoshino marked this conversation as resolved.
Resolved
}

sub config_add_panels {
my ($self, $args) = @_;
$args->{panel_modules}->{Hackbot} = 'Bugzilla::Extension::Hackbot::Config';
}

__PACKAGE__->NAME;
37 changes: 37 additions & 0 deletions extensions/Hackbot/lib/Config.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.

package Bugzilla::Extension::Hackbot::Config;

use 5.10.1;
use strict;
use warnings;

use Bugzilla::Config::Common;
use Bugzilla::User;

our $sortkey = 1310;

sub get_param_list {
my ($class) = @_;

return ({
name => 'hackbot_user',
type => 't',
default => '',
checker => \&check_hackbot_user,
});
}

# Empty is valid: it turns the extension off.
sub check_hackbot_user {
my ($login) = @_;
return '' if $login eq '' || Bugzilla::User::login_to_id($login);
return "There is no user named '$login'.";
}

1;
57 changes: 57 additions & 0 deletions extensions/Hackbot/lib/Metadata.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.

package Bugzilla::Extension::Hackbot::Metadata;

use 5.10.1;
use strict;
use warnings;

use base qw(Exporter);

our @EXPORT_OK = qw(parse_front_matter);

# The block hackbot-api puts at the top of a comment:
#
# ---
# hackbot:
# run: 550e8400-e29b-41d4-a716-446655440000
# action: 3f2a9c1e7b4d4f0a8e6c2d1b5a7f9e30
# agent: bug-fix
# url: https://hackbot.moz.tools/runs/550e8400-e29b-41d4-a716-446655440000
# ---
#
# It is only ever generated, so this reads that one shape rather than YAML in
# general. Anything else is an ordinary comment that happens to start with a
# rule, and unknown keys are skipped so that fields can be added. The /a flag
# keeps the character classes to ASCII.
my $FRONT_MATTER
= qr/\A---\nhackbot:\n((?: [[:lower:]_]+: [^\n]*\n)+)---(?:\n+|\z)(.*)\z/as;
my $FIELD = qr/^ ([[:lower:]_]+): ([^\n]*?)\s*$/am;
my $IDENTIFIER = qr/\A[[:alnum:]._-]+\z/a;

# Returns the metadata hash and the comment text without the block, or an empty
# list when the text doesn't start with a valid block. The url key is left out
# unless it is an https URL, because it ends up in a link's href.
sub parse_front_matter {
my ($text) = @_;

my ($block, $body) = ($text // '') =~ $FRONT_MATTER or return;

my %fields = $block =~ /$FIELD/g;

my %metadata;
foreach my $key (qw(run action agent)) {
return unless ($fields{$key} // '') =~ $IDENTIFIER;
$metadata{$key} = $fields{$key};
}
$metadata{url} = $fields{url} if ($fields{url} // '') =~ m{\Ahttps://\S+\z};

return (\%metadata, $body);
}

1;
Loading