Skip to content

Commit

Permalink
feat(webhook): Support for Webhook Name in Webhook Endpoints (#8203)
Browse files Browse the repository at this point in the history
* feat(webhooks): add by-name methods to connection helper

* feat(webhooks): add connection endpoints by name

* feat(webhooks): add post deployment endpoint by name

* feat(webhooks): add issue endpoints by name
  • Loading branch information
rodrigoluizs authored Nov 22, 2024
1 parent 3d61b78 commit 5ec1d32
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 2 deletions.
27 changes: 26 additions & 1 deletion backend/helpers/pluginhelper/api/connection_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,20 @@ func (c *ConnectionApiHelper) Patch(connection interface{}, input *plugin.ApiRes
return c.save(connection, c.db.CreateOrUpdate)
}

// First finds connection from db by parsing request input and decrypt it
// PatchByName (Modify) a connection record based on request body by connection name
func (c *ConnectionApiHelper) PatchByName(connection interface{}, input *plugin.ApiResourceInput) errors.Error {
err := c.FirstByName(connection, input.Params)
if err != nil {
return err
}
err = c.merge(connection, input.Body)
if err != nil {
return err
}
return c.save(connection, c.db.CreateOrUpdate)
}

// First finds connection from db by id, parsing request input and decrypt it
func (c *ConnectionApiHelper) First(connection interface{}, params map[string]string) errors.Error {
connectionId := params["connectionId"]
if connectionId == "" {
Expand All @@ -117,6 +130,18 @@ func (c *ConnectionApiHelper) FirstById(connection interface{}, id uint64) error
return CallDB(c.db.First, connection, dal.Where("id = ?", id))
}

// FirstByName finds connection from db by name, parsing request input and decrypting it
func (c *ConnectionApiHelper) FirstByName(connection interface{}, params map[string]string) errors.Error {
connectionName := params["connectionName"]
if connectionName == "" {
return errors.BadInput.New("missing connectionName")
}
if len(connectionName) > 100 {
return errors.BadInput.New("invalid connectionName")
}
return CallDB(c.db.First, connection, dal.Where("name = ?", connectionName))
}

// List returns all connections with password/token decrypted
func (c *ConnectionApiHelper) List(connections interface{}) errors.Error {
return CallDB(c.db.All, connections)
Expand Down
63 changes: 62 additions & 1 deletion backend/plugins/webhook/api/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,24 @@ func PatchConnection(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput,
return &plugin.ApiResourceOutput{Body: connection}, nil
}

// PatchConnectionByName
// @Summary patch webhook connection by name
// @Description Patch webhook connection
// @Tags plugins/webhook
// @Param body body models.WebhookConnection true "json body"
// @Success 200 {object} models.WebhookConnection
// @Failure 400 {string} errcode.Error "Bad Request"
// @Failure 500 {string} errcode.Error "Internal Error"
// @Router /plugins/webhook/connections/by-name/{connectionName} [PATCH]
func PatchConnectionByName(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
connection := &models.WebhookConnection{}
err := connectionHelper.PatchByName(connection, input)
if err != nil {
return nil, err
}
return &plugin.ApiResourceOutput{Body: connection}, nil
}

// DeleteConnection
// @Summary delete a webhook connection
// @Description Delete a webhook connection
Expand All @@ -107,7 +125,32 @@ func PatchConnection(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput,
// @Failure 500 {string} errcode.Error "Internal Error"
// @Router /plugins/webhook/connections/{connectionId} [DELETE]
func DeleteConnection(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
connectionId, e := strconv.ParseInt(input.Params["connectionId"], 10, 64)
connectionId, e := strconv.ParseUint(input.Params["connectionId"], 10, 64)
return deleteConnection(e, connectionId)
}

// DeleteConnectionByName
// @Summary delete a webhook connection by name
// @Description Delete a webhook connection
// @Tags plugins/webhook
// @Success 200 {object} models.WebhookConnection
// @Failure 400 {string} errcode.Error "Bad Request"
// @Failure 409 {object} services.BlueprintProjectPairs "References exist to this connection"
// @Failure 500 {string} errcode.Error "Internal Error"
// @Router /plugins/webhook/connections/by-name/{connectionName} [DELETE]
func DeleteConnectionByName(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
connection := &models.WebhookConnection{}
err := connectionHelper.FirstByName(connection, input.Params)

if err != nil {
logger.Error(err, "query connection")
return nil, err
}

return deleteConnection(nil, connection.ConnectionId())
}

func deleteConnection(e error, connectionId uint64) (*plugin.ApiResourceOutput, errors.Error) {
if e != nil {
return nil, errors.BadInput.WrapRaw(e)
}
Expand Down Expand Up @@ -183,6 +226,24 @@ func ListConnections(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput,
func GetConnection(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
connection := &models.WebhookConnection{}
err := connectionHelper.First(connection, input.Params)
return getConnection(err, connection)
}

// GetConnectionByName
// @Summary get webhook connection detail by name
// @Description Get webhook connection detail
// @Tags plugins/webhook
// @Success 200 {object} WebhookConnectionResponse
// @Failure 400 {string} errcode.Error "Bad Request"
// @Failure 500 {string} errcode.Error "Internal Error"
// @Router /plugins/webhook/connections/by-name/{connectionName} [GET]
func GetConnectionByName(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
connection := &models.WebhookConnection{}
err := connectionHelper.FirstByName(connection, input.Params)
return getConnection(err, connection)
}

func getConnection(err errors.Error, connection *models.WebhookConnection) (*plugin.ApiResourceOutput, errors.Error) {
if err != nil {
logger.Error(err, "query connection")
return nil, err
Expand Down
25 changes: 25 additions & 0 deletions backend/plugins/webhook/api/deployments.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,31 @@ type WebhookDeploymentCommitReq struct {
func PostDeployments(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
connection := &models.WebhookConnection{}
err := connectionHelper.First(connection, input.Params)

return postDeployments(input, connection, err)
}

// PostDeploymentsByName
// @Summary create deployment by webhook name
// @Description Create deployment pipeline by webhook name.<br/>
// @Description example1: {"repo_url":"devlake","commit_sha":"015e3d3b480e417aede5a1293bd61de9b0fd051d","start_time":"2020-01-01T12:00:00+00:00","end_time":"2020-01-01T12:59:59+00:00","environment":"PRODUCTION"}<br/>
// @Description So we suggest request before task after deployment pipeline finish.
// @Description Both cicd_pipeline and cicd_task will be created
// @Tags plugins/webhook
// @Param body body WebhookDeploymentReq true "json body"
// @Success 200
// @Failure 400 {string} errcode.Error "Bad Request"
// @Failure 403 {string} errcode.Error "Forbidden"
// @Failure 500 {string} errcode.Error "Internal Error"
// @Router /plugins/webhook/connections/by-name/:connectionName/deployments [POST]
func PostDeploymentsByName(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
connection := &models.WebhookConnection{}
err := connectionHelper.FirstByName(connection, input.Params)

return postDeployments(input, connection, err)
}

func postDeployments(input *plugin.ApiResourceInput, connection *models.WebhookConnection, err errors.Error) (*plugin.ApiResourceOutput, errors.Error) {
if err != nil {
return nil, err
}
Expand Down
37 changes: 37 additions & 0 deletions backend/plugins/webhook/api/issues.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,25 @@ func saveIncidentRelatedRecordsFromIssue(db dal.Transaction, logger log.Logger,
func PostIssue(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
connection := &models.WebhookConnection{}
err := connectionHelper.First(connection, input.Params)
return postIssue(input, err, connection)
}

// PostIssueByName
// @Summary receive a record as defined and save it
// @Description receive a record as follow and save it, example: {"url":"","issue_key":"DLK-1234","title":"a feature from DLK","description":"","epic_key":"","type":"BUG","status":"TODO","original_status":"created","story_point":0,"resolution_date":null,"created_date":"2020-01-01T12:00:00+00:00","updated_date":null,"lead_time_minutes":0,"parent_issue_key":"DLK-1200","priority":"","original_estimate_minutes":0,"time_spent_minutes":0,"time_remaining_minutes":0,"creator_id":"user1131","creator_name":"Nick name 1","assignee_id":"user1132","assignee_name":"Nick name 2","severity":"","component":""}
// @Tags plugins/webhook
// @Param body body WebhookIssueRequest true "json body"
// @Success 200 {string} noResponse ""
// @Failure 400 {string} errcode.Error "Bad Request"
// @Failure 500 {string} errcode.Error "Internal Error"
// @Router /plugins/webhook/by-name/:connectionName/issues [POST]
func PostIssueByName(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
connection := &models.WebhookConnection{}
err := connectionHelper.FirstByName(connection, input.Params)
return postIssue(input, err, connection)
}

func postIssue(input *plugin.ApiResourceInput, err errors.Error, connection *models.WebhookConnection) (*plugin.ApiResourceOutput, errors.Error) {
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -212,6 +231,24 @@ func PostIssue(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, error
func CloseIssue(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
connection := &models.WebhookConnection{}
err := connectionHelper.First(connection, input.Params)
return closeIssue(input, err, connection)
}

// CloseIssueByName
// @Summary set issue's status to DONE
// @Description set issue's status to DONE
// @Tags plugins/webhook
// @Success 200 {string} noResponse ""
// @Failure 400 {string} errcode.Error "Bad Request"
// @Failure 500 {string} errcode.Error "Internal Error"
// @Router /plugins/webhook/by-name/:connectionName/issue/:issueKey/close [POST]
func CloseIssueByName(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
connection := &models.WebhookConnection{}
err := connectionHelper.FirstByName(connection, input.Params)
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
}
Expand Down
14 changes: 14 additions & 0 deletions backend/plugins/webhook/impl/impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,5 +110,19 @@ func (p Webhook) ApiResources() map[string]map[string]plugin.ApiResourceHandler
":connectionId/issue/:issueKey/close": {
"POST": api.CloseIssue,
},
"connections/by-name/:connectionName": {
"GET": api.GetConnectionByName,
"PATCH": api.PatchConnectionByName,
"DELETE": api.DeleteConnectionByName,
},
"connections/by-name/:connectionName/deployments": {
"POST": api.PostDeploymentsByName,
},
"connections/by-name/:connectionName/issues": {
"POST": api.PostIssueByName,
},
"connections/by-name/:connectionName/issue/:issueKey/close": {
"POST": api.CloseIssueByName,
},
}
}

0 comments on commit 5ec1d32

Please sign in to comment.