You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by kl...@apache.org on 2026/06/22 03:28:09 UTC

(devlake) branch main updated: feat(webhook): allow closing incidents via request body instead of url (#8939)

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 <bu...@gmail.com>
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,
 		},