JIRA如何批量删除评论?
How can I mass delete comments in JIRA?
我们有几个 JIRA 问题有超过 1000 条重复的、虚假的、类似垃圾邮件的评论。如何快速删除它们?
背景:
我们禁用了活动目录 (Exchange) 中的用户,但未禁用 JIRA,因此 JIRA 一直尝试通过电子邮件向他们发送更新。电子邮件服务器给出了一条退回消息,JIRA 尽职尽责地将其记录到任务中,导致它再次发送更新,反馈循环就此诞生。
消息的格式如下:
Delivery has failed to these recipients or groups:
mail@example.com<mail@example.com>
The e-mail address you entered couldn't be found. Please check the recipient's e-mail address and try to resend the message. If the problem continues, please contact your helpdesk.
Diagnostic information for administrators:
Generating server: emailserver.example.com
user@example.com
#550 5.1.1 RESOLVER.ADR.RecipNotFound; not found ##
Original message headers:
Received: from jiraserver.example.com (10.0.0.999) by emailserver.example.com (10.0.0.999)
with Microsoft SMTP Server id nn.n.nnn.n; Mon, 13 Jun 2016 15:57:04 -0500
Date: Mon, 13 Jun 2016 15:57:03 -0500
Our research did not discover an easy way without using purchased plug-ins such as Script Runner 或 "hacking" 我们想要避免的数据库。
注:
我们想出了一个解决方案,在此发布以供分享。
通过 Chrome JavaScript 控制台使用 JIRA REST API。
背景:
我们不想为我们希望的孤立事件编写完整的应用程序。我们最初计划使用 PowerShell 的 Invoke-WebRequest. However, authentication proved to be a challenge. The API supports Basic Authentication, though it's only recommended when using SSL,我们并未将其用于我们的内部服务器。此外,我们的初始测试导致 401 错误(可能是由于错误)。
但是,API 还支持 cookie-based 身份验证,因此只要您从具有有效 JIRA 会话的浏览器生成请求,它就可以正常工作。我们选择了那个方法。
解决方案详情:
首先,查找并查看相关评论和问题 ID:
SELECT * FROM jira..jiraaction WHERE actiontype = 'comment' AND actionbody LIKE '%RESOLVER.ADR.RecipNotFound%';
这可能是一个缓慢的查询,具体取决于您的 JIRA 数据的大小。它似乎在 issueid
上建立了索引,所以如果您知道,请指定它。此外,向该查询添加其他条件,使其仅代表您希望删除的评论。
下面的解决方案是为对单个问题发表评论而编写的,但可以扩展 JavaScript 以支持多个问题。
我们需要在 Chrome JavaScript 控制台中使用的评论 ID 列表。一种有用的格式是 comma-delimited 字符串列表,您可以按如下方式创建它:
SELECT '"' + CONVERT(VARCHAR(50),ID) + '", ' FROM jira..jiraaction WHERE actiontype = 'comment' AND actionbody LIKE '%RESOLVER.ADR.RecipNotFound%' AND issueid = @issueid FOR XML PATH('')
(这不一定是达到 concatenate strings in SQL 的最佳方式,但它很简单并且适用于此目的。)
现在,打开一个新的浏览器会话并向您的 JIRA 实例进行身份验证。我们使用 Chrome,但任何带有 JavaScript 控制台的浏览器都应该可以。
获取该查询生成的字符串并将其放入 JavaScript 控制台中的语句内,如下所示:
CommentIDs = [StringFromSQL];
您需要手动 trim 尾随逗号(或调整上述查询来为您执行此操作)。它看起来像这样:
CommentIDs = ["1234", "2345"];
当您 运行 该命令时,您将创建一个包含所有这些评论 ID 的 JavaScript 数组。
现在我们到达了技术的核心。我们将遍历该数组的内容并使用 XMLHttpRequest(通常缩写为 XHR)对 REST API 进行新的 AJAX 调用。 (还有一个jQuery option。)
for (let s of CommentIDs) {let r = new XMLHttpRequest; r.open("DELETE","http://jira.example.com/rest/api/2/issue/11111/comment/"+s,true); r.send();}
您必须将“11111”替换为相关问题 ID。您可以为多个问题 ID 重复此操作,或者您可以构建一个 multi-dimensional 数组和一个更高级的循环。
这不优雅。它没有任何错误处理,但您可以使用 Chrome JavaScript API.
监视进度
我会使用 jira-python 脚本或 ScriptRunner groovy 脚本。即使是一次性批量更新,因为它更容易测试并且不需要数据库访问。
很高兴它对你有用!
我创建了一个 python 脚本来删除特定 Jira 问题的所有评论。
它使用来自 Jira 的 API。
'''
This script removes all comments from a specified jira issue
Please provide Jira-Issue-Key/Id, Jira-URL, Username, PAssword in the variables below.
'''
import sys
import json
import requests
import urllib3
# Jira Issue Key or Id where comments are deleted from
JIRA_ISSUE_KEY = 'TST-123'
# URL to Jira
URL_JIRA = 'https://jira.contoso.com'
# Username with enough rights to delete comments
JIRA_USERNAME = 'admin'
# Password to Jira User
JIRA_PASSWORD = 'S9ev6ZpQ4sy2VFH2_bjKKQAYRUlDfW7ujNnrIq9Lbn5w'
''' ----- ----- Do not change anything below ----- ----- '''
# Ignore SSL problem (certificate) - self signed
urllib3.disable_warnings()
# get issue comments:
# https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-issue-issueIdOrKey-comment-get
URL_GET_COMMENT = '{0}/rest/api/latest/issue/{1}/comment'.format(URL_JIRA, JIRA_ISSUE_KEY)
# delete issue comment:
# https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-issue-issueIdOrKey-comment-id-delete
URL_DELETE_COMMENT = '{0}/rest/api/2/issue/{1}/comment/{2}'
def user_yesno():
''' Asks user for input yes or no, responds with boolean '''
allowed_response_yes = {'yes', 'y'}
allowed_response_no = {'no', 'n'}
user_response = input().lower()
if user_response in allowed_response_yes:
return True
elif user_response in allowed_response_no:
return False
else:
sys.stdout.write("Please respond with 'yes' or 'no'")
return False
# get jira comments
RESPONSE = requests.get(URL_GET_COMMENT, verify=False, auth=(JIRA_USERNAME, JIRA_PASSWORD))
# check if http response is OK (200)
if RESPONSE.status_code != 200:
print('Exit-101: Could not connect to api [HTTP-Error: {0}]'.format(RESPONSE.status_code))
sys.exit(101)
# parse response to json
JSON_RESPONSE = json.loads(RESPONSE.text)
# get user confirmation to delete all comments for issue
print('You want to delete {0} comments for issue {1}? (yes/no)' \
.format(len(JSON_RESPONSE['comments']), JIRA_ISSUE_KEY))
if user_yesno():
for jira_comment in JSON_RESPONSE['comments']:
print('Deleting Jira comment {0}'.format(jira_comment['id']))
# send delete request
RESPONSE = requests.delete(
URL_DELETE_COMMENT.format(URL_JIRA, JIRA_ISSUE_KEY, jira_comment['id']),
verify=False, auth=(JIRA_USERNAME, JIRA_PASSWORD))
# check if http response is No Content (204)
if RESPONSE.status_code != 204:
print('Exit-102: Could not connect to api [HTTP-Error: {0}; {1}]' \
.format(RESPONSE.status_code, RESPONSE.text))
sys.exit(102)
else:
print('User abort script...')
源代码控制:https://gist.github.com/fty4/151ee7070f2a3f9da2cfa9b1ee1c132d
我们使用 ScriptRunner 和 Groovy 脚本解决了这个时常发生的问题:
// this script takes some time, when executing it in console, it takes a long time to repsonse, and then the console retunrs "null"
// - but it kepps running in the backgorund, give it some time - at least 1 second per comment and attachment to delete.
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.comments.Comment
import com.atlassian.jira.issue.comments.CommentManager
import com.atlassian.jira.issue.attachment.Attachment
import com.atlassian.jira.issue.managers.DefaultAttachmentManager
import com.atlassian.jira.issue.AttachmentManager
import org.apache.log4j.Logger
import org.apache.log4j.Level
log.setLevel(Level.DEBUG)
// NRS-1959
def issueKeys = ['XS-8071', 'XS-8060', 'XS-8065', 'XRFS-26', 'NRNM-45']
def deleted_attachments = 0
def deleted_comments = 0
IssueManager issueManager = ComponentAccessor.issueManager
CommentManager commentManager = ComponentAccessor.commentManager
AttachmentManager attachmentManager = ComponentAccessor.attachmentManager
issueKeys.each{ issueKey ->
MutableIssue issue = issueManager.getIssueObject(issueKey)
List<Comment> comments = commentManager.getComments(issue)
comments.each {comment ->
if (comment.body.contains('550 5.1.1 The email account that you tried to reach does not exist')) {
log.info issueKey + " DELETE comment:"
//log.debug comment.body
commentManager.delete(comment)
deleted_comments++
} else {
log.info issueKey + " KEEP comment:"
log.debug comment.body
}
}
List<Attachment> attachments = attachmentManager.getAttachments(issue)
attachments.each {attachment ->
if (attachment.filename.equals('icon.png')) {
log.info issueKey + " DELETE attachment " + attachment.filename
attachmentManager.deleteAttachment(attachment)
deleted_attachments++
} else {
log.info issueKey + " KEEP attachment " + attachment.filename
}
}
}
log.info "${deleted_comments} deleted comments, and ${deleted_attachments} deleted attachments"
return "${deleted_comments} deleted comments, and ${deleted_attachments} deleted attachments"
我们有几个 JIRA 问题有超过 1000 条重复的、虚假的、类似垃圾邮件的评论。如何快速删除它们?
背景:
我们禁用了活动目录 (Exchange) 中的用户,但未禁用 JIRA,因此 JIRA 一直尝试通过电子邮件向他们发送更新。电子邮件服务器给出了一条退回消息,JIRA 尽职尽责地将其记录到任务中,导致它再次发送更新,反馈循环就此诞生。
消息的格式如下:
Delivery has failed to these recipients or groups:
mail@example.com<mail@example.com>
The e-mail address you entered couldn't be found. Please check the recipient's e-mail address and try to resend the message. If the problem continues, please contact your helpdesk.
Diagnostic information for administrators:
Generating server: emailserver.example.com
user@example.com
#550 5.1.1 RESOLVER.ADR.RecipNotFound; not found ##
Original message headers:
Received: from jiraserver.example.com (10.0.0.999) by emailserver.example.com (10.0.0.999)
with Microsoft SMTP Server id nn.n.nnn.n; Mon, 13 Jun 2016 15:57:04 -0500
Date: Mon, 13 Jun 2016 15:57:03 -0500
Our research did not discover an easy way without using purchased plug-ins such as Script Runner 或 "hacking" 我们想要避免的数据库。
注:
我们想出了一个解决方案,在此发布以供分享。
通过 Chrome JavaScript 控制台使用 JIRA REST API。
背景:
我们不想为我们希望的孤立事件编写完整的应用程序。我们最初计划使用 PowerShell 的 Invoke-WebRequest. However, authentication proved to be a challenge. The API supports Basic Authentication, though it's only recommended when using SSL,我们并未将其用于我们的内部服务器。此外,我们的初始测试导致 401 错误(可能是由于错误)。
但是,API 还支持 cookie-based 身份验证,因此只要您从具有有效 JIRA 会话的浏览器生成请求,它就可以正常工作。我们选择了那个方法。
解决方案详情:
首先,查找并查看相关评论和问题 ID:
SELECT * FROM jira..jiraaction WHERE actiontype = 'comment' AND actionbody LIKE '%RESOLVER.ADR.RecipNotFound%';
这可能是一个缓慢的查询,具体取决于您的 JIRA 数据的大小。它似乎在 issueid
上建立了索引,所以如果您知道,请指定它。此外,向该查询添加其他条件,使其仅代表您希望删除的评论。
下面的解决方案是为对单个问题发表评论而编写的,但可以扩展 JavaScript 以支持多个问题。
我们需要在 Chrome JavaScript 控制台中使用的评论 ID 列表。一种有用的格式是 comma-delimited 字符串列表,您可以按如下方式创建它:
SELECT '"' + CONVERT(VARCHAR(50),ID) + '", ' FROM jira..jiraaction WHERE actiontype = 'comment' AND actionbody LIKE '%RESOLVER.ADR.RecipNotFound%' AND issueid = @issueid FOR XML PATH('')
(这不一定是达到 concatenate strings in SQL 的最佳方式,但它很简单并且适用于此目的。)
现在,打开一个新的浏览器会话并向您的 JIRA 实例进行身份验证。我们使用 Chrome,但任何带有 JavaScript 控制台的浏览器都应该可以。
获取该查询生成的字符串并将其放入 JavaScript 控制台中的语句内,如下所示:
CommentIDs = [StringFromSQL];
您需要手动 trim 尾随逗号(或调整上述查询来为您执行此操作)。它看起来像这样:
CommentIDs = ["1234", "2345"];
当您 运行 该命令时,您将创建一个包含所有这些评论 ID 的 JavaScript 数组。
现在我们到达了技术的核心。我们将遍历该数组的内容并使用 XMLHttpRequest(通常缩写为 XHR)对 REST API 进行新的 AJAX 调用。 (还有一个jQuery option。)
for (let s of CommentIDs) {let r = new XMLHttpRequest; r.open("DELETE","http://jira.example.com/rest/api/2/issue/11111/comment/"+s,true); r.send();}
您必须将“11111”替换为相关问题 ID。您可以为多个问题 ID 重复此操作,或者您可以构建一个 multi-dimensional 数组和一个更高级的循环。
这不优雅。它没有任何错误处理,但您可以使用 Chrome JavaScript API.
监视进度我会使用 jira-python 脚本或 ScriptRunner groovy 脚本。即使是一次性批量更新,因为它更容易测试并且不需要数据库访问。
很高兴它对你有用!
我创建了一个 python 脚本来删除特定 Jira 问题的所有评论。 它使用来自 Jira 的 API。
'''
This script removes all comments from a specified jira issue
Please provide Jira-Issue-Key/Id, Jira-URL, Username, PAssword in the variables below.
'''
import sys
import json
import requests
import urllib3
# Jira Issue Key or Id where comments are deleted from
JIRA_ISSUE_KEY = 'TST-123'
# URL to Jira
URL_JIRA = 'https://jira.contoso.com'
# Username with enough rights to delete comments
JIRA_USERNAME = 'admin'
# Password to Jira User
JIRA_PASSWORD = 'S9ev6ZpQ4sy2VFH2_bjKKQAYRUlDfW7ujNnrIq9Lbn5w'
''' ----- ----- Do not change anything below ----- ----- '''
# Ignore SSL problem (certificate) - self signed
urllib3.disable_warnings()
# get issue comments:
# https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-issue-issueIdOrKey-comment-get
URL_GET_COMMENT = '{0}/rest/api/latest/issue/{1}/comment'.format(URL_JIRA, JIRA_ISSUE_KEY)
# delete issue comment:
# https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-issue-issueIdOrKey-comment-id-delete
URL_DELETE_COMMENT = '{0}/rest/api/2/issue/{1}/comment/{2}'
def user_yesno():
''' Asks user for input yes or no, responds with boolean '''
allowed_response_yes = {'yes', 'y'}
allowed_response_no = {'no', 'n'}
user_response = input().lower()
if user_response in allowed_response_yes:
return True
elif user_response in allowed_response_no:
return False
else:
sys.stdout.write("Please respond with 'yes' or 'no'")
return False
# get jira comments
RESPONSE = requests.get(URL_GET_COMMENT, verify=False, auth=(JIRA_USERNAME, JIRA_PASSWORD))
# check if http response is OK (200)
if RESPONSE.status_code != 200:
print('Exit-101: Could not connect to api [HTTP-Error: {0}]'.format(RESPONSE.status_code))
sys.exit(101)
# parse response to json
JSON_RESPONSE = json.loads(RESPONSE.text)
# get user confirmation to delete all comments for issue
print('You want to delete {0} comments for issue {1}? (yes/no)' \
.format(len(JSON_RESPONSE['comments']), JIRA_ISSUE_KEY))
if user_yesno():
for jira_comment in JSON_RESPONSE['comments']:
print('Deleting Jira comment {0}'.format(jira_comment['id']))
# send delete request
RESPONSE = requests.delete(
URL_DELETE_COMMENT.format(URL_JIRA, JIRA_ISSUE_KEY, jira_comment['id']),
verify=False, auth=(JIRA_USERNAME, JIRA_PASSWORD))
# check if http response is No Content (204)
if RESPONSE.status_code != 204:
print('Exit-102: Could not connect to api [HTTP-Error: {0}; {1}]' \
.format(RESPONSE.status_code, RESPONSE.text))
sys.exit(102)
else:
print('User abort script...')
源代码控制:https://gist.github.com/fty4/151ee7070f2a3f9da2cfa9b1ee1c132d
我们使用 ScriptRunner 和 Groovy 脚本解决了这个时常发生的问题:
// this script takes some time, when executing it in console, it takes a long time to repsonse, and then the console retunrs "null"
// - but it kepps running in the backgorund, give it some time - at least 1 second per comment and attachment to delete.
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.comments.Comment
import com.atlassian.jira.issue.comments.CommentManager
import com.atlassian.jira.issue.attachment.Attachment
import com.atlassian.jira.issue.managers.DefaultAttachmentManager
import com.atlassian.jira.issue.AttachmentManager
import org.apache.log4j.Logger
import org.apache.log4j.Level
log.setLevel(Level.DEBUG)
// NRS-1959
def issueKeys = ['XS-8071', 'XS-8060', 'XS-8065', 'XRFS-26', 'NRNM-45']
def deleted_attachments = 0
def deleted_comments = 0
IssueManager issueManager = ComponentAccessor.issueManager
CommentManager commentManager = ComponentAccessor.commentManager
AttachmentManager attachmentManager = ComponentAccessor.attachmentManager
issueKeys.each{ issueKey ->
MutableIssue issue = issueManager.getIssueObject(issueKey)
List<Comment> comments = commentManager.getComments(issue)
comments.each {comment ->
if (comment.body.contains('550 5.1.1 The email account that you tried to reach does not exist')) {
log.info issueKey + " DELETE comment:"
//log.debug comment.body
commentManager.delete(comment)
deleted_comments++
} else {
log.info issueKey + " KEEP comment:"
log.debug comment.body
}
}
List<Attachment> attachments = attachmentManager.getAttachments(issue)
attachments.each {attachment ->
if (attachment.filename.equals('icon.png')) {
log.info issueKey + " DELETE attachment " + attachment.filename
attachmentManager.deleteAttachment(attachment)
deleted_attachments++
} else {
log.info issueKey + " KEEP attachment " + attachment.filename
}
}
}
log.info "${deleted_comments} deleted comments, and ${deleted_attachments} deleted attachments"
return "${deleted_comments} deleted comments, and ${deleted_attachments} deleted attachments"