This is an automated email from the ASF dual-hosted git repository.
klesh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/devlake.git
The following commit(s) were added to refs/heads/main by this push:
new f2de2dcb1 feat(webhook): allow closing incidents via request body
instead of url (#8939)
f2de2dcb1 is described below
commit f2de2dcb19cdb2af5b2613a2b939f50a33bfd209
Author: bujjibabukatta <[email protected]>
AuthorDate: Mon Jun 22 08:58:04 2026 +0530
feat(webhook): allow closing incidents via request body instead of url
(#8939)
---
backend/plugins/webhook/api/issues.go | 64 +++++++++++++++++++++++++++++++++++
backend/plugins/webhook/impl/impl.go | 9 +++++
2 files changed, 73 insertions(+)
diff --git a/backend/plugins/webhook/api/issues.go
b/backend/plugins/webhook/api/issues.go
index 0402be164..e280d4c9f 100644
--- a/backend/plugins/webhook/api/issues.go
+++ b/backend/plugins/webhook/api/issues.go
@@ -255,6 +255,42 @@ func CloseIssue(input *plugin.ApiResourceInput)
(*plugin.ApiResourceOutput, erro
return closeIssue(input, err, connection)
}
+// CloseIssueByBodyRequest is the body for the body-based close endpoint
+type CloseIssueByBodyRequest struct {
+ IssueKey string `mapstructure:"issueKey"
validate:"required,max=255"`
+ ResolutionDate *time.Time `mapstructure:"resolutionDate"`
+ OriginalStatus string `mapstructure:"originalStatus"`
+}
+
+// CloseIssueByBody
+// @Summary close an issue (body-based)
+// @Description Close an incident by passing issueKey in the request body.
+// @Description Use this when the client (e.g. Kibana) cannot construct a
dynamic URL.
+// @Tags plugins/webhook
+// @Param connectionId path int true "connection
ID"
+// @Param body body CloseIssueByBodyRequest true "close
request"
+// @Success 200 {string} noResponse ""
+// @Failure 400 {string} errcode.Error "Bad Request"
+// @Failure 500 {string} errcode.Error "Internal Error"
+// @Router /plugins/webhook/connections/{connectionId}/issue/close
[POST]
+func CloseIssueByBody(input *plugin.ApiResourceInput)
(*plugin.ApiResourceOutput, errors.Error) {
+ connection := &models.WebhookConnection{}
+ err := connectionHelper.First(connection, input.Params)
+ if err != nil {
+ return nil, err
+ }
+ request := &CloseIssueByBodyRequest{}
+ if err2 := helper.DecodeMapStruct(input.Body, request, true); err2 !=
nil {
+ return &plugin.ApiResourceOutput{Body: err2.Error(), Status:
http.StatusBadRequest}, nil
+ }
+ vld = validator.New()
+ if err2 := errors.Convert(vld.Struct(request)); err2 != nil {
+ return &plugin.ApiResourceOutput{Body: err2.Error(), Status:
http.StatusBadRequest}, nil
+ }
+ input.Params["issueKey"] = request.IssueKey
+ return closeIssue(input, err, connection)
+}
+
// CloseIssueByName
// @Summary set issue's status to DONE
// @Description set issue's status to DONE
@@ -269,6 +305,34 @@ func CloseIssueByName(input *plugin.ApiResourceInput)
(*plugin.ApiResourceOutput
return closeIssue(input, err, connection)
}
+// CloseIssueByBodyByName
+// @Summary close an issue by connection name (body-based)
+// @Description Close an incident using connection name + issueKey in request
body.
+// @Tags plugins/webhook
+// @Param connectionName path string true "connection
name"
+// @Param body body CloseIssueByBodyRequest true "close
request"
+// @Success 200 {string} noResponse ""
+// @Failure 400 {string} errcode.Error "Bad Request"
+// @Failure 500 {string} errcode.Error "Internal Error"
+// @Router
/plugins/webhook/connections/by-name/{connectionName}/issue/close [POST]
+func CloseIssueByBodyByName(input *plugin.ApiResourceInput)
(*plugin.ApiResourceOutput, errors.Error) {
+ connection := &models.WebhookConnection{}
+ err := connectionHelper.FirstByName(connection, input.Params)
+ if err != nil {
+ return nil, err
+ }
+ request := &CloseIssueByBodyRequest{}
+ if err2 := helper.DecodeMapStruct(input.Body, request, true); err2 !=
nil {
+ return &plugin.ApiResourceOutput{Body: err2.Error(), Status:
http.StatusBadRequest}, nil
+ }
+ vld = validator.New()
+ if err2 := errors.Convert(vld.Struct(request)); err2 != nil {
+ return &plugin.ApiResourceOutput{Body: err2.Error(), Status:
http.StatusBadRequest}, nil
+ }
+ input.Params["issueKey"] = request.IssueKey
+ return closeIssue(input, err, connection)
+}
+
func closeIssue(input *plugin.ApiResourceInput, err errors.Error, connection
*models.WebhookConnection) (*plugin.ApiResourceOutput, errors.Error) {
if err != nil {
return nil, err
diff --git a/backend/plugins/webhook/impl/impl.go
b/backend/plugins/webhook/impl/impl.go
index 93f2ac5e0..97dc59adc 100644
--- a/backend/plugins/webhook/impl/impl.go
+++ b/backend/plugins/webhook/impl/impl.go
@@ -99,6 +99,9 @@ func (p Webhook) ApiResources()
map[string]map[string]plugin.ApiResourceHandler
"connections/:connectionId/issue/:issueKey/close": {
"POST": api.CloseIssue,
},
+ "connections/:connectionId/issue/close": {
+ "POST": api.CloseIssueByBody,
+ },
":connectionId/deployments": {
"POST": api.PostDeployments,
},
@@ -111,6 +114,9 @@ func (p Webhook) ApiResources()
map[string]map[string]plugin.ApiResourceHandler
":connectionId/issue/:issueKey/close": {
"POST": api.CloseIssue,
},
+ ":connectionId/issue/close": {
+ "POST": api.CloseIssueByBody,
+ },
"connections/by-name/:connectionName": {
"GET": api.GetConnectionByName,
"PATCH": api.PatchConnectionByName,
@@ -128,6 +134,9 @@ func (p Webhook) ApiResources()
map[string]map[string]plugin.ApiResourceHandler
"connections/by-name/:connectionName/issue/:issueKey/close": {
"POST": api.CloseIssueByName,
},
+ "connections/by-name/:connectionName/issue/close": {
+ "POST": api.CloseIssueByBodyByName,
+ },
"projects/:projectName/deployments": {
"POST": api.PostDeploymentsByProjectName,
},