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"