Jira Webhook配置与使用
阿特蓝胖 2024-09-15 16:03:06 阅读 71
Jira Webhook配置与使用
什么是Jira Webhook(网络钩子)创建Webhook使用Webhook通过Webhook创建时设置的事件来触发通过工作流来触发Webhook
总结一下
什么是Jira Webhook(网络钩子)
Webhook是一种回调接口,一般用在由事件驱动的系统中。那么Jira内置的功能是支持自定义Webhook的,然后在特定的事件或者工作流执行中调用自己定义的Webhook,来实现与第三方系统的通信,并且Webhook中提供了几乎与Issue相关的所有字段信息。
创建Webhook
使用系统管员登录,进入系统设置页面,点击左侧“网络钩子菜单”,进入网络钩子管理页面,点击右上角“创建网络钩子”按钮,添加一个新的Webhook。
在这里,我们使用一个在线工具来测试打印出webhook推送给第三方系统的数据,UU在线工具https://uutool.cn/mock/。
名称:自己取个名字就好了
状态:可以启用或禁用掉Webhook,因为在实际工作中Webhook可以在很多流程里面去触发,所以这里可以禁用掉就不用去修改每个流程了
URL:这里填写的就是第三方系统的接口地址,我这里填写的是UU在线工具的临时接口地址,这个接口是一个挡板能把所有的请求信息打印出来。
事件:这里我们定义在创建和修改Issue时去触发这个Webhook的执行,注意这里也可以不选任何的事件,而是由工作流中我们去触发这个Webhook,事件的主体有Issue、项目、用户、备注还有敏捷相关的看板等等。注意这里选的事件类型与数据主体信息是相对应的,比如选Issue的事件,Raw里面的json数据就是issue相关的;如果选择的是用户事件,那么Raw里面在json主体就是用户信息
排除主体:有些事件发生时我们只需要少量的字段我们可以通过在URL的变量中携带而不必传输整个Raw数据,这时我们勾选排除主体就可以了。
使用Webhook
通过Webhook创建时设置的事件来触发
刚才我们定义的Webhook是在Issue创建和修改的时候触发的,那么我们现在创建一个Issue观察一下Webhook推送的数据。
创建Issue:
查看推送数据:
请求头
<code>请求头,可以看到是通用HttpClient的库发送的请求
{
"Content-Type": "application/json; charset=UTF-8",
"User-Agent": "Atlassian HttpClient 3.0.4 / JIRA-9.13.1 (9130002) / Default",
"Connection": "Keep-Alive",
"Host": "mock.uutool.cn",
"Content-Length": "7137",
"Via": "1.1 localhost (Apache-HttpClient/4.5.14 (cache))",
"Accept": "*/*"
}
数据结构
Issue的Webhook数据结构中包括了用户、项目、报告人、Issue字段等的信息,如果在第三方系统需要其它信息都可以通过自定义字段去放置。
数据位置和结构,可以看到Webhook在GET参数中带了用户名,具体信息在请求体中通过JSON结构传输的。
GET参数:
{
"user_id": "admin",
"user_key": "JIRAUSER10000"
}
POST参数:
[]
Raw:
{
"timestamp": 1725868650247,
"webhookEvent": "jira:issue_created",
"issue_event_type_name": "issue_created",
"user": {
"self": "http://192.168.1.100:8080/rest/api/2/user?username=admin",
"name": "admin",
"key": "JIRAUSER10000",
"emailAddress": "admin@abdxww.co",
"avatarUrls": {
"48x48": "http://192.168.1.100:8080/secure/useravatar?avatarId=10351",
"24x24": "http://192.168.1.100:8080/secure/useravatar?size=small&avatarId=10351",
"16x16": "http://192.168.1.100:8080/secure/useravatar?size=xsmall&avatarId=10351",
"32x32": "http://192.168.1.100:8080/secure/useravatar?size=medium&avatarId=10351"
},
"displayName": "admin",
"active": true,
"timeZone": "Asia/Shanghai"
},
"issue": {
"id": "11104",
"self": "http://192.168.1.100:8080/rest/api/2/issue/11104",
"key": "TEST-17",
"fields": {
"issuetype": {
"self": "http://192.168.1.100:8080/rest/api/2/issuetype/10006",
"id": "10006",
"description": "",
"iconUrl": "http://192.168.1.100:8080/secure/viewavatar?size=xsmall&avatarId=10303&avatarType=issuetype",
"name": "故障",
"subtask": false,
"avatarId": 10303
},
"timespent": null,
"project": {
"self": "http://192.168.1.100:8080/rest/api/2/project/10200",
"id": "10200",
"key": "TEST",
"name": "TEST1",
"projectTypeKey": "software",
"avatarUrls": {
"48x48": "http://192.168.1.100:8080/secure/projectavatar?avatarId=10324",
"24x24": "http://192.168.1.100:8080/secure/projectavatar?size=small&avatarId=10324",
"16x16": "http://192.168.1.100:8080/secure/projectavatar?size=xsmall&avatarId=10324",
"32x32": "http://192.168.1.100:8080/secure/projectavatar?size=medium&avatarId=10324"
}
},
"fixVersions": [],
"customfield_10110": null,
"aggregatetimespent": null,
"resolution": null,
"customfield_10104": null,
"customfield_10108": "0|i005h3:",
"customfield_10109": null,
"resolutiondate": null,
"workratio": -1,
"lastViewed": null,
"watches": {
"self": "http://192.168.1.100:8080/rest/api/2/issue/TEST-17/watchers",
"watchCount": 0,
"isWatching": false
},
"created": "2024-09-09T15:57:30.142+0800",
"priority": {
"self": "http://192.168.1.100:8080/rest/api/2/priority/3",
"iconUrl": "http://192.168.1.100:8080/images/icons/priorities/medium.svg",
"name": "Medium",
"id": "3"
},
"customfield_10100": null,
"customfield_10101": null,
"customfield_10102": null,
"customfield_10300": null,
"labels": [],
"customfield_10103": null,
"timeestimate": null,
"aggregatetimeoriginalestimate": null,
"versions": [],
"issuelinks": [],
"assignee": null,
"updated": "2024-09-09T15:57:30.142+0800",
"status": {
"self": "http://192.168.1.100:8080/rest/api/2/status/10000",
"description": "",
"iconUrl": "http://192.168.1.100:8080/",
"name": "待办",
"id": "10000",
"statusCategory": {
"self": "http://192.168.1.100:8080/rest/api/2/statuscategory/2",
"id": 2,
"key": "new",
"colorName": "default",
"name": "待办"
}
},
"components": [],
"timeoriginalestimate": null,
"description": null,
"timetracking": { },
"archiveddate": null,
"customfield_10401": null,
"customfield_10203": null,
"attachment": [],
"aggregatetimeestimate": null,
"summary": "test webhook1",
"creator": {
"self": "http://192.168.1.100:8080/rest/api/2/user?username=admin",
"name": "admin",
"key": "JIRAUSER10000",
"emailAddress": "admin@abdxww.co",
"avatarUrls": {
"48x48": "http://192.168.1.100:8080/secure/useravatar?avatarId=10351",
"24x24": "http://192.168.1.100:8080/secure/useravatar?size=small&avatarId=10351",
"16x16": "http://192.168.1.100:8080/secure/useravatar?size=xsmall&avatarId=10351",
"32x32": "http://192.168.1.100:8080/secure/useravatar?size=medium&avatarId=10351"
},
"displayName": "admin",
"active": true,
"timeZone": "Asia/Shanghai"
},
"subtasks": [],
"reporter": {
"self": "http://192.168.1.100:8080/rest/api/2/user?username=admin",
"name": "admin",
"key": "JIRAUSER10000",
"emailAddress": "admin@abdxww.co",
"avatarUrls": {
"48x48": "http://192.168.1.100:8080/secure/useravatar?avatarId=10351",
"24x24": "http://192.168.1.100:8080/secure/useravatar?size=small&avatarId=10351",
"16x16": "http://192.168.1.100:8080/secure/useravatar?size=xsmall&avatarId=10351",
"32x32": "http://192.168.1.100:8080/secure/useravatar?size=medium&avatarId=10351"
},
"displayName": "admin",
"active": true,
"timeZone": "Asia/Shanghai"
},
"customfield_10000": "{summaryBean=com.atlassian.jira.plugin.devstatus.rest.SummaryBean@7ae0bb11[summary={pullrequest=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@f3a1dea[byInstanceType={},overall=PullRequestOverallBean{stateCount=0, state='OPEN', details=PullRequestOverallDetails{openCount=0, mergedCount=0, declinedCount=0}}], build=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@4a6368d[byInstanceType={},overall=com.atlassian.jira.plugin.devstatus.summary.beans.BuildOverallBean@5b2caebe[failedBuildCount=0,successfulBuildCount=0,unknownBuildCount=0,count=0,lastUpdated=<null>,lastUpdatedTimestamp=<null>]], review=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@4a735e4b[byInstanceType={},overall=com.atlassian.jira.plugin.devstatus.summary.beans.ReviewsOverallBean@38f61afc[dueDate=<null>,overDue=false,state=<null>,stateCount=0,count=0,lastUpdated=<null>,lastUpdatedTimestamp=<null>]], deployment-environment=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@dd14131[byInstanceType={},overall=com.atlassian.jira.plugin.devstatus.summary.beans.DeploymentOverallBean@33b6576b[showProjects=false,successfulCount=0,topEnvironments=[],count=0,lastUpdated=<null>,lastUpdatedTimestamp=<null>]], repository=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@63e7c650[byInstanceType={},overall=com.atlassian.jira.plugin.devstatus.summary.beans.CommitOverallBean@6bce112a[count=0,lastUpdated=<null>,lastUpdatedTimestamp=<null>]], branch=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@6c110dab[byInstanceType={},overall=com.atlassian.jira.plugin.devstatus.summary.beans.BranchOverallBean@6d1d024d[count=0,lastUpdated=<null>,lastUpdatedTimestamp=<null>]]},configErrors=[],errors=[]], devSummaryJson={\"cachedValue\":{\"errors\":[],\"configErrors\":[],\"summary\":{\"pullrequest\":{\"overall\":{\"count\":0,\"lastUpdated\":null,\"stateCount\":0,\"state\":\"OPEN\",\"details\":{\"openCount\":0,\"mergedCount\":0,\"declinedCount\":0,\"total\":0},\"open\":true},\"byInstanceType\":{}},\"build\":{\"overall\":{\"count\":0,\"lastUpdated\":null,\"failedBuildCount\":0,\"successfulBuildCount\":0,\"unknownBuildCount\":0},\"byInstanceType\":{}},\"review\":{\"overall\":{\"count\":0,\"lastUpdated\":null,\"stateCount\":0,\"state\":null,\"dueDate\":null,\"overDue\":false,\"completed\":false},\"byInstanceType\":{}},\"deployment-environment\":{\"overall\":{\"count\":0,\"lastUpdated\":null,\"topEnvironments\":[],\"showProjects\":false,\"successfulCount\":0},\"byInstanceType\":{}},\"repository\":{\"overall\":{\"count\":0,\"lastUpdated\":null},\"byInstanceType\":{}},\"branch\":{\"overall\":{\"count\":0,\"lastUpdated\":null},\"byInstanceType\":{}}}},\"isStale\":false}}",code>
"aggregateprogress": {
"progress": 0,
"total": 0
},
"customfield_10200": null,
"customfield_10201": null,
"customfield_10400": null,
"customfield_10202": null,
"environment": null,
"duedate": null,
"progress": {
"progress": 0,
"total": 0
},
"comment": {
"comments": [],
"maxResults": 0,
"total": 0,
"startAt": 0
},
"votes": {
"self": "http://192.168.1.100:8080/rest/api/2/issue/TEST-17/votes",
"votes": 0,
"hasVoted": false
},
"worklog": {
"startAt": 0,
"maxResults": 20,
"total": 0,
"worklogs": []
},
"archivedby": null
}
}
}
通过工作流来触发Webhook
我们创建的Webhook,不勾选任何事件,然后在流程中去触发。
修改项目D35问题类型是故障的工作流,在完成的转换中添加后处理功能,去触发刚才定义的Webhook。
点击后处理功能:
点击添加后处理功能:
点击引发一个webhook,选择刚才定义的webhook。
在D35-1这个Issue中点击完成。
我们可以看到通过流程触发的webhook的数据主体还是Issue,多了流程状态的数据。
<code>GET和POST外的其他方法见Raw数据:
GET参数:
{
"user_id": "admin",
"user_key": "JIRAUSER10000"
}
POST参数:
[]
Raw:
{
"transition": {
"workflowId": 11105,
"workflowName": "Software Simplified Workflow for Project D35",
"transitionId": 31,
"transitionName": "Done",
"from_status": "To Do",
"to_status": "Done"
},
"comment": "",
"user": {
"self": "http://192.168.1.100:8080/rest/api/2/user?username=admin",
"name": "admin",
"key": "JIRAUSER10000",
"emailAddress": "admin@abdxww.co"
"avatarUrls": {
"48x48": "http://192.168.1.100:8080/secure/useravatar?avatarId=10351",
"24x24": "http://192.168.1.100:8080/secure/useravatar?size=small&avatarId=10351",
"16x16": "http://192.168.1.100:8080/secure/useravatar?size=xsmall&avatarId=10351",
"32x32": "http://192.168.1.100:8080/secure/useravatar?size=medium&avatarId=10351"
},
"displayName": "admin",
"active": true,
"timeZone": "Asia/Shanghai"
},
"issue": {
"id": "11105",
"self": "http://192.168.1.100:8080/rest/api/2/issue/11105",
"key": "D35-1",
"fields": {
"issuetype": {
"self": "http://192.168.1.100:8080/rest/api/2/issuetype/10006",
"id": "10006",
"description": "",
"iconUrl": "http://192.168.1.100:8080/secure/viewavatar?size=xsmall&avatarId=10303&avatarType=issuetype",
"name": "故障",
"subtask": false,
"avatarId": 10303
},
"timespent": null,
"project": {
"self": "http://192.168.1.100:8080/rest/api/2/project/10302",
"id": "10302",
"key": "D35",
"name": "D35",
"projectTypeKey": "software",
"avatarUrls": {
"48x48": "http://192.168.1.100:8080/secure/projectavatar?avatarId=10324",
"24x24": "http://192.168.1.100:8080/secure/projectavatar?size=small&avatarId=10324",
"16x16": "http://192.168.1.100:8080/secure/projectavatar?size=xsmall&avatarId=10324",
"32x32": "http://192.168.1.100:8080/secure/projectavatar?size=medium&avatarId=10324"
}
},
"fixVersions": [],
"customfield_10110": null,
"aggregatetimespent": null,
"resolution": null,
"customfield_10104": null,
"customfield_10108": "0|i005hb:",
"customfield_10109": null,
"resolutiondate": null,
"workratio": -1,
"lastViewed": "2024-09-09T16:45:38.601+0800",
"watches": {
"self": "http://192.168.1.100:8080/rest/api/2/issue/D35-1/watchers",
"watchCount": 1,
"isWatching": true
},
"created": "2024-09-09T16:35:50.000+0800",
"priority": {
"self": "http://192.168.1.100:8080/rest/api/2/priority/3",
"iconUrl": "http://192.168.1.100:8080/images/icons/priorities/medium.svg",
"name": "Medium",
"id": "3"
},
"customfield_10100": null,
"customfield_10101": null,
"customfield_10102": null,
"customfield_10300": null,
"labels": [],
"customfield_10103": null,
"timeestimate": null,
"aggregatetimeoriginalestimate": null,
"versions": [],
"issuelinks": [],
"assignee": null,
"updated": "2024-09-09T16:44:11.000+0800",
"status": {
"self": "http://192.168.1.100:8080/rest/api/2/status/10000",
"description": "",
"iconUrl": "http://192.168.1.100:8080/",
"name": "待办",
"id": "10000",
"statusCategory": {
"self": "http://192.168.1.100:8080/rest/api/2/statuscategory/2",
"id": 2,
"key": "new",
"colorName": "default",
"name": "待办"
}
},
"components": [],
"timeoriginalestimate": null,
"description": null,
"timetracking": { },
"archiveddate": null,
"customfield_10401": null,
"customfield_10203": null,
"attachment": [],
"aggregatetimeestimate": null,
"summary": "bug1",
"creator": {
"self": "http://192.168.1.100:8080/rest/api/2/user?username=admin",
"name": "admin",
"key": "JIRAUSER10000",
"emailAddress": "admin@abdxww.co",
"avatarUrls": {
"48x48": "http://192.168.1.100:8080/secure/useravatar?avatarId=10351",
"24x24": "http://192.168.1.100:8080/secure/useravatar?size=small&avatarId=10351",
"16x16": "http://192.168.1.100:8080/secure/useravatar?size=xsmall&avatarId=10351",
"32x32": "http://192.168.1.100:8080/secure/useravatar?size=medium&avatarId=10351"
},
"displayName": "admin",
"active": true,
"timeZone": "Asia/Shanghai"
},
"subtasks": [],
"reporter": {
"self": "http://192.168.1.100:8080/rest/api/2/user?username=admin",
"name": "admin",
"key": "JIRAUSER10000",
"emailAddress": "admin@abdxww.co",
"avatarUrls": {
"48x48": "http://192.168.1.100:8080/secure/useravatar?avatarId=10351",
"24x24": "http://192.168.1.100:8080/secure/useravatar?size=small&avatarId=10351",
"16x16": "http://192.168.1.100:8080/secure/useravatar?size=xsmall&avatarId=10351",
"32x32": "http://192.168.1.100:8080/secure/useravatar?size=medium&avatarId=10351"
},
"displayName": "admin",
"active": true,
"timeZone": "Asia/Shanghai"
},
"customfield_10000": "{summaryBean=com.atlassian.jira.plugin.devstatus.rest.SummaryBean@46fa0a08[summary={pullrequest=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@4c98c539[byInstanceType={},overall=PullRequestOverallBean{stateCount=0, state='OPEN', details=PullRequestOverallDetails{openCount=0, mergedCount=0, declinedCount=0}}], build=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@734de769[byInstanceType={},overall=com.atlassian.jira.plugin.devstatus.summary.beans.BuildOverallBean@79f64534[failedBuildCount=0,successfulBuildCount=0,unknownBuildCount=0,count=0,lastUpdated=<null>,lastUpdatedTimestamp=<null>]], review=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@780f02[byInstanceType={},overall=com.atlassian.jira.plugin.devstatus.summary.beans.ReviewsOverallBean@6d38812b[dueDate=<null>,overDue=false,state=<null>,stateCount=0,count=0,lastUpdated=<null>,lastUpdatedTimestamp=<null>]], deployment-environment=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@71d2454a[byInstanceType={},overall=com.atlassian.jira.plugin.devstatus.summary.beans.DeploymentOverallBean@3a4671a1[showProjects=false,successfulCount=0,topEnvironments=[],count=0,lastUpdated=<null>,lastUpdatedTimestamp=<null>]], repository=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@7a0669f4[byInstanceType={},overall=com.atlassian.jira.plugin.devstatus.summary.beans.CommitOverallBean@531e8e46[count=0,lastUpdated=<null>,lastUpdatedTimestamp=<null>]], branch=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@3005697a[byInstanceType={},overall=com.atlassian.jira.plugin.devstatus.summary.beans.BranchOverallBean@39c7cda2[count=0,lastUpdated=<null>,lastUpdatedTimestamp=<null>]]},configErrors=[],errors=[]], devSummaryJson={\"cachedValue\":{\"errors\":[],\"configErrors\":[],\"summary\":{\"pullrequest\":{\"overall\":{\"count\":0,\"lastUpdated\":null,\"stateCount\":0,\"state\":\"OPEN\",\"details\":{\"openCount\":0,\"mergedCount\":0,\"declinedCount\":0,\"total\":0},\"open\":true},\"byInstanceType\":{}},\"build\":{\"overall\":{\"count\":0,\"lastUpdated\":null,\"failedBuildCount\":0,\"successfulBuildCount\":0,\"unknownBuildCount\":0},\"byInstanceType\":{}},\"review\":{\"overall\":{\"count\":0,\"lastUpdated\":null,\"stateCount\":0,\"state\":null,\"dueDate\":null,\"overDue\":false,\"completed\":false},\"byInstanceType\":{}},\"deployment-environment\":{\"overall\":{\"count\":0,\"lastUpdated\":null,\"topEnvironments\":[],\"showProjects\":false,\"successfulCount\":0},\"byInstanceType\":{}},\"repository\":{\"overall\":{\"count\":0,\"lastUpdated\":null},\"byInstanceType\":{}},\"branch\":{\"overall\":{\"count\":0,\"lastUpdated\":null},\"byInstanceType\":{}}}},\"isStale\":false}}",code>
"aggregateprogress": {
"progress": 0,
"total": 0
},
"customfield_10200": null,
"customfield_10201": null,
"customfield_10400": null,
"customfield_10202": null,
"environment": null,
"duedate": null,
"progress": {
"progress": 0,
"total": 0
},
"comment": {
"comments": [],
"maxResults": 0,
"total": 0,
"startAt": 0
},
"votes": {
"self": "http://192.168.1.100:8080/rest/api/2/issue/D35-1/votes",
"votes": 0,
"hasVoted": false
},
"worklog": {
"startAt": 0,
"maxResults": 20,
"total": 0,
"worklogs": []
},
"archivedby": null
}
},
"timestamp": 1725871538608
}
总结一下
Jira的Webhook定义及使用都非常灵活,几乎可以Jira侧不进行二次开发来实现与第三方系统对接的所有功能。如果再结合Scriptrunner可以实现与第三方系统之间双向通信,来实现复杂的业务逻辑,这些都是不需要通Jira的二次开发来实现的功能。
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。