|
| 1 | +/* |
| 2 | +Licensed to the Apache Software Foundation (ASF) under one or more |
| 3 | +contributor license agreements. See the NOTICE file distributed with |
| 4 | +this work for additional information regarding copyright ownership. |
| 5 | +The ASF licenses this file to You under the Apache License, Version 2.0 |
| 6 | +(the "License"); you may not use this file except in compliance with |
| 7 | +the License. You may obtain a copy of the License at |
| 8 | +
|
| 9 | + http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | +
|
| 11 | +Unless required by applicable law or agreed to in writing, software |
| 12 | +distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | +See the License for the specific language governing permissions and |
| 15 | +limitations under the License. |
| 16 | +*/ |
| 17 | + |
| 18 | +package api |
| 19 | + |
| 20 | +import ( |
| 21 | + "fmt" |
| 22 | + "net/http" |
| 23 | + "time" |
| 24 | + |
| 25 | + "github.com/apache/incubator-devlake/core/dal" |
| 26 | + "github.com/apache/incubator-devlake/core/log" |
| 27 | + |
| 28 | + "github.com/apache/incubator-devlake/helpers/dbhelper" |
| 29 | + "github.com/go-playground/validator/v10" |
| 30 | + |
| 31 | + "github.com/apache/incubator-devlake/core/errors" |
| 32 | + "github.com/apache/incubator-devlake/core/models/domainlayer" |
| 33 | + "github.com/apache/incubator-devlake/core/models/domainlayer/code" |
| 34 | + "github.com/apache/incubator-devlake/core/plugin" |
| 35 | + "github.com/apache/incubator-devlake/helpers/pluginhelper/api" |
| 36 | + "github.com/apache/incubator-devlake/plugins/webhook/models" |
| 37 | +) |
| 38 | + |
| 39 | +type WebhookPullRequestReq struct { |
| 40 | + Id string `mapstructure:"id" validate:"required"` |
| 41 | + BaseRepoId string `mapstructure:"baseRepoId"` |
| 42 | + HeadRepoId string `mapstructure:"headRepoId"` |
| 43 | + Status string `mapstructure:"status" validate:"omitempty,oneof=OPEN CLOSED MERGED"` |
| 44 | + OriginalStatus string `mapstructure:"originalStatus"` |
| 45 | + Title string `mapstructure:"displayTitle" validate:"required"` |
| 46 | + Description string `mapstructure:"description"` |
| 47 | + Url string `mapstructure:"url"` |
| 48 | + AuthorName string `mapstructure:"authorName"` |
| 49 | + AuthorId string `mapstructure:"authorId"` |
| 50 | + MergedByName string `mapstructure:"mergedByName"` |
| 51 | + MergedById string `mapstructure:"mergedById"` |
| 52 | + ParentPrId string `mapstructure:"parentPrId"` |
| 53 | + PullRequestKey int `mapstructure:"pullRequestKey" validate:"required"` |
| 54 | + CreatedDate time.Time `mapstructure:"createdDate" validate:"required"` |
| 55 | + MergedDate *time.Time `mapstructure:"mergedDate"` |
| 56 | + ClosedDate *time.Time `mapstructure:"closedDate"` |
| 57 | + Type string `mapstructure:"type"` |
| 58 | + Component string `mapstructure:"component"` |
| 59 | + MergeCommitSha string `mapstructure:"mergeCommitSha"` |
| 60 | + HeadRef string `mapstructure:"headRef"` |
| 61 | + BaseRef string `mapstructure:"baseRef"` |
| 62 | + BaseCommitSha string `mapstructure:"baseCommitSha"` |
| 63 | + HeadCommitSha string `mapstructure:"headCommitSha"` |
| 64 | + Additions int `mapstructure:"additions"` |
| 65 | + Deletions int `mapstructure:"deletions"` |
| 66 | + IsDraft bool `mapstructure:"isDraft"` |
| 67 | +} |
| 68 | + |
| 69 | +// PostPullRequests |
| 70 | +// @Summary create pull requests by webhook |
| 71 | +// @Description Create pull request by webhook.<br/> |
| 72 | +// @Description example1: {"id": "pr1","baseRepoId": "webhook:1","headRepoId": "repo_fork1","status": "MERGED","originalStatus": "OPEN","displayTitle": "Feature: Add new functionality","description": "This PR adds new features","url": "https://github.com/org/repo/pull/1","authorName": "johndoe","authorId": "johnd123","mergedByName": "janedoe","mergedById": "janed123","parentPrId": "","pullRequestKey": 1,"createdDate": "2025-02-20T16:17:36Z","mergedDate": "2025-02-20T17:17:36Z","closedDate": null,"type": "feature","component": "backend","mergeCommitSha": "bf0a79c57dff8f5f1f393de315ee5105a535e059","headRef": "repo_fork1:feature-branch","baseRef": "main","baseCommitSha": "e73325c2c9863f42ea25871cbfaeebcb8edcf604","headCommitSha": "b22f772f1197edfafd4cc5fe679a2d299ec12837","additions": 100,"deletions": 50,"isDraft": false}<br/> |
| 73 | +// @Description "baseRepoId" must be equal to "webhook:{connectionId}" for this to work correctly and calculate DORA metrics |
| 74 | +// @Tags plugins/webhook |
| 75 | +// @Param body body WebhookPullRequestReq true "json body" |
| 76 | +// @Success 200 |
| 77 | +// @Failure 400 {string} errcode.Error "Bad Request" |
| 78 | +// @Failure 403 {string} errcode.Error "Forbidden" |
| 79 | +// @Failure 500 {string} errcode.Error "Internal Error" |
| 80 | +// @Router /plugins/webhook/connections/:connectionId/pullrequests [POST] |
| 81 | +func PostPullRequests(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) { |
| 82 | + connection := &models.WebhookConnection{} |
| 83 | + err := connectionHelper.First(connection, input.Params) |
| 84 | + |
| 85 | + return postPullRequests(input, connection, err) |
| 86 | +} |
| 87 | + |
| 88 | +// PostPullRequestsByName |
| 89 | +// @Summary create pull requests by webhook name |
| 90 | +// @Description Create pull request by webhook name.<br/> |
| 91 | +// @Description example1: {"id": "pr1","baseRepoId": "webhook:1","headRepoId": "repo_fork1","status": "MERGED","originalStatus": "OPEN","displayTitle": "Feature: Add new functionality","description": "This PR adds new features","url": "https://github.com/org/repo/pull/1","authorName": "johndoe","authorId": "johnd123","mergedByName": "janedoe","mergedById": "janed123","parentPrId": "","pullRequestKey": 1,"createdDate": "2025-02-20T16:17:36Z","mergedDate": "2025-02-20T17:17:36Z","closedDate": null,"type": "feature","component": "backend","mergeCommitSha": "bf0a79c57dff8f5f1f393de315ee5105a535e059","headRef": "repo_fork1:feature-branch","baseRef": "main","baseCommitSha": "e73325c2c9863f42ea25871cbfaeebcb8edcf604","headCommitSha": "b22f772f1197edfafd4cc5fe679a2d299ec12837","additions": 100,"deletions": 50,"isDraft": false}<br/> |
| 92 | +// @Description "baseRepoId" must be equal to "webhook:{connectionId}" for this to work correctly and calculate DORA metrics |
| 93 | +// @Tags plugins/webhook |
| 94 | +// @Param body body WebhookPullRequestReq true "json body" |
| 95 | +// @Success 200 |
| 96 | +// @Failure 400 {string} errcode.Error "Bad Request" |
| 97 | +// @Failure 403 {string} errcode.Error "Forbidden" |
| 98 | +// @Failure 500 {string} errcode.Error "Internal Error" |
| 99 | +// @Router /plugins/webhook/connections/by-name/:connectionName/pullrequests [POST] |
| 100 | +func PostPullRequestsByName(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) { |
| 101 | + connection := &models.WebhookConnection{} |
| 102 | + err := connectionHelper.FirstByName(connection, input.Params) |
| 103 | + |
| 104 | + return postPullRequests(input, connection, err) |
| 105 | +} |
| 106 | + |
| 107 | +func postPullRequests(input *plugin.ApiResourceInput, connection *models.WebhookConnection, err errors.Error) (*plugin.ApiResourceOutput, errors.Error) { |
| 108 | + if err != nil { |
| 109 | + return nil, err |
| 110 | + } |
| 111 | + // get request |
| 112 | + request := &WebhookPullRequestReq{} |
| 113 | + err = api.DecodeMapStruct(input.Body, request, true) |
| 114 | + if err != nil { |
| 115 | + return &plugin.ApiResourceOutput{Body: err.Error(), Status: http.StatusBadRequest}, nil |
| 116 | + } |
| 117 | + // validate |
| 118 | + vld = validator.New() |
| 119 | + err = errors.Convert(vld.Struct(request)) |
| 120 | + if err != nil { |
| 121 | + return nil, errors.BadInput.Wrap(vld.Struct(request), `input json error`) |
| 122 | + } |
| 123 | + txHelper := dbhelper.NewTxHelper(basicRes, &err) |
| 124 | + defer txHelper.End() |
| 125 | + tx := txHelper.Begin() |
| 126 | + if err := CreatePullRequest(connection, request, tx, logger); err != nil { |
| 127 | + logger.Error(err, "create pull requests") |
| 128 | + return nil, err |
| 129 | + } |
| 130 | + |
| 131 | + return &plugin.ApiResourceOutput{Body: nil, Status: http.StatusOK}, nil |
| 132 | +} |
| 133 | + |
| 134 | +func CreatePullRequest(connection *models.WebhookConnection, request *WebhookPullRequestReq, tx dal.Transaction, logger log.Logger) errors.Error { |
| 135 | + // validation |
| 136 | + if request == nil { |
| 137 | + return errors.BadInput.New("request body is nil") |
| 138 | + } |
| 139 | + // create a pull_request record |
| 140 | + pullRequest := &code.PullRequest{ |
| 141 | + DomainEntity: domainlayer.DomainEntity{ |
| 142 | + Id: fmt.Sprintf("%s:%d:%d", "webhook", connection.ID, request.PullRequestKey), |
| 143 | + }, |
| 144 | + BaseRepoId: fmt.Sprintf("%s:%d", "webhook", connection.ID), |
| 145 | + HeadRepoId: request.HeadRepoId, |
| 146 | + Status: request.Status, |
| 147 | + OriginalStatus: request.OriginalStatus, |
| 148 | + Title: request.Title, |
| 149 | + Description: request.Description, |
| 150 | + Url: request.Url, |
| 151 | + AuthorName: request.AuthorName, |
| 152 | + AuthorId: request.AuthorId, |
| 153 | + MergedByName: request.MergedByName, |
| 154 | + MergedById: request.MergedById, |
| 155 | + ParentPrId: request.ParentPrId, |
| 156 | + PullRequestKey: request.PullRequestKey, |
| 157 | + CreatedDate: request.CreatedDate, |
| 158 | + MergedDate: request.MergedDate, |
| 159 | + ClosedDate: request.ClosedDate, |
| 160 | + Type: request.Type, |
| 161 | + Component: request.Component, |
| 162 | + MergeCommitSha: request.MergeCommitSha, |
| 163 | + HeadRef: request.HeadRef, |
| 164 | + BaseRef: request.BaseRef, |
| 165 | + BaseCommitSha: request.BaseCommitSha, |
| 166 | + HeadCommitSha: request.HeadCommitSha, |
| 167 | + Additions: request.Additions, |
| 168 | + Deletions: request.Deletions, |
| 169 | + IsDraft: request.IsDraft, |
| 170 | + } |
| 171 | + if err := tx.CreateOrUpdate(pullRequest); err != nil { |
| 172 | + logger.Error(err, "failed to save pull request") |
| 173 | + return err |
| 174 | + } |
| 175 | + return nil |
| 176 | +} |
0 commit comments