Jira 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:
查看推送数据:
请求头
请求头,可以看到是通用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":"[email protected]","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":"[email protected]","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":"[email protected]","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}}","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,多了流程状态的数据。
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":"[email protected]""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":"[email protected]","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":"[email protected]","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}}","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的二次开发来实现的功能。
版权归原作者 阿特蓝胖 所有, 如有侵权,请联系我们删除。