From ba0765bf4dc468815e4fa45509010c0cd675e5b2 Mon Sep 17 00:00:00 2001 From: Byron Jones Date: Mon, 25 Nov 2013 16:21:03 +0800 Subject: Bug 793963: add the ability to tag comments with arbitrary tags r=dkl, a=glob --- Bugzilla/Bug.pm | 33 +++- Bugzilla/Comment.pm | 195 ++++++++++++++++++++- Bugzilla/Comment/TagWeights.pm | 74 ++++++++ Bugzilla/Config/BugFields.pm | 8 +- Bugzilla/Config/Common.pm | 14 ++ Bugzilla/Config/GroupSecurity.pm | 11 +- Bugzilla/Constants.pm | 7 + Bugzilla/DB/Schema.pm | 48 ++++++ Bugzilla/Field.pm | 1 + Bugzilla/User.pm | 17 ++ Bugzilla/WebService/Bug.pm | 211 ++++++++++++++++++++++- Bugzilla/WebService/Constants.pm | 7 +- Bugzilla/WebService/Server/REST/Resources/Bug.pm | 16 ++ 13 files changed, 626 insertions(+), 16 deletions(-) create mode 100644 Bugzilla/Comment/TagWeights.pm (limited to 'Bugzilla') diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm index 1e82da30d..9669352cd 100644 --- a/Bugzilla/Bug.pm +++ b/Bugzilla/Bug.pm @@ -3822,7 +3822,7 @@ sub _bugs_in_order { # Get the activity of a bug, starting from $starttime (if given). # This routine assumes Bugzilla::Bug->check has been previously called. sub get_activity { - my ($self, $attach_id, $starttime) = @_; + my ($self, $attach_id, $starttime, $include_comment_tags) = @_; my $dbh = Bugzilla->dbh; my $user = Bugzilla->user; @@ -3834,7 +3834,7 @@ sub get_activity { if (defined $starttime) { trick_taint($starttime); push (@args, $starttime); - $datepart = "AND bugs_activity.bug_when > ?"; + $datepart = "AND bug_when > ?"; } my $attachpart = ""; @@ -3854,7 +3854,7 @@ sub get_activity { my $query = "SELECT fielddefs.name, bugs_activity.attach_id, " . $dbh->sql_date_format('bugs_activity.bug_when', '%Y.%m.%d %H:%i:%s') . - ", bugs_activity.removed, bugs_activity.added, profiles.login_name, + " AS bug_when, bugs_activity.removed, bugs_activity.added, profiles.login_name, bugs_activity.comment_id FROM bugs_activity $suppjoins @@ -3865,8 +3865,31 @@ sub get_activity { WHERE bugs_activity.bug_id = ? $datepart $attachpart - $suppwhere - ORDER BY bugs_activity.bug_when, bugs_activity.id"; + $suppwhere "; + + if (Bugzilla->params->{'comment_taggers_group'} + && $include_comment_tags + && !$attach_id) + { + $query .= " + UNION ALL + SELECT 'comment_tag' AS name, + NULL AS attach_id," . + $dbh->sql_date_format('longdescs_tags_activity.bug_when', '%Y.%m.%d %H:%i:%s') . " AS bug_when, + longdescs_tags_activity.removed, + longdescs_tags_activity.added, + profiles.login_name, + longdescs_tags_activity.comment_id as comment_id + FROM longdescs_tags_activity + INNER JOIN profiles ON profiles.userid = longdescs_tags_activity.who + WHERE longdescs_tags_activity.bug_id = ? + $datepart + "; + push @args, $self->id; + push @args, $starttime if defined $starttime; + } + + $query .= "ORDER BY bug_when, comment_id"; my $list = $dbh->selectall_arrayref($query, undef, @args); diff --git a/Bugzilla/Comment.pm b/Bugzilla/Comment.pm index d4e3383ea..d1e1e2530 100644 --- a/Bugzilla/Comment.pm +++ b/Bugzilla/Comment.pm @@ -13,11 +13,13 @@ use strict; use parent qw(Bugzilla::Object); use Bugzilla::Attachment; +use Bugzilla::Comment::TagWeights; use Bugzilla::Constants; use Bugzilla::Error; use Bugzilla::User; use Bugzilla::Util; +use List::Util qw(first); use Scalar::Util qw(blessed); ############################### @@ -79,21 +81,90 @@ use constant VALIDATOR_DEPENDENCIES => { sub update { my $self = shift; - my $changes = $self->SUPER::update(@_); - $self->bug->_sync_fulltext( update_comments => 1); + my ($changes, $old_comment) = $self->SUPER::update(@_); + + if (exists $changes->{'thetext'} || exists $changes->{'isprivate'}) { + $self->bug->_sync_fulltext( update_comments => 1); + } + + my @old_tags = @{ $old_comment->tags }; + my @new_tags = @{ $self->tags }; + my ($removed_tags, $added_tags) = diff_arrays(\@old_tags, \@new_tags); + + if (@$removed_tags || @$added_tags) { + my $dbh = Bugzilla->dbh; + my $when = $dbh->selectrow_array("SELECT LOCALTIMESTAMP(0)"); + my $sth_delete = $dbh->prepare( + "DELETE FROM longdescs_tags WHERE comment_id = ? AND tag = ?" + ); + my $sth_insert = $dbh->prepare( + "INSERT INTO longdescs_tags(comment_id, tag) VALUES (?, ?)" + ); + my $sth_activity = $dbh->prepare( + "INSERT INTO longdescs_tags_activity + (bug_id, comment_id, who, bug_when, added, removed) + VALUES (?, ?, ?, ?, ?, ?)" + ); + + foreach my $tag (@$removed_tags) { + my $weighted = Bugzilla::Comment::TagWeights->new({ name => $tag }); + if ($weighted) { + if ($weighted->weight == 1) { + $weighted->remove_from_db(); + } else { + $weighted->set_weight($weighted->weight - 1); + $weighted->update(); + } + } + trick_taint($tag); + $sth_delete->execute($self->id, $tag); + $sth_activity->execute( + $self->bug_id, $self->id, Bugzilla->user->id, $when, '', $tag); + } + + foreach my $tag (@$added_tags) { + my $weighted = Bugzilla::Comment::TagWeights->new({ name => $tag }); + if ($weighted) { + $weighted->set_weight($weighted->weight + 1); + $weighted->update(); + } else { + Bugzilla::Comment::TagWeights->create({ tag => $tag, weight => 1 }); + } + trick_taint($tag); + $sth_insert->execute($self->id, $tag); + $sth_activity->execute( + $self->bug_id, $self->id, Bugzilla->user->id, $when, $tag, ''); + } + } + return $changes; } -# Speeds up displays of comment lists by loading all ->author objects -# at once for a whole list. +# Speeds up displays of comment lists by loading all author objects and tags at +# once for a whole list. sub preload { my ($class, $comments) = @_; + # Author my %user_ids = map { $_->{who} => 1 } @$comments; my $users = Bugzilla::User->new_from_list([keys %user_ids]); my %user_map = map { $_->id => $_ } @$users; foreach my $comment (@$comments) { $comment->{author} = $user_map{$comment->{who}}; } + # Tags + if (Bugzilla->params->{'comment_taggers_group'}) { + my $dbh = Bugzilla->dbh; + my @comment_ids = map { $_->id } @$comments; + my %comment_map = map { $_->id => $_ } @$comments; + my $rows = $dbh->selectall_arrayref( + "SELECT comment_id, " . $dbh->sql_group_concat('tag', "','") . " + FROM longdescs_tags + WHERE " . $dbh->sql_in('comment_id', \@comment_ids) . " + GROUP BY comment_id"); + foreach my $row (@$rows) { + $comment_map{$row->[0]}->{tags} = [ split(/,/, $row->[1]) ]; + } + } } ############################### @@ -113,6 +184,39 @@ sub work_time { sub type { return $_[0]->{'type'}; } sub extra_data { return $_[0]->{'extra_data'} } +sub tags { + my ($self) = @_; + return [] unless Bugzilla->params->{'comment_taggers_group'}; + $self->{'tags'} ||= Bugzilla->dbh->selectcol_arrayref( + "SELECT tag + FROM longdescs_tags + WHERE comment_id = ? + ORDER BY tag", + undef, $self->id); + return $self->{'tags'}; +} + +sub collapsed { + my ($self) = @_; + return 0 unless Bugzilla->params->{'comment_taggers_group'}; + return $self->{collapsed} if exists $self->{collapsed}; + $self->{collapsed} = 0; + Bugzilla->request_cache->{comment_tags_collapsed} + ||= [ split(/\s*,\s*/, Bugzilla->params->{'collapsed_comment_tags'}) ]; + my @collapsed_tags = @{ Bugzilla->request_cache->{comment_tags_collapsed} }; + foreach my $my_tag (@{ $self->tags }) { + $my_tag = lc($my_tag); + foreach my $collapsed_tag (@collapsed_tags) { + if ($my_tag eq lc($collapsed_tag)) { + $self->{collapsed} = 1; + last; + } + } + last if $self->{collapsed}; + } + return $self->{collapsed}; +} + sub bug { my $self = shift; require Bugzilla::Bug; @@ -170,6 +274,26 @@ sub set_is_private { $_[0]->set('isprivate', $_[1]); } sub set_type { $_[0]->set('type', $_[1]); } sub set_extra_data { $_[0]->set('extra_data', $_[1]); } +sub add_tag { + my ($self, $tag) = @_; + $tag = $self->_check_tag($tag); + + my $tags = $self->tags; + return if grep { lc($tag) eq lc($_) } @$tags; + push @$tags, $tag; + $self->{'tags'} = [ sort @$tags ]; +} + +sub remove_tag { + my ($self, $tag) = @_; + $tag = $self->_check_tag($tag); + + my $tags = $self->tags; + my $index = first { lc($tags->[$_]) eq lc($tag) } 0..scalar(@$tags) - 1; + return unless defined $index; + splice(@$tags, $index, 1); +} + ############## # Validators # ############## @@ -312,6 +436,17 @@ sub _check_isprivate { return $isprivate ? 1 : 0; } +sub _check_tag { + my ($invocant, $tag) = @_; + length($tag) < MIN_COMMENT_TAG_LENGTH + and ThrowUserError('comment_tag_too_short', { tag => $tag }); + length($tag) > MAX_COMMENT_TAG_LENGTH + and ThrowUserError('comment_tag_too_long', { tag => $tag }); + $tag =~ /^[\w\d\._-]+$/ + or ThrowUserError('comment_tag_invalid', { tag => $tag }); + return $tag; +} + sub count { my ($self) = @_; @@ -326,7 +461,7 @@ sub count { undef, $self->bug_id, $self->creation_ts); return --$self->{'count'}; -} +} 1; @@ -372,7 +507,7 @@ C Time spent as related to this comment. =item C -C Comment is marked as private +C Comment is marked as private. =item C @@ -387,6 +522,54 @@ L who created the comment. C The position this comment is located in the full list of comments for a bug starting from 0. +=item C + +C Comment should be displayed as collapsed by default. + +=item C + +C The tags attached to the comment. + +=item C + +=over + +=item B + +Attaches the specified tag to the comment. + +=item B + +=over + +=item C + +C The tag to attach. + +=back + +=back + +=item C + +=over + +=item B + +Detaches the specified tag from the comment. + +=item B + +=over + +=item C + +C The tag to detach. + +=back + +=back + =item C =over diff --git a/Bugzilla/Comment/TagWeights.pm b/Bugzilla/Comment/TagWeights.pm new file mode 100644 index 000000000..5835efbc4 --- /dev/null +++ b/Bugzilla/Comment/TagWeights.pm @@ -0,0 +1,74 @@ +# 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::Comment::TagWeights; + +use 5.10.1; +use strict; + +use parent qw(Bugzilla::Object); + +use Bugzilla::Constants; + +# No auditing required +use constant AUDIT_CREATES => 0; +use constant AUDIT_UPDATES => 0; +use constant AUDIT_REMOVES => 0; + +use constant DB_COLUMNS => qw( + id + tag + weight +); + +use constant UPDATE_COLUMNS => qw( + weight +); + +use constant DB_TABLE => 'longdescs_tags_weights'; +use constant ID_FIELD => 'id'; +use constant NAME_FIELD => 'tag'; +use constant LIST_ORDER => 'weight DESC'; +use constant VALIDATORS => { }; + +sub tag { return $_[0]->{'tag'} } +sub weight { return $_[0]->{'weight'} } + +sub set_weight { $_[0]->set('weight', $_[1]); } + +1; + +=head1 NAME + +Comment::TagWeights - Bugzilla comment weighting class. + +=head1 DESCRIPTION + +TagWeights.pm represents a Comment::TagWeight object. It is an implementation +of L, and thus provides all methods that L +provides. + +TagWeights is used to quickly find tags and order by their usage count. + +=head1 PROPERTIES + +=over + +=item C + +C The tag + +=item C + +C The tag's weight. The value returned corresponds to the number of +comments with this tag attached. + +=item C + +C Set the tag's weight. + +=back diff --git a/Bugzilla/Config/BugFields.pm b/Bugzilla/Config/BugFields.pm index e24f75661..39c43cb92 100644 --- a/Bugzilla/Config/BugFields.pm +++ b/Bugzilla/Config/BugFields.pm @@ -84,7 +84,13 @@ sub get_param_list { choices => ['', @legal_OS], default => '', checker => \&check_opsys - } ); + }, + + { + name => 'collapsed_comment_tags', + type => 't', + default => 'obsolete, spam', + }); return @param_list; } diff --git a/Bugzilla/Config/Common.pm b/Bugzilla/Config/Common.pm index 5872ae1e1..96d7c1c89 100644 --- a/Bugzilla/Config/Common.pm +++ b/Bugzilla/Config/Common.pm @@ -28,6 +28,7 @@ use parent qw(Exporter); check_mail_delivery_method check_notification check_utf8 check_bug_status check_smtp_auth check_theschwartz_available check_maxattachmentsize check_email check_smtp_ssl + check_comment_taggers_group ); # Checking functions for the various values @@ -367,6 +368,14 @@ sub check_theschwartz_available { return ""; } +sub check_comment_taggers_group { + my $group_name = shift; + if ($group_name && !Bugzilla->feature('jsonrpc')) { + return "Comment tagging requires installation of the JSONRPC feature"; + } + return check_group($group_name); +} + # OK, here are the parameter definitions themselves. # # Each definition is a hash with keys: @@ -465,6 +474,11 @@ Checks that the value is a valid number Checks that the value is a valid regexp +=item C + +Checks that the required modules for comment tagging are installed, and that a +valid group is provided. + =back =head1 B diff --git a/Bugzilla/Config/GroupSecurity.pm b/Bugzilla/Config/GroupSecurity.pm index d57573de3..1d3878415 100644 --- a/Bugzilla/Config/GroupSecurity.pm +++ b/Bugzilla/Config/GroupSecurity.pm @@ -56,7 +56,15 @@ sub get_param_list { default => 'editbugs', checker => \&check_group }, - + + { + name => 'comment_taggers_group', + type => 's', + choices => \&_get_all_group_names, + default => 'editbugs', + checker => \&check_comment_taggers_group + }, + { name => 'debug_group', type => 's', @@ -84,4 +92,5 @@ sub _get_all_group_names { unshift(@group_names, ''); return \@group_names; } + 1; diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm index 60c7a34e0..de045273b 100644 --- a/Bugzilla/Constants.pm +++ b/Bugzilla/Constants.pm @@ -69,6 +69,9 @@ use Memoize; COMMENT_COLS MAX_COMMENT_LENGTH + MIN_COMMENT_TAG_LENGTH + MAX_COMMENT_TAG_LENGTH + CMT_NORMAL CMT_DUPE_OF CMT_HAS_DUPE @@ -291,6 +294,10 @@ use constant COMMENT_COLS => 80; # Used in _check_comment(). Gives the max length allowed for a comment. use constant MAX_COMMENT_LENGTH => 65535; +# The minimum and maximum length of comment tags. +use constant MIN_COMMENT_TAG_LENGTH => 3; +use constant MAX_COMMENT_TAG_LENGTH => 24; + # The type of bug comments. use constant CMT_NORMAL => 0; use constant CMT_DUPE_OF => 1; diff --git a/Bugzilla/DB/Schema.pm b/Bugzilla/DB/Schema.pm index c619a5780..30ed55b9a 100644 --- a/Bugzilla/DB/Schema.pm +++ b/Bugzilla/DB/Schema.pm @@ -406,6 +406,54 @@ use constant ABSTRACT_SCHEMA => { ], }, + longdescs_tags => { + FIELDS => [ + id => { TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1 }, + comment_id => { TYPE => 'INT4', + REFERENCES => { TABLE => 'longdescs', + COLUMN => 'comment_id', + DELETE => 'CASCADE' }}, + tag => { TYPE => 'varchar(24)', NOTNULL => 1 }, + ], + INDEXES => [ + longdescs_tags_idx => { FIELDS => ['comment_id', 'tag'], TYPE => 'UNIQUE' }, + ], + }, + + longdescs_tags_weights => { + FIELDS => [ + id => { TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1 }, + tag => { TYPE => 'varchar(24)', NOTNULL => 1 }, + weight => { TYPE => 'INT3', NOTNULL => 1 }, + ], + INDEXES => [ + longdescs_tags_weights_tag_idx => { FIELDS => ['tag'], TYPE => 'UNIQUE' }, + ], + }, + + longdescs_tags_activity => { + FIELDS => [ + id => { TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1 }, + bug_id => { TYPE => 'INT3', NOTNULL => 1, + REFERENCES => { TABLE => 'bugs', + COLUMN => 'bug_id', + DELETE => 'CASCADE' }}, + comment_id => { TYPE => 'INT4', + REFERENCES => { TABLE => 'longdescs', + COLUMN => 'comment_id', + DELETE => 'CASCADE' }}, + who => { TYPE => 'INT3', NOTNULL => 1, + REFERENCES => { TABLE => 'profiles', + COLUMN => 'userid' }}, + bug_when => { TYPE => 'DATETIME', NOTNULL => 1 }, + added => { TYPE => 'varchar(24)' }, + removed => { TYPE => 'varchar(24)' }, + ], + INDEXES => [ + longdescs_tags_activity_bug_id_idx => ['bug_id'], + ], + }, + dependencies => { FIELDS => [ blocked => {TYPE => 'INT3', NOTNULL => 1, diff --git a/Bugzilla/Field.pm b/Bugzilla/Field.pm index cda252542..82c82161a 100644 --- a/Bugzilla/Field.pm +++ b/Bugzilla/Field.pm @@ -255,6 +255,7 @@ use constant DEFAULT_FIELDS => ( type => FIELD_TYPE_BUG_URLS}, {name => 'tag', desc => 'Tags', buglist => 1, type => FIELD_TYPE_KEYWORDS}, + {name => 'comment_tag', desc => 'Comment Tag'}, ); ################ diff --git a/Bugzilla/User.pm b/Bugzilla/User.pm index 84487dbbe..7a067fce0 100644 --- a/Bugzilla/User.pm +++ b/Bugzilla/User.pm @@ -1852,6 +1852,17 @@ sub is_timetracker { return $self->{'is_timetracker'}; } +sub can_tag_comments { + my $self = shift; + + if (!defined $self->{'can_tag_comments'}) { + my $group = Bugzilla->params->{'comment_taggers_group'}; + $self->{'can_tag_comments'} = + ($group && $self->in_group($group)) ? 1 : 0; + } + return $self->{'can_tag_comments'}; +} + sub get_userlist { my $self = shift; @@ -2648,6 +2659,12 @@ i.e. if the 'insidergroup' parameter is set and the user belongs to this group. Returns true if the user is a global watcher, i.e. if the 'globalwatchers' parameter contains the user. +=item C + +Returns true if the user can attach tags to comments. +i.e. if the 'comment_taggers_group' parameter is set and the user belongs to +this group. + =back =head1 CLASS FUNCTIONS diff --git a/Bugzilla/WebService/Bug.pm b/Bugzilla/WebService/Bug.pm index 598316c59..7f012160c 100644 --- a/Bugzilla/WebService/Bug.pm +++ b/Bugzilla/WebService/Bug.pm @@ -13,6 +13,7 @@ use strict; use parent qw(Bugzilla::WebService); use Bugzilla::Comment; +use Bugzilla::Comment::TagWeights; use Bugzilla::Constants; use Bugzilla::Error; use Bugzilla::Field; @@ -20,7 +21,7 @@ use Bugzilla::WebService::Constants; use Bugzilla::WebService::Util qw(filter filter_wants validate translate); use Bugzilla::Bug; use Bugzilla::BugMail; -use Bugzilla::Util qw(trick_taint trim diff_arrays); +use Bugzilla::Util qw(trick_taint trim diff_arrays detaint_natural); use Bugzilla::Version; use Bugzilla::Milestone; use Bugzilla::Status; @@ -320,7 +321,8 @@ sub _translate_comment { my ($self, $comment, $filters) = @_; my $attach_id = $comment->is_about_attachment ? $comment->extra_data : undef; - return filter $filters, { + + my $comment_hash = { id => $self->type('int', $comment->id), bug_id => $self->type('int', $comment->bug_id), creator => $self->type('email', $comment->author->login), @@ -332,6 +334,16 @@ sub _translate_comment { attachment_id => $self->type('int', $attach_id), count => $self->type('int', $comment->count), }; + + # Don't load comment tags unless enabled + if (Bugzilla->params->{'comment_taggers_group'}) { + $comment_hash->{tags} = [ + map { $self->type('string', $_) } + @{ $comment->tags } + ]; + } + + return filter $filters, $comment_hash; } sub get { @@ -999,6 +1011,70 @@ sub update_tags { return { changes => \%changes }; } +sub update_comment_tags { + my ($self, $params) = @_; + + my $user = Bugzilla->login(LOGIN_REQUIRED); + Bugzilla->params->{'comment_taggers_group'} + || ThrowUserError("comment_tag_disabled"); + $user->can_tag_comments + || ThrowUserError("auth_failure", + { group => Bugzilla->params->{'comment_taggers_group'}, + action => "update", + object => "comment_tags" }); + + my $comment_id = $params->{comment_id} + // ThrowCodeError('param_required', + { function => 'Bug.update_comment_tags', + param => 'comment_id' }); + + my $comment = Bugzilla::Comment->new($comment_id) + || return []; + $comment->bug->check_is_visible(); + if ($comment->is_private && !$user->is_insider) { + ThrowUserError('comment_is_private', { id => $comment_id }); + } + + my $dbh = Bugzilla->dbh; + $dbh->bz_start_transaction(); + foreach my $tag (@{ $params->{add} || [] }) { + $comment->add_tag($tag) if defined $tag; + } + foreach my $tag (@{ $params->{remove} || [] }) { + $comment->remove_tag($tag) if defined $tag; + } + $comment->update(); + $dbh->bz_commit_transaction(); + + return $comment->tags; +} + +sub search_comment_tags { + my ($self, $params) = @_; + + Bugzilla->login(LOGIN_REQUIRED); + Bugzilla->params->{'comment_taggers_group'} + || ThrowUserError("comment_tag_disabled"); + Bugzilla->user->can_tag_comments + || ThrowUserError("auth_failure", { group => Bugzilla->params->{'comment_taggers_group'}, + action => "search", + object => "comment_tags"}); + + my $query = $params->{query}; + $query + // ThrowCodeError('param_required', { param => 'query' }); + my $limit = detaint_natural($params->{limit}) || 7; + + my $tags = Bugzilla::Comment::TagWeights->match({ + WHERE => { + 'tag LIKE ?' => "\%$query\%", + }, + LIMIT => $limit, + }); + return [ map { $_->tag } @$tags ]; +} + + ############################## # Private Helper Subroutines # ############################## @@ -4032,6 +4108,137 @@ This method can throw the same errors as L. =back +=head2 search_comment_tags + +B + +=over + +=item B + +Searches for tags which contain the provided substring. + +=item B + +To search for comment tags: + +GET /bug/comment/tags/ + +=item B + +=over + +=item C + +B C Only tags containg this substring will be returned. + +=item C + +C If provided will return no more than C tags. Defaults to C<10>. + +=back + +=item B + +An C of matching tags. + +=item B + +This method can throw all of the errors that L throws, plus: + +=over + +=item 125 (Comment Tagging Disabled) + +Comment tagging support is not available or enabled. + +=back + +=item B + +=over + +=item Added in Bugzilla B<5.0>. + +=back + +=back + +=head2 update_comment_tags + +B + +=over + +=item B + +Adds or removes tags from a comment. + +=item B + +To update the tags comments attached to a comment: + +PUT /bug/comment/tags + +The params to include in the PUT body as well as the returned data format, +are the same as below. + +=item B + +=over + +=item C + +B C The ID of the comment to update. + +=item C + +C The tags to attach to the comment. + +=item C + +C The tags to detach from the comment. + +=back + +=item B + +An C containing the comment's updated tags. + +=item B + +This method can throw all of the errors that L throws, plus: + +=over + +=item 125 (Comment Tagging Disabled) + +Comment tagging support is not available or enabled. + +=item 126 (Invalid Comment Tag) + +The comment tag provided was not valid (eg. contains invalid characters). + +=item 127 (Comment Tag Too Short) + +The comment tag provided is shorter than the minimum length. + +=item 128 (Comment Tag Too Long) + +The comment tag provided is longer than the maximum length. + +=back + +=item B + +=over + +=item Added in Bugzilla B<5.0>. + +=back + +=back + =head1 B =over diff --git a/Bugzilla/WebService/Constants.pm b/Bugzilla/WebService/Constants.pm index 1c3929e53..2c5bc31dd 100644 --- a/Bugzilla/WebService/Constants.pm +++ b/Bugzilla/WebService/Constants.pm @@ -98,7 +98,12 @@ use constant WS_ERROR_CODE => { comment_is_private => 110, comment_id_invalid => 111, comment_too_long => 114, - comment_invalid_isprivate => 117, + comment_invalid_isprivate => 117, + # Comment tagging + comment_tag_disabled => 125, + comment_tag_invalid => 126, + comment_tag_too_long => 127, + comment_tag_too_short => 128, # See Also errors bug_url_invalid => 112, bug_url_too_long => 112, diff --git a/Bugzilla/WebService/Server/REST/Resources/Bug.pm b/Bugzilla/WebService/Server/REST/Resources/Bug.pm index 98ae6049c..ea420b4ed 100644 --- a/Bugzilla/WebService/Server/REST/Resources/Bug.pm +++ b/Bugzilla/WebService/Server/REST/Resources/Bug.pm @@ -65,6 +65,22 @@ sub _rest_resources { } } }, + qr{^/bug/comment/tags/([^/]+)$}, { + GET => { + method => 'search_comment_tags', + params => sub { + return { query => $_[0] }; + }, + }, + }, + qr{^/bug/comment/([^/]+)/tags$}, { + PUT => { + method => 'update_comment_tags', + params => sub { + return { comment_id => $_[0] }; + }, + }, + }, qr{^/bug/([^/]+)/history$}, { GET => { method => 'history', -- cgit v1.2.3-24-g4f1b