New webhook trigger for receiving Pull Request review requests (#24481)
close https://github.com/go-gitea/gitea/issues/16321 Provided a webhook trigger for requesting someone to review the Pull Request. Some modifications have been made to the returned `PullRequestPayload` based on the GitHub webhook settings, including: - add a description of the current reviewer object as `RequestedReviewer` . - setting the action to either **review_requested** or **review_request_removed** based on the operation. - adding the `RequestedReviewers` field to the issues_model.PullRequest. This field will be loaded into the PullRequest through `LoadRequestedReviewers()` when `ToAPIPullRequest` is called. After the Pull Request is merged, I will supplement the relevant documentation.
This commit is contained in:
parent
93c6a9a652
commit
309354c70e
|
@ -178,6 +178,7 @@ type PullRequest struct {
|
|||
IssueID int64 `xorm:"INDEX"`
|
||||
Issue *Issue `xorm:"-"`
|
||||
Index int64
|
||||
RequestedReviewers []*user_model.User `xorm:"-"`
|
||||
|
||||
HeadRepoID int64 `xorm:"INDEX"`
|
||||
HeadRepo *repo_model.Repository `xorm:"-"`
|
||||
|
@ -302,6 +303,29 @@ func (pr *PullRequest) LoadHeadRepo(ctx context.Context) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
// LoadRequestedReviewers loads the requested reviewers.
|
||||
func (pr *PullRequest) LoadRequestedReviewers(ctx context.Context) error {
|
||||
if len(pr.RequestedReviewers) > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
reviews, err := GetReviewsByIssueID(pr.Issue.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(reviews) > 0 {
|
||||
err = LoadReviewers(ctx, reviews)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, review := range reviews {
|
||||
pr.RequestedReviewers = append(pr.RequestedReviewers, review.Reviewer)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadBaseRepo loads the target repository. ErrRepoNotExist may be returned.
|
||||
func (pr *PullRequest) LoadBaseRepo(ctx context.Context) (err error) {
|
||||
if pr.BaseRepo != nil {
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
@ -74,6 +75,34 @@ func TestPullRequestsNewest(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestLoadRequestedReviewers(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1})
|
||||
assert.NoError(t, pull.LoadIssue(db.DefaultContext))
|
||||
issue := pull.Issue
|
||||
assert.NoError(t, issue.LoadRepo(db.DefaultContext))
|
||||
assert.Len(t, pull.RequestedReviewers, 0)
|
||||
|
||||
user1, err := user_model.GetUserByID(db.DefaultContext, 1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
comment, err := issues_model.AddReviewRequest(issue, user1, &user_model.User{})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, comment)
|
||||
|
||||
assert.NoError(t, pull.LoadRequestedReviewers(db.DefaultContext))
|
||||
assert.Len(t, pull.RequestedReviewers, 1)
|
||||
|
||||
comment, err = issues_model.RemoveReviewRequest(issue, user1, &user_model.User{})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, comment)
|
||||
|
||||
pull.RequestedReviewers = nil
|
||||
assert.NoError(t, pull.LoadRequestedReviewers(db.DefaultContext))
|
||||
assert.Empty(t, pull.RequestedReviewers)
|
||||
}
|
||||
|
||||
func TestPullRequestsOldest(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
prs, count, err := issues_model.PullRequests(1, &issues_model.PullRequestsOptions{
|
||||
|
|
|
@ -162,6 +162,27 @@ func (r *Review) LoadReviewer(ctx context.Context) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
// LoadReviewers loads reviewers
|
||||
func LoadReviewers(ctx context.Context, reviews []*Review) (err error) {
|
||||
reviewerIds := make([]int64, len(reviews))
|
||||
for i := 0; i < len(reviews); i++ {
|
||||
reviewerIds[i] = reviews[i].ReviewerID
|
||||
}
|
||||
reviewers, err := user_model.GetPossibleUserByIDs(ctx, reviewerIds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userMap := make(map[int64]*user_model.User, len(reviewers))
|
||||
for _, reviewer := range reviewers {
|
||||
userMap[reviewer.ID] = reviewer
|
||||
}
|
||||
for _, review := range reviews {
|
||||
review.Reviewer = userMap[review.ReviewerID]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadReviewerTeam loads reviewer team
|
||||
func (r *Review) LoadReviewerTeam(ctx context.Context) (err error) {
|
||||
if r.ReviewerTeamID == 0 || r.ReviewerTeam != nil {
|
||||
|
@ -520,8 +541,8 @@ func GetReviews(ctx context.Context, opts *GetReviewOptions) ([]*Review, error)
|
|||
return reviews, sess.Find(&reviews)
|
||||
}
|
||||
|
||||
// GetReviewersByIssueID gets the latest review of each reviewer for a pull request
|
||||
func GetReviewersByIssueID(issueID int64) ([]*Review, error) {
|
||||
// GetReviewsByIssueID gets the latest review of each reviewer for a pull request
|
||||
func GetReviewsByIssueID(issueID int64) ([]*Review, error) {
|
||||
reviews := make([]*Review, 0, 10)
|
||||
|
||||
sess := db.GetEngine(db.DefaultContext)
|
||||
|
|
|
@ -132,11 +132,22 @@ func TestGetReviewersByIssueID(t *testing.T) {
|
|||
UpdatedUnix: 946684814,
|
||||
})
|
||||
|
||||
allReviews, err := issues_model.GetReviewersByIssueID(issue.ID)
|
||||
for _, reviewer := range allReviews {
|
||||
assert.NoError(t, reviewer.LoadReviewer(db.DefaultContext))
|
||||
}
|
||||
allReviews, err := issues_model.GetReviewsByIssueID(issue.ID)
|
||||
assert.NoError(t, err)
|
||||
for _, review := range allReviews {
|
||||
assert.NoError(t, review.LoadReviewer(db.DefaultContext))
|
||||
}
|
||||
if assert.Len(t, allReviews, 3) {
|
||||
for i, review := range allReviews {
|
||||
assert.Equal(t, expectedReviews[i].Reviewer, review.Reviewer)
|
||||
assert.Equal(t, expectedReviews[i].Type, review.Type)
|
||||
assert.Equal(t, expectedReviews[i].UpdatedUnix, review.UpdatedUnix)
|
||||
}
|
||||
}
|
||||
|
||||
allReviews, err = issues_model.GetReviewsByIssueID(issue.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, issues_model.LoadReviewers(db.DefaultContext, allReviews))
|
||||
if assert.Len(t, allReviews, 3) {
|
||||
for i, review := range allReviews {
|
||||
assert.Equal(t, expectedReviews[i].Reviewer, review.Reviewer)
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/auth/openid"
|
||||
"code.gitea.io/gitea/modules/auth/password/hash"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
@ -910,6 +911,15 @@ func GetUserByID(ctx context.Context, id int64) (*User, error) {
|
|||
return u, nil
|
||||
}
|
||||
|
||||
// GetUserByIDs returns the user objects by given IDs if exists.
|
||||
func GetUserByIDs(ctx context.Context, ids []int64) ([]*User, error) {
|
||||
users := make([]*User, 0, len(ids))
|
||||
err := db.GetEngine(ctx).In("id", ids).
|
||||
Table("user").
|
||||
Find(&users)
|
||||
return users, err
|
||||
}
|
||||
|
||||
// GetPossibleUserByID returns the user if id > 0 or return system usrs if id < 0
|
||||
func GetPossibleUserByID(ctx context.Context, id int64) (*User, error) {
|
||||
switch id {
|
||||
|
@ -924,6 +934,25 @@ func GetPossibleUserByID(ctx context.Context, id int64) (*User, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// GetPossibleUserByIDs returns the users if id > 0 or return system users if id < 0
|
||||
func GetPossibleUserByIDs(ctx context.Context, ids []int64) ([]*User, error) {
|
||||
uniqueIDs := container.SetOf(ids...)
|
||||
users := make([]*User, 0, len(ids))
|
||||
_ = uniqueIDs.Remove(0)
|
||||
if uniqueIDs.Remove(-1) {
|
||||
users = append(users, NewGhostUser())
|
||||
}
|
||||
if uniqueIDs.Remove(ActionsUserID) {
|
||||
users = append(users, NewActionsUser())
|
||||
}
|
||||
res, err := GetUserByIDs(ctx, uniqueIDs.Values())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
users = append(users, res...)
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// GetUserByNameCtx returns user by given name.
|
||||
func GetUserByName(ctx context.Context, name string) (*User, error) {
|
||||
if len(name) == 0 {
|
||||
|
|
|
@ -298,6 +298,12 @@ func (w *Webhook) HasPackageEvent() bool {
|
|||
(w.ChooseEvents && w.HookEvents.Package)
|
||||
}
|
||||
|
||||
// HasPullRequestReviewRequestEvent returns true if hook enabled pull request review request event.
|
||||
func (w *Webhook) HasPullRequestReviewRequestEvent() bool {
|
||||
return w.SendEverything ||
|
||||
(w.ChooseEvents && w.HookEvents.PullRequestReviewRequest)
|
||||
}
|
||||
|
||||
// EventCheckers returns event checkers
|
||||
func (w *Webhook) EventCheckers() []struct {
|
||||
Has func() bool
|
||||
|
@ -329,6 +335,7 @@ func (w *Webhook) EventCheckers() []struct {
|
|||
{w.HasRepositoryEvent, webhook_module.HookEventRepository},
|
||||
{w.HasReleaseEvent, webhook_module.HookEventRelease},
|
||||
{w.HasPackageEvent, webhook_module.HookEventPackage},
|
||||
{w.HasPullRequestReviewRequestEvent, webhook_module.HookEventPullRequestReviewRequest},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ func TestWebhook_EventsArray(t *testing.T) {
|
|||
"pull_request", "pull_request_assign", "pull_request_label", "pull_request_milestone",
|
||||
"pull_request_comment", "pull_request_review_approved", "pull_request_review_rejected",
|
||||
"pull_request_review_comment", "pull_request_sync", "wiki", "repository", "release",
|
||||
"package",
|
||||
"package", "pull_request_review_request",
|
||||
},
|
||||
(&Webhook{
|
||||
HookEvent: &webhook_module.HookEvent{SendEverything: true},
|
||||
|
|
|
@ -342,6 +342,10 @@ const (
|
|||
HookIssueDemilestoned HookIssueAction = "demilestoned"
|
||||
// HookIssueReviewed is an issue action for when a pull request is reviewed
|
||||
HookIssueReviewed HookIssueAction = "reviewed"
|
||||
// HookIssueReviewRequested is an issue action for when a reviewer is requested for a pull request.
|
||||
HookIssueReviewRequested HookIssueAction = "review_requested"
|
||||
// HookIssueReviewRequestRemoved is an issue action for removing a review request to someone on a pull request.
|
||||
HookIssueReviewRequestRemoved HookIssueAction = "review_request_removed"
|
||||
)
|
||||
|
||||
// IssuePayload represents the payload information that is sent along with an issue event.
|
||||
|
@ -385,6 +389,7 @@ type PullRequestPayload struct {
|
|||
Index int64 `json:"number"`
|
||||
Changes *ChangesPayload `json:"changes,omitempty"`
|
||||
PullRequest *PullRequest `json:"pull_request"`
|
||||
RequestedReviewer *User `json:"requested_reviewer"`
|
||||
Repository *Repository `json:"repository"`
|
||||
Sender *User `json:"sender"`
|
||||
CommitID string `json:"commit_id"`
|
||||
|
|
|
@ -19,6 +19,7 @@ type PullRequest struct {
|
|||
Milestone *Milestone `json:"milestone"`
|
||||
Assignee *User `json:"assignee"`
|
||||
Assignees []*User `json:"assignees"`
|
||||
RequestedReviewers []*User `json:"requested_reviewers"`
|
||||
State StateType `json:"state"`
|
||||
IsLocked bool `json:"is_locked"`
|
||||
Comments int `json:"comments"`
|
||||
|
|
|
@ -21,6 +21,7 @@ type HookEvents struct {
|
|||
PullRequestComment bool `json:"pull_request_comment"`
|
||||
PullRequestReview bool `json:"pull_request_review"`
|
||||
PullRequestSync bool `json:"pull_request_sync"`
|
||||
PullRequestReviewRequest bool `json:"pull_request_review_request"`
|
||||
Wiki bool `json:"wiki"`
|
||||
Repository bool `json:"repository"`
|
||||
Release bool `json:"release"`
|
||||
|
|
|
@ -26,6 +26,7 @@ const (
|
|||
HookEventPullRequestReviewRejected HookEventType = "pull_request_review_rejected"
|
||||
HookEventPullRequestReviewComment HookEventType = "pull_request_review_comment"
|
||||
HookEventPullRequestSync HookEventType = "pull_request_sync"
|
||||
HookEventPullRequestReviewRequest HookEventType = "pull_request_review_request"
|
||||
HookEventWiki HookEventType = "wiki"
|
||||
HookEventRepository HookEventType = "repository"
|
||||
HookEventRelease HookEventType = "release"
|
||||
|
@ -46,7 +47,7 @@ func (h HookEventType) Event() string {
|
|||
case HookEventIssues, HookEventIssueAssign, HookEventIssueLabel, HookEventIssueMilestone:
|
||||
return "issues"
|
||||
case HookEventPullRequest, HookEventPullRequestAssign, HookEventPullRequestLabel, HookEventPullRequestMilestone,
|
||||
HookEventPullRequestSync:
|
||||
HookEventPullRequestSync, HookEventPullRequestReviewRequest:
|
||||
return "pull_request"
|
||||
case HookEventIssueComment, HookEventPullRequestComment:
|
||||
return "issue_comment"
|
||||
|
|
|
@ -2118,6 +2118,8 @@ settings.event_pull_request_review = Pull Request Reviewed
|
|||
settings.event_pull_request_review_desc = Pull request approved, rejected, or review comment.
|
||||
settings.event_pull_request_sync = Pull Request Synchronized
|
||||
settings.event_pull_request_sync_desc = Pull request synchronized.
|
||||
settings.event_pull_request_review_request = Pull Request Review Requested
|
||||
settings.event_pull_request_review_request_desc = Pull request review requested or review request removed.
|
||||
settings.event_pull_request_approvals = Pull Request Approvals
|
||||
settings.event_pull_request_merge = Pull Request Merge
|
||||
settings.event_package = Package
|
||||
|
|
|
@ -194,6 +194,7 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoI
|
|||
PullRequestMilestone: pullHook(form.Events, string(webhook_module.HookEventPullRequestMilestone)),
|
||||
PullRequestComment: pullHook(form.Events, string(webhook_module.HookEventPullRequestComment)),
|
||||
PullRequestReview: pullHook(form.Events, "pull_request_review"),
|
||||
PullRequestReviewRequest: pullHook(form.Events, string(webhook_module.HookEventPullRequestReviewRequest)),
|
||||
PullRequestSync: pullHook(form.Events, string(webhook_module.HookEventPullRequestSync)),
|
||||
Wiki: util.SliceContainsString(form.Events, string(webhook_module.HookEventWiki), true),
|
||||
Repository: util.SliceContainsString(form.Events, string(webhook_module.HookEventRepository), true),
|
||||
|
@ -379,6 +380,7 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh
|
|||
w.PullRequestMilestone = pullHook(form.Events, string(webhook_module.HookEventPullRequestMilestone))
|
||||
w.PullRequestComment = pullHook(form.Events, string(webhook_module.HookEventPullRequestComment))
|
||||
w.PullRequestReview = pullHook(form.Events, "pull_request_review")
|
||||
w.PullRequestReviewRequest = pullHook(form.Events, string(webhook_module.HookEventPullRequestReviewRequest))
|
||||
w.PullRequestSync = pullHook(form.Events, string(webhook_module.HookEventPullRequestSync))
|
||||
|
||||
if err := w.UpdateEvent(); err != nil {
|
||||
|
|
|
@ -576,7 +576,7 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is
|
|||
}
|
||||
ctx.Data["OriginalReviews"] = originalAuthorReviews
|
||||
|
||||
reviews, err := issues_model.GetReviewersByIssueID(issue.ID)
|
||||
reviews, err := issues_model.GetReviewsByIssueID(issue.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetReviewersByIssueID", err)
|
||||
return
|
||||
|
|
|
@ -177,6 +177,7 @@ func ParseHookEvent(form forms.WebhookForm) *webhook_module.HookEvent {
|
|||
PullRequestComment: form.PullRequestComment,
|
||||
PullRequestReview: form.PullRequestReview,
|
||||
PullRequestSync: form.PullRequestSync,
|
||||
PullRequestReviewRequest: form.PullRequestReviewRequest,
|
||||
Wiki: form.Wiki,
|
||||
Repository: form.Repository,
|
||||
Package: form.Package,
|
||||
|
|
|
@ -88,6 +88,14 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u
|
|||
},
|
||||
}
|
||||
|
||||
if err = pr.LoadRequestedReviewers(ctx); err != nil {
|
||||
log.Error("LoadRequestedReviewers[%d]: %v", pr.ID, err)
|
||||
return nil
|
||||
}
|
||||
for _, reviewer := range pr.RequestedReviewers {
|
||||
apiPullRequest.RequestedReviewers = append(apiPullRequest.RequestedReviewers, ToUser(ctx, reviewer, nil))
|
||||
}
|
||||
|
||||
if pr.Issue.ClosedUnix != 0 {
|
||||
apiPullRequest.Closed = pr.Issue.ClosedUnix.AsTimePtr()
|
||||
}
|
||||
|
|
|
@ -246,6 +246,7 @@ type WebhookForm struct {
|
|||
PullRequestComment bool
|
||||
PullRequestReview bool
|
||||
PullRequestSync bool
|
||||
PullRequestReviewRequest bool
|
||||
Wiki bool
|
||||
Repository bool
|
||||
Package bool
|
||||
|
|
|
@ -719,6 +719,34 @@ func (m *webhookNotifier) NotifyPullRequestReview(ctx context.Context, pr *issue
|
|||
}
|
||||
}
|
||||
|
||||
func (m *webhookNotifier) NotifyPullReviewRequest(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, reviewer *user_model.User, isRequest bool, comment *issues_model.Comment) {
|
||||
if !issue.IsPull {
|
||||
log.Warn("NotifyPullReviewRequest: issue is not a pull request: %v", issue.ID)
|
||||
return
|
||||
}
|
||||
mode, _ := access_model.AccessLevelUnit(ctx, doer, issue.Repo, unit.TypePullRequests)
|
||||
if err := issue.LoadPullRequest(ctx); err != nil {
|
||||
log.Error("LoadPullRequest failed: %v", err)
|
||||
return
|
||||
}
|
||||
apiPullRequest := &api.PullRequestPayload{
|
||||
Index: issue.Index,
|
||||
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil),
|
||||
RequestedReviewer: convert.ToUser(ctx, reviewer, nil),
|
||||
Repository: convert.ToRepo(ctx, issue.Repo, mode),
|
||||
Sender: convert.ToUser(ctx, doer, nil),
|
||||
}
|
||||
if isRequest {
|
||||
apiPullRequest.Action = api.HookIssueReviewRequested
|
||||
} else {
|
||||
apiPullRequest.Action = api.HookIssueReviewRequestRemoved
|
||||
}
|
||||
if err := PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventPullRequestReviewRequest, apiPullRequest); err != nil {
|
||||
log.Error("PrepareWebhooks [review_requested: %v]: %v", isRequest, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (m *webhookNotifier) NotifyCreateRef(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, refType, refFullName, refID string) {
|
||||
apiPusher := convert.ToUser(ctx, pusher, nil)
|
||||
apiRepo := convert.ToRepo(ctx, repo, perm.AccessModeNone)
|
||||
|
|
|
@ -43,7 +43,7 @@ func convertPayloader(s PayloadConvertor, p api.Payloader, event webhook_module.
|
|||
case webhook_module.HookEventPush:
|
||||
return s.Push(p.(*api.PushPayload))
|
||||
case webhook_module.HookEventPullRequest, webhook_module.HookEventPullRequestAssign, webhook_module.HookEventPullRequestLabel,
|
||||
webhook_module.HookEventPullRequestMilestone, webhook_module.HookEventPullRequestSync:
|
||||
webhook_module.HookEventPullRequestMilestone, webhook_module.HookEventPullRequestSync, webhook_module.HookEventPullRequestReviewRequest:
|
||||
return s.PullRequest(p.(*api.PullRequestPayload))
|
||||
case webhook_module.HookEventPullRequestReviewApproved, webhook_module.HookEventPullRequestReviewRejected, webhook_module.HookEventPullRequestReviewComment:
|
||||
return s.Review(p.(*api.PullRequestPayload), event)
|
||||
|
|
|
@ -238,6 +238,16 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Pull Request Review Request -->
|
||||
<div class="seven wide column">
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input name="pull_request_review_request" type="checkbox" tabindex="0" {{if .Webhook.PullRequestReviewRequest}}checked{{end}}>
|
||||
<label>{{.locale.Tr "repo.settings.event_pull_request_review_request"}}</label>
|
||||
<span class="help">{{.locale.Tr "repo.settings.event_pull_request_review_request_desc"}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
7
templates/swagger/v1_json.tmpl
generated
7
templates/swagger/v1_json.tmpl
generated
|
@ -19934,6 +19934,13 @@
|
|||
"type": "string",
|
||||
"x-go-name": "PatchURL"
|
||||
},
|
||||
"requested_reviewers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/User"
|
||||
},
|
||||
"x-go-name": "RequestedReviewers"
|
||||
},
|
||||
"state": {
|
||||
"$ref": "#/definitions/StateType"
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue