Python 第三方模块之 jira

Jira 提供了完善的 RESTful API,如果不想直接请求API接口可以使用Python 的 Jira 库来操作 Jira

安装

1
pip install jira

认证

Jira的访问是有权限的,在访问Jira项目时首先要进行认证,Jira Python库提供了3种认证方式:

  1. 通过Cookis方式认证(用户名,密码)
  2. 通过Basic Auth方式认证(用户名,密码)
  3. 通过OAuth方式认证

认证方式只需要选择一种即可,以下代码为使用Cookies方式认证。

1
2
3
form jira import JIRA

jira = JIRA('http://jira.***.com/', auth=('用户名', '登录密码')`

返回的jira对象便可以对Jira进行操作。主要的操作包括:

  1. 项目
  2. 问题
  3. 搜索
  4. 关注者
  5. 评论
  6. 附件

项目(Project)

  • jira.projects(): 查看所有项目列表
  • jira.project(“项目的Key”): 查看单个项目

项目对象的主要属性及方法如下:

  • key: 项目的Key
  • name: 项目名称
  • description: 项目描述
  • lead: 项目负责人
  • projectCategory: 项目分类
  • components: 项目组件
  • versions: 项目中的版本
  • raw: 项目的原始API数据
1
2
3
print(jira.projects())  # 打印所有你有权限访问的项目列表
project = jira.project('某个项目的Key')
print(project.key, project.name, project.lead)`

问题(Issue)

Issue是Jira的核心,Jira中的任务,用户Story,Bug实质上都是一个Issue。
单个问题对象可以通过jira.issue(“问题的Key”)得到,问题的主要属性和方法如下:

  • id: 问题的id
  • key: 问题的Key
  • permalink(): 获取问题连接
  • fields: 问题的描述,创建时间等所有的配置域
  • raw: 问题的原始API数据
1
issue = jira.issue('JRA-1330')

Fields

一般问题的Fields中的属性分为

  • 固定属性
  • 自定义属性,自定义属性格式一般为类似customfield_10012这种。

固定属性

  • project: 所示项目. dict:{“id”: “10000”} or {“key”: “project_key”}
  • issuetype: 问题类型. dict:{“id”: “11523”} or {“name”: ‘安全专项’}
  • summary: 问题描述. str:”something’s wrong”
  • priorit: 优先级. dict:{“name”: ‘Highest’} or {“id”: ‘1’}
  • assignee:经办人. dict:{“name”: “fenglepeng”} or {“name”: ‘-1’} -1 表示不指定
  • created: 创建时间
  • creator: 创建人
  • labels: 标签. list:[“bugfix”,”blitz_test”]
  • description: 描述. str:”description”
  • progress:
  • reporter: 报告人. dict: {“name”: “smithers”}
  • status: 状态
  • security: dict:{“id”: “10000”}
  • versions”: list:[{“id”: “10000”}],
  • environment”: str:”environment”,
  • worklog: 活动日志
  • updated: 更新时间
  • watches: 关注者
  • comments: 评论
  • resolution: 解决方案
  • subtasks: 子任务
  • issuelinks: 连接问题
  • lastViewed: 最近查看时间
  • attachment

自定义属性

自定义属性就是自己定义的,通常以 customfield_xxx 的形式出现,如 customfield_11453

属性分类

自定义属性和固定属性可以分为几类,参考

  • 单行text:is a single line of text. 如:Summary

    “summary”: “This is an example summary”

  • 多行text: is multiple lines of text. 如:Description

    “description”: “This is an example description with multiples lines of text\n separated by\n line feeds”

  • 单选name: allows a single user to be selected.

    “customfield_11453”: {“name”:”tommytomtomahawk” }

  • 多选name: multiple values addressed by ‘name’. 如:Components

    “components” : [{“name”: “Active Directory”}, { “name”: “Network Switch” }]
    “customfield_11458”: [{ “name”:”inigomontoya” }, {“name”:”tommytomtomahawk” }]

  • 日期: is a date in ‘YYYY-MM-DD’ format. 如: Due date

    “duedate”: “2015-11-18”
    “customfield_11441”: “2015-11-18”

  • 日期时间:is a date time in ISO 8601 YYYY-MM-DDThh:mm:ss.sTZD format.

    “customfield_11442”: “2015-11-18T14:39:00.000+1100”

  • 数字: contains a number.

    “customfield_11444” : 123

  • 单选: allows you to select a single value from a defined list of values. You can address them by value or by ID.

    “customfield_11445”: {“value”: “option2” }
    “customfield_11445”: {“id”: 10112 }

  • 级联选择: allows you to select a single parent value and then a related child value. You can address them by value or by ID.

    “customfield_11447”:{“value”: ‘河北’, “child”: {“value”: ‘石家庄’}}
    “customfield_11447”:{ “id”: 10112, “child”: {“id”: 10115 }}

  • 数组array: is an array of string values

    “customfield_12946”: [“篮球”, “羽毛球”],
    “labels” : [“examplelabelnumber1”, “examplelabelnumber2”]

  • 多选: allows you to select a multiple values from a defined list of values. You can address them by value or by ID.

    “customfield_11440”: [{“value” : “option1”}, {“value” : “option2”}]
    “customfield_11440”: [{“id” : 10112}, {“id” : 10115}]

  • URL: allows a URL to be entered.

    “customfield_11452”: “http://www.atlassian.com

创建issue

1
2
new_issue = jira.create_issue(project='PROJ_key_or_id', summary='New issue from jira-python',
description='Look into this one', issuetype={'name': 'Bug'})

或者你可以使用一个字典:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
data = {
'project': {'id': 123},
'summary': 'New issue from jira-python',
'description': 'Look into this one',
'issuetype': {'name': 'Bug'},
}

data = {
"project": {"key": project_key},
"issuetype": {"name": "安全专项"}, # 问题类型。安全专项:11523
# "issuetype": {"id": "11523"}, # 问题类型。安全专项:11523

"summary": "test概要", # 概要

"priority": {"id": "1"}, # 优先级Highest:1 --> lowest:5
# "priority": {"name": "Highest"}, # 优先级Highest:1 --> lowest:5

"assignee": {"name": "-1"}, # 经办人 自动:-1

# "customfield_11703": {"id": "11438", "1": "11446"}, # EBG产品族 koala 11438
"customfield_11703": {"value": "Koala", "child": {"value": "Koala-通行"}}, # EBG产品族 koala 11438, Koala-通行 11446

"customfield_13081": {"id": "14125"}, # 漏洞等级
# "customfield_13081": {"value": "严重"}, # 漏洞等级

"customfield_13078": {"value": "上线前安全测试"}, # 问题来源
"customfield_12492": {"value": "必然出现"}, # 复现概率
"customfield_12946": ["V1.2.0", "1.0.5"], # 影响版本
# "customfield_12946": [{"value":"V1.2.0"}, {"value":"1.0.5"}], # 影响版本
# "description": "test description", # 描述
}
new_issue = jira.create_issuse(data)

创建问题时始终需要项目、摘要、描述和问题类型。您的 Jira 可能需要额外的字段来创建问题;请参阅jira.createmeta获取该信息的方法。

批量创建多个 issue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
issue_list = [
{
'project': {'id': 123},
'summary': 'First issue of many',
'description': 'Look into this one',
'issuetype': {'name': 'Bug'},
},
{
'project': {'key': 'FOO'},
'summary': 'Second issue',
'description': 'Another one',
'issuetype': {'name': 'Bug'},
},
{
'project': {'name': 'Bar'},
'summary': 'Last issue',
'description': 'Final issue of batch.',
'issuetype': {'name': 'Bug'},
}]
issues = jira.create_issues(field_list=issue_list)

使用批量创建不会为创建失败的问题抛出异常。如果该问题具有无效字段,它将返回一个字典列表,每个字典都包含一个可能的错误签名。成功创建的问题将包含问题对象作为issue键的值。

获取 issue

1
2
3
4
5
issue = jira.issue('JRA-1330')

# issue JSON 被自动编组并用于扩充返回的问题对象,因此您可以直接访问字段:
summary = issue.fields.summary # 'Field level security permissions'
votes = issue.fields.votes.votes # 440 (at least)

如果您只需要几个特定字段,请通过明确询问它们来节省时间:

1
issue = jira.issue('JRA-1330', fields='summary,comment')

分配 issue

1
2
# requires issue assign permission, which is different from issue editing permission!
jira.assign_issue(issue, 'newassignee')

如果您想再次取消分配,只需执行以下操作:

1
jira.assign_issue(issue, None)

更新 issue

可以使用关键字参数更新问题的字段:

1
2
issue.update(summary='new summary', description='A new summary was added')
issue.update(assignee={'name': 'new_user'}) # reassigning in update requires issue edit permission

或使用新字段值的字典:

1
issue.update(fields={'summary': 'new summary', 'description': 'A new summary was added'})

您可以禁止通知:

1
issue.update(notify=False, description='A quiet description change was made')

删除 issue

1
issue.delete()

更新组件

1
2
3
4
existingComponents = []
for component in issue.fields.components:
existingComponents.append({"name" : component.name})
issue.update(fields={"components": existingComponents})

获取 issue 的工作流

  • jira.transitions(): 获取问题的工作流
  • jira.transition_issue(): 转换问题
1
2
# 转换问题
jira.transition_issue(issue, '5', assignee={'name': 'pm_user'}, resolution={'id': '3'})

Fields

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
issue.fields.worklog.worklogs                                 # list of Worklog objects
issue.fields.worklog.worklogs[0].author
issue.fields.worklog.worklogs[0].comment
issue.fields.worklog.worklogs[0].created
issue.fields.worklog.worklogs[0].id
issue.fields.worklog.worklogs[0].self
issue.fields.worklog.worklogs[0].started
issue.fields.worklog.worklogs[0].timeSpent
issue.fields.worklog.worklogs[0].timeSpentSeconds
issue.fields.worklog.worklogs[0].updateAuthor # dictionary
issue.fields.worklog.worklogs[0].updated


issue.fields.timetracking.remainingEstimate # may be NULL or string ("0m", "2h"...)
issue.fields.timetracking.remainingEstimateSeconds # may be NULL or integer
issue.fields.timetracking.timeSpent # may be NULL or string
issue.fields.timetracking.timeSpentSeconds # may be NULL or integer

issue.fields.status

附件(issue 中插入图片先上传附件,然后再修改相应字段)

附件允许用户将文件添加到 issue 中。首先,您需要将附件上传到相应的 issue。接下来,您需要将要作为附件的文件本身。该文件可以是类似文件的对象或字符串,表示本地机器上的路径。如果您不喜欢原始附件,也可以修改附件的最终名称。

1
2
3
4
5
6
7
8
9
10
11
12
# upload file from `/some/path/attachment.txt`
jira.add_attachment(issue=issue, attachment='/some/path/attachment.txt')

# read and upload a file (note binary mode for opening, it's important):
with open('/some/path/attachment.txt', 'rb') as f:
jira.add_attachment(issue=issue, attachment=f)

# attach file from memory (you can skip IO operations). In this case you MUST provide `filename`.
from io import StringIO
attachment = StringIO()
attachment.write(data)
jira.add_attachment(issue=issue, attachment=attachment, filename='content.txt')

列出所有可用的附件

1
2
3
4
5
for attachment in issue.fields.attachment:
print("Name: '{filename}', size: {size}".format(
filename=attachment.filename, size=attachment.size))
# to read content use `get` method:
print("Content: '{}'".format(attachment.get()))

您可以通过 id 删除附件:

1
2
3
4
5
6
7
8
# Find issues with attachments:
query = jira.search_issues(jql_str="attachments is not EMPTY", json_result=True, fields="key, attachment")

# And remove attachments one by one
for i in query['issues']:
for a in i['fields']['attachment']:
print("For issue {0}, found attach: '{1}' [{2}].".format(i['key'], a['filename'], a['id']))
jira.delete_attachment(a['id'])

description 字段增加图片

先把图片上传到附件,并返回 图片的名字

更新 description 字段。增加需要在两个叹点之间追加图片的名称或链接.

  • example:!http://www.host.com/image.gif! or !attached-image.gif!
    Inserts an image into the page.If a fully qualified URL is given the image will be displayed from the remote source, otherwise an attached image file is displayed.

  • example:!image.jpg|thumbnail!
    Insert a thumbnail of the image into the page (only works with images that are attached to the page).

  • example:!image.gif|align=right, vspace=4!
    For any image, you can also specify attributes of the image tag as a comma separated list of name=value pairs like so.

  • example: !image.gir|width=1000,height=600!

报错

  • 对于type是array的字段,提交时候报错”data was not an array”

    写法: “customfield_10449”: [{“name”: “1.0.0”}], 外面是列表,里面是字典

  • jira string expected at index 0

    写法: “customfield_12946”: [“V1.2.0”, “1.0.5”],

关注者/评论/附件

  • jira.watchers(): 问题的关注者
  • jira.add_watcher(): 添加关注者
  • jira.remove_watcher(): 移除关注者
  • jira.comments(): 问题的所有评论
  • jira.comment(): 某条评论
  • jira.add_comment():添加评论
  • comment.update()/delete(): 更新/删除评论
  • jira.add_attachment(): 添加附件

示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
issue = jira.issue('JRA-1330')

print(jiaa.watchers(issue)) # 所有关注者
jira.add_watcher(issue, 'username') # 添加关注者

print(jira.comments(issue)) # 所有评论
comment = jira.comment(issue, '10234') # 某条评论
jira.add_comment(issue, 'new comment') # 新增评论
comment.update(body='update comment') # 更新评论
comment.delete() # 删除该评论

print(issue.fields.attachment) # 问题附件
jira.add_attachment(issue=issue, attachment='/some/path/attachment.txt') # 添加附件

Python 第三方模块之 jira
https://flepeng.github.io/021-Python-31-Python-第三方模块-Python-第三方模块之-jira/
作者
Lepeng
发布于
2021年7月30日
许可协议