Correct swagger annotations for enums, status codes, and notification state (#37030)

## ⚠️ BREAKING ⚠️

- delete reaction endpoints is changed to return 204 No Content rather
than 200 with no content.

## Summary

Add swagger:enum annotations and migrate all enum comments from the
deprecated comma-separated format to JSON arrays. Introduce
NotifySubjectStateType with open/closed/merged values. Fix delete
reaction endpoints to return 204 instead of 200.
This commit is contained in:
Myers Carpenter
2026-03-29 20:28:48 -04:00
committed by GitHub
parent cbea04c1fc
commit 2633f9677d
24 changed files with 265 additions and 110 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ type Activity struct {
UserID int64 `json:"user_id"` // Receiver user UserID int64 `json:"user_id"` // Receiver user
// the type of action // the type of action
// //
// enum: create_repo,rename_repo,star_repo,watch_repo,commit_repo,create_issue,create_pull_request,transfer_repo,push_tag,comment_issue,merge_pull_request,close_issue,reopen_issue,close_pull_request,reopen_pull_request,delete_tag,delete_branch,mirror_sync_push,mirror_sync_create,mirror_sync_delete,approve_pull_request,reject_pull_request,comment_pull,publish_release,pull_review_dismissed,pull_request_ready_for_review,auto_merge_pull_request // enum: ["create_repo","rename_repo","star_repo","watch_repo","commit_repo","create_issue","create_pull_request","transfer_repo","push_tag","comment_issue","merge_pull_request","close_issue","reopen_issue","close_pull_request","reopen_pull_request","delete_tag","delete_branch","mirror_sync_push","mirror_sync_create","mirror_sync_delete","approve_pull_request","reject_pull_request","comment_pull","publish_release","pull_review_dismissed","pull_request_ready_for_review","auto_merge_pull_request"]
OpType string `json:"op_type"` OpType string `json:"op_type"`
// The ID of the user who performed the action // The ID of the user who performed the action
ActUserID int64 `json:"act_user_id"` ActUserID int64 `json:"act_user_id"`
+1 -1
View File
@@ -51,7 +51,7 @@ type CreateHookOptionConfig map[string]string
// CreateHookOption options when create a hook // CreateHookOption options when create a hook
type CreateHookOption struct { type CreateHookOption struct {
// required: true // required: true
// enum: dingtalk,discord,gitea,gogs,msteams,slack,telegram,feishu,wechatwork,packagist // enum: ["dingtalk","discord","gitea","gogs","msteams","slack","telegram","feishu","wechatwork","packagist"]
// The type of the webhook to create // The type of the webhook to create
Type string `json:"type" binding:"Required"` Type string `json:"type" binding:"Required"`
// required: true // required: true
+14 -12
View File
@@ -14,6 +14,8 @@ import (
) )
// StateType issue state type // StateType issue state type
//
// swagger:enum StateType
type StateType string type StateType string
const ( const (
@@ -21,10 +23,11 @@ const (
StateOpen StateType = "open" StateOpen StateType = "open"
// StateClosed pr is closed // StateClosed pr is closed
StateClosed StateType = "closed" StateClosed StateType = "closed"
// StateAll is all
StateAll StateType = "all"
) )
// StateAll is a query parameter filter value, not a valid object state.
const StateAll = "all"
// PullRequestMeta PR info if an issue is a PR // PullRequestMeta PR info if an issue is a PR
type PullRequestMeta struct { type PullRequestMeta struct {
HasMerged bool `json:"merged"` HasMerged bool `json:"merged"`
@@ -58,15 +61,11 @@ type Issue struct {
Labels []*Label `json:"labels"` Labels []*Label `json:"labels"`
Milestone *Milestone `json:"milestone"` Milestone *Milestone `json:"milestone"`
// deprecated // deprecated
Assignee *User `json:"assignee"` Assignee *User `json:"assignee"`
Assignees []*User `json:"assignees"` Assignees []*User `json:"assignees"`
// Whether the issue is open or closed State StateType `json:"state"`
// IsLocked bool `json:"is_locked"`
// type: string Comments int `json:"comments"`
// enum: open,closed
State StateType `json:"state"`
IsLocked bool `json:"is_locked"`
Comments int `json:"comments"`
// swagger:strfmt date-time // swagger:strfmt date-time
Created time.Time `json:"created_at"` Created time.Time `json:"created_at"`
// swagger:strfmt date-time // swagger:strfmt date-time
@@ -132,6 +131,8 @@ type IssueDeadline struct {
} }
// IssueFormFieldType defines issue form field type, can be "markdown", "textarea", "input", "dropdown" or "checkboxes" // IssueFormFieldType defines issue form field type, can be "markdown", "textarea", "input", "dropdown" or "checkboxes"
//
// swagger:enum IssueFormFieldType
type IssueFormFieldType string type IssueFormFieldType string
const ( const (
@@ -168,7 +169,8 @@ func (iff IssueFormField) VisibleInContent() bool {
} }
// IssueFormFieldVisible defines issue form field visible // IssueFormFieldVisible defines issue form field visible
// swagger:model //
// swagger:enum IssueFormFieldVisible
type IssueFormFieldVisible string type IssueFormFieldVisible string
const ( const (
+2 -1
View File
@@ -40,7 +40,7 @@ type CreateMilestoneOption struct {
// swagger:strfmt date-time // swagger:strfmt date-time
// Deadline is the due date for the milestone // Deadline is the due date for the milestone
Deadline *time.Time `json:"due_on"` Deadline *time.Time `json:"due_on"`
// enum: open,closed // enum: ["open","closed"]
// State indicates the initial state of the milestone // State indicates the initial state of the milestone
State string `json:"state"` State string `json:"state"`
} }
@@ -52,6 +52,7 @@ type EditMilestoneOption struct {
// Description provides updated details about the milestone // Description provides updated details about the milestone
Description *string `json:"description"` Description *string `json:"description"`
// State indicates the updated state of the milestone // State indicates the updated state of the milestone
// enum: ["open","closed"]
State *string `json:"state"` State *string `json:"state"`
// Deadline is the updated due date for the milestone // Deadline is the updated due date for the milestone
Deadline *time.Time `json:"due_on"` Deadline *time.Time `json:"due_on"`
+16 -1
View File
@@ -40,7 +40,7 @@ type NotificationSubject struct {
// Type indicates the type of the notification subject // Type indicates the type of the notification subject
Type NotifySubjectType `json:"type" binding:"In(Issue,Pull,Commit,Repository)"` Type NotifySubjectType `json:"type" binding:"In(Issue,Pull,Commit,Repository)"`
// State indicates the current state of the notification subject // State indicates the current state of the notification subject
State StateType `json:"state"` State NotifySubjectStateType `json:"state"`
} }
// NotificationCount number of unread notifications // NotificationCount number of unread notifications
@@ -49,7 +49,22 @@ type NotificationCount struct {
New int64 `json:"new"` New int64 `json:"new"`
} }
// NotifySubjectStateType represents the state of a notification subject
// swagger:enum NotifySubjectStateType
type NotifySubjectStateType string
const (
// NotifySubjectStateOpen is an open subject
NotifySubjectStateOpen NotifySubjectStateType = "open"
// NotifySubjectStateClosed is a closed subject
NotifySubjectStateClosed NotifySubjectStateType = "closed"
// NotifySubjectStateMerged is a merged pull request
NotifySubjectStateMerged NotifySubjectStateType = "merged"
)
// NotifySubjectType represent type of notification subject // NotifySubjectType represent type of notification subject
//
// swagger:enum NotifySubjectType
type NotifySubjectType string type NotifySubjectType string
const ( const (
+2 -2
View File
@@ -60,7 +60,7 @@ type CreateOrgOption struct {
// The location of the organization // The location of the organization
Location string `json:"location" binding:"MaxSize(50)"` Location string `json:"location" binding:"MaxSize(50)"`
// possible values are `public` (default), `limited` or `private` // possible values are `public` (default), `limited` or `private`
// enum: public,limited,private // enum: ["public","limited","private"]
Visibility string `json:"visibility" binding:"In(,public,limited,private)"` Visibility string `json:"visibility" binding:"In(,public,limited,private)"`
// Whether repository administrators can change team access // Whether repository administrators can change team access
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"` RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"`
@@ -79,7 +79,7 @@ type EditOrgOption struct {
// The location of the organization // The location of the organization
Location *string `json:"location" binding:"MaxSize(50)"` Location *string `json:"location" binding:"MaxSize(50)"`
// possible values are `public`, `limited` or `private` // possible values are `public`, `limited` or `private`
// enum: public,limited,private // enum: ["public","limited","private"]
Visibility *string `json:"visibility" binding:"In(,public,limited,private)"` Visibility *string `json:"visibility" binding:"In(,public,limited,private)"`
// Whether repository administrators can change team access // Whether repository administrators can change team access
RepoAdminChangeTeamAccess *bool `json:"repo_admin_change_team_access"` RepoAdminChangeTeamAccess *bool `json:"repo_admin_change_team_access"`
+3 -3
View File
@@ -16,7 +16,7 @@ type Team struct {
Organization *Organization `json:"organization"` Organization *Organization `json:"organization"`
// Whether the team has access to all repositories in the organization // Whether the team has access to all repositories in the organization
IncludesAllRepositories bool `json:"includes_all_repositories"` IncludesAllRepositories bool `json:"includes_all_repositories"`
// enum: none,read,write,admin,owner // enum: ["none","read","write","admin","owner"]
Permission string `json:"permission"` Permission string `json:"permission"`
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.projects","repo.ext_wiki"] // example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.projects","repo.ext_wiki"]
// Deprecated: This variable should be replaced by UnitsMap and will be dropped in later versions. // Deprecated: This variable should be replaced by UnitsMap and will be dropped in later versions.
@@ -35,7 +35,7 @@ type CreateTeamOption struct {
Description string `json:"description" binding:"MaxSize(255)"` Description string `json:"description" binding:"MaxSize(255)"`
// Whether the team has access to all repositories in the organization // Whether the team has access to all repositories in the organization
IncludesAllRepositories bool `json:"includes_all_repositories"` IncludesAllRepositories bool `json:"includes_all_repositories"`
// enum: read,write,admin // enum: ["read","write","admin"]
Permission string `json:"permission"` Permission string `json:"permission"`
// example: ["repo.actions","repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.ext_wiki","repo.pulls","repo.releases","repo.projects","repo.ext_wiki"] // example: ["repo.actions","repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.ext_wiki","repo.pulls","repo.releases","repo.projects","repo.ext_wiki"]
// Deprecated: This variable should be replaced by UnitsMap and will be dropped in later versions. // Deprecated: This variable should be replaced by UnitsMap and will be dropped in later versions.
@@ -54,7 +54,7 @@ type EditTeamOption struct {
Description *string `json:"description" binding:"MaxSize(255)"` Description *string `json:"description" binding:"MaxSize(255)"`
// Whether the team has access to all repositories in the organization // Whether the team has access to all repositories in the organization
IncludesAllRepositories *bool `json:"includes_all_repositories"` IncludesAllRepositories *bool `json:"includes_all_repositories"`
// enum: read,write,admin // enum: ["read","write","admin"]
Permission string `json:"permission"` Permission string `json:"permission"`
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.projects","repo.ext_wiki"] // example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.projects","repo.ext_wiki"]
// Deprecated: This variable should be replaced by UnitsMap and will be dropped in later versions. // Deprecated: This variable should be replaced by UnitsMap and will be dropped in later versions.
+5 -2
View File
@@ -8,6 +8,8 @@ import (
) )
// ReviewStateType review state type // ReviewStateType review state type
//
// swagger:enum ReviewStateType
type ReviewStateType string type ReviewStateType string
const ( const (
@@ -21,10 +23,11 @@ const (
ReviewStateRequestChanges ReviewStateType = "REQUEST_CHANGES" ReviewStateRequestChanges ReviewStateType = "REQUEST_CHANGES"
// ReviewStateRequestReview review is requested from user // ReviewStateRequestReview review is requested from user
ReviewStateRequestReview ReviewStateType = "REQUEST_REVIEW" ReviewStateRequestReview ReviewStateType = "REQUEST_REVIEW"
// ReviewStateUnknown state of pr is unknown
ReviewStateUnknown ReviewStateType = ""
) )
// ReviewStateUnknown is an internal sentinel for unknown review state, not a valid API value.
const ReviewStateUnknown = ""
// PullReview represents a pull request review // PullReview represents a pull request review
type PullReview struct { type PullReview struct {
ID int64 `json:"id"` ID int64 `json:"id"`
+4 -4
View File
@@ -114,7 +114,7 @@ type Repository struct {
Internal bool `json:"internal"` Internal bool `json:"internal"`
MirrorInterval string `json:"mirror_interval"` MirrorInterval string `json:"mirror_interval"`
// ObjectFormatName of the underlying git repository // ObjectFormatName of the underlying git repository
// enum: sha1,sha256 // enum: ["sha1","sha256"]
ObjectFormatName string `json:"object_format_name"` ObjectFormatName string `json:"object_format_name"`
// swagger:strfmt date-time // swagger:strfmt date-time
MirrorUpdated time.Time `json:"mirror_updated"` MirrorUpdated time.Time `json:"mirror_updated"`
@@ -150,10 +150,10 @@ type CreateRepoOption struct {
// DefaultBranch of the repository (used when initializes and in template) // DefaultBranch of the repository (used when initializes and in template)
DefaultBranch string `json:"default_branch" binding:"GitRefName;MaxSize(100)"` DefaultBranch string `json:"default_branch" binding:"GitRefName;MaxSize(100)"`
// TrustModel of the repository // TrustModel of the repository
// enum: default,collaborator,committer,collaboratorcommitter // enum: ["default","collaborator","committer","collaboratorcommitter"]
TrustModel string `json:"trust_model"` TrustModel string `json:"trust_model"`
// ObjectFormatName of the underlying git repository, empty string for default (sha1) // ObjectFormatName of the underlying git repository, empty string for default (sha1)
// enum: sha1,sha256 // enum: ["sha1","sha256"]
ObjectFormatName string `json:"object_format_name" binding:"MaxSize(6)"` ObjectFormatName string `json:"object_format_name" binding:"MaxSize(6)"`
} }
@@ -378,7 +378,7 @@ type MigrateRepoOptions struct {
// required: true // required: true
RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"` RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"`
// enum: git,github,gitea,gitlab,gogs,onedev,gitbucket,codebase,codecommit // enum: ["git","github","gitea","gitlab","gogs","onedev","gitbucket","codebase","codecommit"]
Service string `json:"service"` Service string `json:"service"`
AuthUsername string `json:"auth_username"` AuthUsername string `json:"auth_username"`
AuthPassword string `json:"auth_password"` AuthPassword string `json:"auth_password"`
+1 -1
View File
@@ -5,7 +5,7 @@ package structs
// AddCollaboratorOption options when adding a user as a collaborator of a repository // AddCollaboratorOption options when adding a user as a collaborator of a repository
type AddCollaboratorOption struct { type AddCollaboratorOption struct {
// enum: read,write,admin // enum: ["read","write","admin"]
// Permission level to grant the collaborator // Permission level to grant the collaborator
Permission *string `json:"permission"` Permission *string `json:"permission"`
} }
+1 -1
View File
@@ -72,7 +72,7 @@ type ChangeFileOperation struct {
// indicates what to do with the file: "create" for creating a new file, "update" for updating an existing file, // indicates what to do with the file: "create" for creating a new file, "update" for updating an existing file,
// "upload" for creating or updating a file, "rename" for renaming a file, and "delete" for deleting an existing file. // "upload" for creating or updating a file, "rename" for renaming a file, and "delete" for deleting an existing file.
// required: true // required: true
// enum: create,update,upload,rename,delete // enum: ["create","update","upload","rename","delete"]
Operation string `json:"operation" binding:"Required"` Operation string `json:"operation" binding:"Required"`
// path to the existing or new file // path to the existing or new file
// required: true // required: true
+3 -3
View File
@@ -40,7 +40,7 @@ func ListRunners(ctx *context.APIContext) {
// required: false // required: false
// responses: // responses:
// "200": // "200":
// "$ref": "#/definitions/ActionRunnersResponse" // "$ref": "#/responses/RunnerList"
// "400": // "400":
// "$ref": "#/responses/error" // "$ref": "#/responses/error"
// "404": // "404":
@@ -63,7 +63,7 @@ func GetRunner(ctx *context.APIContext) {
// required: true // required: true
// responses: // responses:
// "200": // "200":
// "$ref": "#/definitions/ActionRunner" // "$ref": "#/responses/Runner"
// "400": // "400":
// "$ref": "#/responses/error" // "$ref": "#/responses/error"
// "404": // "404":
@@ -115,7 +115,7 @@ func UpdateRunner(ctx *context.APIContext) {
// "$ref": "#/definitions/EditActionRunnerOption" // "$ref": "#/definitions/EditActionRunnerOption"
// responses: // responses:
// "200": // "200":
// "$ref": "#/definitions/ActionRunner" // "$ref": "#/responses/Runner"
// "400": // "400":
// "$ref": "#/responses/error" // "$ref": "#/responses/error"
// "404": // "404":
-2
View File
@@ -11,11 +11,9 @@
// //
// Consumes: // Consumes:
// - application/json // - application/json
// - text/plain
// //
// Produces: // Produces:
// - application/json // - application/json
// - text/html
// //
// Security: // Security:
// - BasicAuth : // - BasicAuth :
+3 -3
View File
@@ -492,7 +492,7 @@ func (Action) ListRunners(ctx *context.APIContext) {
// required: false // required: false
// responses: // responses:
// "200": // "200":
// "$ref": "#/definitions/ActionRunnersResponse" // "$ref": "#/responses/RunnerList"
// "400": // "400":
// "$ref": "#/responses/error" // "$ref": "#/responses/error"
// "404": // "404":
@@ -520,7 +520,7 @@ func (Action) GetRunner(ctx *context.APIContext) {
// required: true // required: true
// responses: // responses:
// "200": // "200":
// "$ref": "#/definitions/ActionRunner" // "$ref": "#/responses/Runner"
// "400": // "400":
// "$ref": "#/responses/error" // "$ref": "#/responses/error"
// "404": // "404":
@@ -582,7 +582,7 @@ func (Action) UpdateRunner(ctx *context.APIContext) {
// "$ref": "#/definitions/EditActionRunnerOption" // "$ref": "#/definitions/EditActionRunnerOption"
// responses: // responses:
// "200": // "200":
// "$ref": "#/definitions/ActionRunner" // "$ref": "#/responses/Runner"
// "400": // "400":
// "$ref": "#/responses/error" // "$ref": "#/responses/error"
// "404": // "404":
+4 -4
View File
@@ -561,7 +561,7 @@ func (Action) ListRunners(ctx *context.APIContext) {
// required: false // required: false
// responses: // responses:
// "200": // "200":
// "$ref": "#/definitions/ActionRunnersResponse" // "$ref": "#/responses/RunnerList"
// "400": // "400":
// "$ref": "#/responses/error" // "$ref": "#/responses/error"
// "404": // "404":
@@ -594,7 +594,7 @@ func (Action) GetRunner(ctx *context.APIContext) {
// required: true // required: true
// responses: // responses:
// "200": // "200":
// "$ref": "#/definitions/ActionRunner" // "$ref": "#/responses/Runner"
// "400": // "400":
// "$ref": "#/responses/error" // "$ref": "#/responses/error"
// "404": // "404":
@@ -666,7 +666,7 @@ func (Action) UpdateRunner(ctx *context.APIContext) {
// "$ref": "#/definitions/EditActionRunnerOption" // "$ref": "#/definitions/EditActionRunnerOption"
// responses: // responses:
// "200": // "200":
// "$ref": "#/definitions/ActionRunner" // "$ref": "#/responses/Runner"
// "400": // "400":
// "$ref": "#/responses/error" // "$ref": "#/responses/error"
// "404": // "404":
@@ -1192,7 +1192,7 @@ func GetWorkflowRun(ctx *context.APIContext) {
// - name: run // - name: run
// in: path // in: path
// description: id of the run // description: id of the run
// type: string // type: integer
// required: true // required: true
// responses: // responses:
// "200": // "200":
+4 -6
View File
@@ -175,7 +175,7 @@ func DeleteIssueCommentReaction(ctx *context.APIContext) {
// schema: // schema:
// "$ref": "#/definitions/EditReactionOption" // "$ref": "#/definitions/EditReactionOption"
// responses: // responses:
// "200": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "403": // "403":
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
@@ -248,8 +248,7 @@ func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOp
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
return return
} }
// ToDo respond 204 ctx.Status(http.StatusNoContent)
ctx.Status(http.StatusOK)
} }
} }
@@ -408,7 +407,7 @@ func DeleteIssueReaction(ctx *context.APIContext) {
// schema: // schema:
// "$ref": "#/definitions/EditReactionOption" // "$ref": "#/definitions/EditReactionOption"
// responses: // responses:
// "200": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "403": // "403":
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
@@ -464,7 +463,6 @@ func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, i
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
return return
} }
// ToDo respond 204 ctx.Status(http.StatusNoContent)
ctx.Status(http.StatusOK)
} }
} }
+2
View File
@@ -898,6 +898,8 @@ func MergePullRequest(ctx *context.APIContext) {
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
// "405": // "405":
+3 -3
View File
@@ -40,7 +40,7 @@ func ListRunners(ctx *context.APIContext) {
// required: false // required: false
// responses: // responses:
// "200": // "200":
// "$ref": "#/definitions/ActionRunnersResponse" // "$ref": "#/responses/RunnerList"
// "400": // "400":
// "$ref": "#/responses/error" // "$ref": "#/responses/error"
// "404": // "404":
@@ -63,7 +63,7 @@ func GetRunner(ctx *context.APIContext) {
// required: true // required: true
// responses: // responses:
// "200": // "200":
// "$ref": "#/definitions/ActionRunner" // "$ref": "#/responses/Runner"
// "400": // "400":
// "$ref": "#/responses/error" // "$ref": "#/responses/error"
// "404": // "404":
@@ -115,7 +115,7 @@ func UpdateRunner(ctx *context.APIContext) {
// "$ref": "#/definitions/EditActionRunnerOption" // "$ref": "#/definitions/EditActionRunnerOption"
// responses: // responses:
// "200": // "200":
// "$ref": "#/definitions/ActionRunner" // "$ref": "#/responses/Runner"
// "400": // "400":
// "$ref": "#/responses/error" // "$ref": "#/responses/error"
// "404": // "404":
+3 -3
View File
@@ -47,7 +47,7 @@ func ToNotificationThread(ctx context.Context, n *activities_model.Notification)
result.Subject.Title = n.Issue.Title result.Subject.Title = n.Issue.Title
result.Subject.URL = n.Issue.APIURL(ctx) result.Subject.URL = n.Issue.APIURL(ctx)
result.Subject.HTMLURL = n.Issue.HTMLURL(ctx) result.Subject.HTMLURL = n.Issue.HTMLURL(ctx)
result.Subject.State = n.Issue.State() result.Subject.State = api.NotifySubjectStateType(n.Issue.State())
comment, err := n.Issue.GetLastComment(ctx) comment, err := n.Issue.GetLastComment(ctx)
if err == nil && comment != nil { if err == nil && comment != nil {
result.Subject.LatestCommentURL = comment.APIURL(ctx) result.Subject.LatestCommentURL = comment.APIURL(ctx)
@@ -60,7 +60,7 @@ func ToNotificationThread(ctx context.Context, n *activities_model.Notification)
result.Subject.Title = n.Issue.Title result.Subject.Title = n.Issue.Title
result.Subject.URL = n.Issue.APIURL(ctx) result.Subject.URL = n.Issue.APIURL(ctx)
result.Subject.HTMLURL = n.Issue.HTMLURL(ctx) result.Subject.HTMLURL = n.Issue.HTMLURL(ctx)
result.Subject.State = n.Issue.State() result.Subject.State = api.NotifySubjectStateType(n.Issue.State())
comment, err := n.Issue.GetLastComment(ctx) comment, err := n.Issue.GetLastComment(ctx)
if err == nil && comment != nil { if err == nil && comment != nil {
result.Subject.LatestCommentURL = comment.APIURL(ctx) result.Subject.LatestCommentURL = comment.APIURL(ctx)
@@ -70,7 +70,7 @@ func ToNotificationThread(ctx context.Context, n *activities_model.Notification)
if err := n.Issue.LoadPullRequest(ctx); err == nil && if err := n.Issue.LoadPullRequest(ctx); err == nil &&
n.Issue.PullRequest != nil && n.Issue.PullRequest != nil &&
n.Issue.PullRequest.HasMerged { n.Issue.PullRequest.HasMerged {
result.Subject.State = "merged" result.Subject.State = api.NotifySubjectStateMerged
} }
} }
case activities_model.NotificationSourceCommit: case activities_model.NotificationSourceCommit:
+75
View File
@@ -7,12 +7,15 @@ import (
"testing" "testing"
activities_model "code.gitea.io/gitea/models/activities" activities_model "code.gitea.io/gitea/models/activities"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestToNotificationThreadIncludesRepoForAccessibleUser(t *testing.T) { func TestToNotificationThreadIncludesRepoForAccessibleUser(t *testing.T) {
@@ -36,6 +39,78 @@ func TestToNotificationThreadOmitsRepoWhenAccessRevoked(t *testing.T) {
assert.Nil(t, thread.Repository) assert.Nil(t, thread.Repository)
} }
func TestToNotificationThread(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
t.Run("issue notification", func(t *testing.T) {
// Notification 1: source=issue, issue_id=1, status=unread
n := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{ID: 1})
require.NoError(t, n.LoadAttributes(t.Context()))
thread := ToNotificationThread(t.Context(), n)
assert.Equal(t, int64(1), thread.ID)
assert.True(t, thread.Unread)
assert.False(t, thread.Pinned)
require.NotNil(t, thread.Subject)
assert.Equal(t, api.NotifySubjectIssue, thread.Subject.Type)
assert.Equal(t, api.NotifySubjectStateOpen, thread.Subject.State)
})
t.Run("pinned notification", func(t *testing.T) {
// Notification 3: status=pinned
n := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{ID: 3})
require.NoError(t, n.LoadAttributes(t.Context()))
thread := ToNotificationThread(t.Context(), n)
assert.False(t, thread.Unread)
assert.True(t, thread.Pinned)
})
t.Run("merged pull request returns merged state", func(t *testing.T) {
// Issue 2 is a pull request; pull_request 1 has has_merged=true.
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
n := &activities_model.Notification{
ID: 999,
UserID: 2,
RepoID: repo.ID,
Status: activities_model.NotificationStatusUnread,
Source: activities_model.NotificationSourcePullRequest,
IssueID: issue.ID,
Issue: issue,
Repository: repo,
}
thread := ToNotificationThread(t.Context(), n)
require.NotNil(t, thread.Subject)
assert.Equal(t, api.NotifySubjectPull, thread.Subject.Type)
assert.Equal(t, api.NotifySubjectStateMerged, thread.Subject.State)
})
t.Run("open pull request returns open state", func(t *testing.T) {
// Issue 3 is a pull request; pull_request 2 has has_merged=false.
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3})
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
n := &activities_model.Notification{
ID: 998,
UserID: 2,
RepoID: repo.ID,
Status: activities_model.NotificationStatusUnread,
Source: activities_model.NotificationSourcePullRequest,
IssueID: issue.ID,
Issue: issue,
Repository: repo,
}
thread := ToNotificationThread(t.Context(), n)
require.NotNil(t, thread.Subject)
assert.Equal(t, api.NotifySubjectPull, thread.Subject.Type)
assert.Equal(t, api.NotifySubjectStateOpen, thread.Subject.State)
})
}
func newRepoNotification(t *testing.T, repoID, userID int64) *activities_model.Notification { func newRepoNotification(t *testing.T, repoID, userID int64) *activities_model.Notification {
t.Helper() t.Helper()
+3
View File
@@ -9,6 +9,7 @@ import (
git_model "code.gitea.io/gitea/models/git" git_model "code.gitea.io/gitea/models/git"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/commitstatus"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
) )
@@ -55,6 +56,8 @@ func ToCombinedStatus(ctx context.Context, commitID string, statuses []*git_mode
if combinedStatus != nil { if combinedStatus != nil {
status.Statuses = ToCommitStatuses(ctx, statuses) status.Statuses = ToCommitStatuses(ctx, statuses)
status.State = combinedStatus.State status.State = combinedStatus.State
} else {
status.State = commitstatus.CommitStatusPending
} }
return &status return &status
} }
+1 -1
View File
@@ -521,7 +521,7 @@ func (f *InitializeLabelsForm) Validate(req *http.Request, errs binding.Errors)
// swagger:model MergePullRequestOption // swagger:model MergePullRequestOption
type MergePullRequestForm struct { type MergePullRequestForm struct {
// required: true // required: true
// enum: merge,rebase,rebase-merge,squash,fast-forward-only,manually-merged // enum: ["merge","rebase","rebase-merge","squash","fast-forward-only","manually-merged"]
Do string `binding:"Required;In(merge,rebase,rebase-merge,squash,fast-forward-only,manually-merged)"` Do string `binding:"Required;In(merge,rebase,rebase-merge,squash,fast-forward-only,manually-merged)"`
MergeTitleField string MergeTitleField string
MergeMessageField string MergeMessageField string
+112 -54
View File
@@ -1,11 +1,9 @@
{ {
"consumes": [ "consumes": [
"application/json", "application/json"
"text/plain"
], ],
"produces": [ "produces": [
"application/json", "application/json"
"text/html"
], ],
"schemes": [ "schemes": [
"https", "https",
@@ -86,7 +84,7 @@
], ],
"responses": { "responses": {
"200": { "200": {
"$ref": "#/definitions/ActionRunnersResponse" "$ref": "#/responses/RunnerList"
}, },
"400": { "400": {
"$ref": "#/responses/error" "$ref": "#/responses/error"
@@ -135,7 +133,7 @@
], ],
"responses": { "responses": {
"200": { "200": {
"$ref": "#/definitions/ActionRunner" "$ref": "#/responses/Runner"
}, },
"400": { "400": {
"$ref": "#/responses/error" "$ref": "#/responses/error"
@@ -205,7 +203,7 @@
], ],
"responses": { "responses": {
"200": { "200": {
"$ref": "#/definitions/ActionRunner" "$ref": "#/responses/Runner"
}, },
"400": { "400": {
"$ref": "#/responses/error" "$ref": "#/responses/error"
@@ -2008,7 +2006,7 @@
], ],
"responses": { "responses": {
"200": { "200": {
"$ref": "#/definitions/ActionRunnersResponse" "$ref": "#/responses/RunnerList"
}, },
"400": { "400": {
"$ref": "#/responses/error" "$ref": "#/responses/error"
@@ -2073,7 +2071,7 @@
], ],
"responses": { "responses": {
"200": { "200": {
"$ref": "#/definitions/ActionRunner" "$ref": "#/responses/Runner"
}, },
"400": { "400": {
"$ref": "#/responses/error" "$ref": "#/responses/error"
@@ -2157,7 +2155,7 @@
], ],
"responses": { "responses": {
"200": { "200": {
"$ref": "#/definitions/ActionRunner" "$ref": "#/responses/Runner"
}, },
"400": { "400": {
"$ref": "#/responses/error" "$ref": "#/responses/error"
@@ -4989,7 +4987,7 @@
], ],
"responses": { "responses": {
"200": { "200": {
"$ref": "#/definitions/ActionRunnersResponse" "$ref": "#/responses/RunnerList"
}, },
"400": { "400": {
"$ref": "#/responses/error" "$ref": "#/responses/error"
@@ -5068,7 +5066,7 @@
], ],
"responses": { "responses": {
"200": { "200": {
"$ref": "#/definitions/ActionRunner" "$ref": "#/responses/Runner"
}, },
"400": { "400": {
"$ref": "#/responses/error" "$ref": "#/responses/error"
@@ -5166,7 +5164,7 @@
], ],
"responses": { "responses": {
"200": { "200": {
"$ref": "#/definitions/ActionRunner" "$ref": "#/responses/Runner"
}, },
"400": { "400": {
"$ref": "#/responses/error" "$ref": "#/responses/error"
@@ -5287,7 +5285,7 @@
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "id of the run", "description": "id of the run",
"name": "run", "name": "run",
"in": "path", "in": "path",
@@ -10230,7 +10228,7 @@
} }
], ],
"responses": { "responses": {
"200": { "204": {
"$ref": "#/responses/empty" "$ref": "#/responses/empty"
}, },
"403": { "403": {
@@ -11969,7 +11967,7 @@
} }
], ],
"responses": { "responses": {
"200": { "204": {
"$ref": "#/responses/empty" "$ref": "#/responses/empty"
}, },
"403": { "403": {
@@ -14495,6 +14493,9 @@
"200": { "200": {
"$ref": "#/responses/empty" "$ref": "#/responses/empty"
}, },
"403": {
"$ref": "#/responses/forbidden"
},
"404": { "404": {
"$ref": "#/responses/notFound" "$ref": "#/responses/notFound"
}, },
@@ -18670,7 +18671,7 @@
], ],
"responses": { "responses": {
"200": { "200": {
"$ref": "#/definitions/ActionRunnersResponse" "$ref": "#/responses/RunnerList"
}, },
"400": { "400": {
"$ref": "#/responses/error" "$ref": "#/responses/error"
@@ -18719,7 +18720,7 @@
], ],
"responses": { "responses": {
"200": { "200": {
"$ref": "#/definitions/ActionRunner" "$ref": "#/responses/Runner"
}, },
"400": { "400": {
"$ref": "#/responses/error" "$ref": "#/responses/error"
@@ -18789,7 +18790,7 @@
], ],
"responses": { "responses": {
"200": { "200": {
"$ref": "#/definitions/ActionRunner" "$ref": "#/responses/Runner"
}, },
"400": { "400": {
"$ref": "#/responses/error" "$ref": "#/responses/error"
@@ -23887,7 +23888,16 @@
"x-go-name": "CommitID" "x-go-name": "CommitID"
}, },
"event": { "event": {
"$ref": "#/definitions/ReviewStateType" "type": "string",
"enum": [
"APPROVED",
"PENDING",
"COMMENT",
"REQUEST_CHANGES",
"REQUEST_REVIEW"
],
"x-go-enum-desc": "APPROVED ReviewStateApproved ReviewStateApproved pr is approved\nPENDING ReviewStatePending ReviewStatePending pr state is pending\nCOMMENT ReviewStateComment ReviewStateComment is a comment review\nREQUEST_CHANGES ReviewStateRequestChanges ReviewStateRequestChanges changes for pr are requested\nREQUEST_REVIEW ReviewStateRequestReview ReviewStateRequestReview review is requested from user",
"x-go-name": "Event"
} }
}, },
"x-go-package": "code.gitea.io/gitea/modules/structs" "x-go-package": "code.gitea.io/gitea/modules/structs"
@@ -24835,6 +24845,10 @@
"state": { "state": {
"description": "State indicates the updated state of the milestone", "description": "State indicates the updated state of the milestone",
"type": "string", "type": "string",
"enum": [
"open",
"closed"
],
"x-go-name": "State" "x-go-name": "State"
}, },
"title": { "title": {
@@ -26272,7 +26286,13 @@
"$ref": "#/definitions/RepositoryMeta" "$ref": "#/definitions/RepositoryMeta"
}, },
"state": { "state": {
"$ref": "#/definitions/StateType" "type": "string",
"enum": [
"open",
"closed"
],
"x-go-enum-desc": "open StateOpen StateOpen pr is opened\nclosed StateClosed StateClosed pr is closed",
"x-go-name": "State"
}, },
"time_estimate": { "time_estimate": {
"type": "integer", "type": "integer",
@@ -26373,7 +26393,16 @@
"x-go-name": "ID" "x-go-name": "ID"
}, },
"type": { "type": {
"$ref": "#/definitions/IssueFormFieldType" "type": "string",
"enum": [
"markdown",
"textarea",
"input",
"dropdown",
"checkboxes"
],
"x-go-enum-desc": "markdown IssueFormFieldTypeMarkdown\ntextarea IssueFormFieldTypeTextarea\ninput IssueFormFieldTypeInput\ndropdown IssueFormFieldTypeDropdown\ncheckboxes IssueFormFieldTypeCheckboxes",
"x-go-name": "Type"
}, },
"validations": { "validations": {
"type": "object", "type": "object",
@@ -26383,23 +26412,18 @@
"visible": { "visible": {
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/IssueFormFieldVisible" "type": "string",
"enum": [
"form",
"content"
],
"x-go-enum-desc": "form IssueFormFieldVisibleForm\ncontent IssueFormFieldVisibleContent"
}, },
"x-go-name": "Visible" "x-go-name": "Visible"
} }
}, },
"x-go-package": "code.gitea.io/gitea/modules/structs" "x-go-package": "code.gitea.io/gitea/modules/structs"
}, },
"IssueFormFieldType": {
"type": "string",
"title": "IssueFormFieldType defines issue form field type, can be \"markdown\", \"textarea\", \"input\", \"dropdown\" or \"checkboxes\"",
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"IssueFormFieldVisible": {
"description": "IssueFormFieldVisible defines issue form field visible",
"type": "string",
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"IssueLabelsOption": { "IssueLabelsOption": {
"description": "IssueLabelsOption a collection of labels", "description": "IssueLabelsOption a collection of labels",
"type": "object", "type": "object",
@@ -26897,7 +26921,14 @@
"x-go-name": "OpenIssues" "x-go-name": "OpenIssues"
}, },
"state": { "state": {
"$ref": "#/definitions/StateType" "description": "State indicates if the milestone is open or closed\nopen StateOpen StateOpen pr is opened\nclosed StateClosed StateClosed pr is closed",
"type": "string",
"enum": [
"open",
"closed"
],
"x-go-enum-desc": "open StateOpen StateOpen pr is opened\nclosed StateClosed StateClosed pr is closed",
"x-go-name": "State"
}, },
"title": { "title": {
"description": "Title is the title of the milestone", "description": "Title is the title of the milestone",
@@ -27111,7 +27142,15 @@
"x-go-name": "LatestCommentURL" "x-go-name": "LatestCommentURL"
}, },
"state": { "state": {
"$ref": "#/definitions/StateType" "description": "State indicates the current state of the notification subject\nopen NotifySubjectStateOpen NotifySubjectStateOpen is an open subject\nclosed NotifySubjectStateClosed NotifySubjectStateClosed is a closed subject\nmerged NotifySubjectStateMerged NotifySubjectStateMerged is a merged pull request",
"type": "string",
"enum": [
"open",
"closed",
"merged"
],
"x-go-enum-desc": "open NotifySubjectStateOpen NotifySubjectStateOpen is an open subject\nclosed NotifySubjectStateClosed NotifySubjectStateClosed is a closed subject\nmerged NotifySubjectStateMerged NotifySubjectStateMerged is a merged pull request",
"x-go-name": "State"
}, },
"title": { "title": {
"description": "Title is the title of the notification subject", "description": "Title is the title of the notification subject",
@@ -27119,7 +27158,16 @@
"x-go-name": "Title" "x-go-name": "Title"
}, },
"type": { "type": {
"$ref": "#/definitions/NotifySubjectType" "description": "Type indicates the type of the notification subject\nIssue NotifySubjectIssue NotifySubjectIssue an issue is subject of an notification\nPull NotifySubjectPull NotifySubjectPull an pull is subject of an notification\nCommit NotifySubjectCommit NotifySubjectCommit an commit is subject of an notification\nRepository NotifySubjectRepository NotifySubjectRepository an repository is subject of an notification",
"type": "string",
"enum": [
"Issue",
"Pull",
"Commit",
"Repository"
],
"x-go-enum-desc": "Issue NotifySubjectIssue NotifySubjectIssue an issue is subject of an notification\nPull NotifySubjectPull NotifySubjectPull an pull is subject of an notification\nCommit NotifySubjectCommit NotifySubjectCommit an commit is subject of an notification\nRepository NotifySubjectRepository NotifySubjectRepository an repository is subject of an notification",
"x-go-name": "Type"
}, },
"url": { "url": {
"description": "URL is the API URL for the notification subject", "description": "URL is the API URL for the notification subject",
@@ -27169,11 +27217,6 @@
}, },
"x-go-package": "code.gitea.io/gitea/modules/structs" "x-go-package": "code.gitea.io/gitea/modules/structs"
}, },
"NotifySubjectType": {
"description": "NotifySubjectType represent type of notification subject",
"type": "string",
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"OAuth2Application": { "OAuth2Application": {
"type": "object", "type": "object",
"title": "OAuth2Application represents an OAuth2 application.", "title": "OAuth2Application represents an OAuth2 application.",
@@ -27806,7 +27849,14 @@
"x-go-name": "ReviewComments" "x-go-name": "ReviewComments"
}, },
"state": { "state": {
"$ref": "#/definitions/StateType" "description": "The current state of the pull request\nopen StateOpen StateOpen pr is opened\nclosed StateClosed StateClosed pr is closed",
"type": "string",
"enum": [
"open",
"closed"
],
"x-go-enum-desc": "open StateOpen StateOpen pr is opened\nclosed StateClosed StateClosed pr is closed",
"x-go-name": "State"
}, },
"title": { "title": {
"description": "The title of the pull request", "description": "The title of the pull request",
@@ -27898,7 +27948,16 @@
"x-go-name": "Stale" "x-go-name": "Stale"
}, },
"state": { "state": {
"$ref": "#/definitions/ReviewStateType" "type": "string",
"enum": [
"APPROVED",
"PENDING",
"COMMENT",
"REQUEST_CHANGES",
"REQUEST_REVIEW"
],
"x-go-enum-desc": "APPROVED ReviewStateApproved ReviewStateApproved pr is approved\nPENDING ReviewStatePending ReviewStatePending pr state is pending\nCOMMENT ReviewStateComment ReviewStateComment is a comment review\nREQUEST_CHANGES ReviewStateRequestChanges ReviewStateRequestChanges changes for pr are requested\nREQUEST_REVIEW ReviewStateRequestReview ReviewStateRequestReview review is requested from user",
"x-go-name": "State"
}, },
"submitted_at": { "submitted_at": {
"type": "string", "type": "string",
@@ -28635,11 +28694,6 @@
}, },
"x-go-package": "code.gitea.io/gitea/modules/structs" "x-go-package": "code.gitea.io/gitea/modules/structs"
}, },
"ReviewStateType": {
"description": "ReviewStateType review state type",
"type": "string",
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"RunDetails": { "RunDetails": {
"description": "RunDetails returns workflow_dispatch runid and url", "description": "RunDetails returns workflow_dispatch runid and url",
"type": "object", "type": "object",
@@ -28714,11 +28768,6 @@
}, },
"x-go-package": "code.gitea.io/gitea/modules/structs" "x-go-package": "code.gitea.io/gitea/modules/structs"
}, },
"StateType": {
"description": "StateType issue state type",
"type": "string",
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"StopWatch": { "StopWatch": {
"description": "StopWatch represent a running stopwatch", "description": "StopWatch represent a running stopwatch",
"type": "object", "type": "object",
@@ -28772,7 +28821,16 @@
"x-go-name": "Body" "x-go-name": "Body"
}, },
"event": { "event": {
"$ref": "#/definitions/ReviewStateType" "type": "string",
"enum": [
"APPROVED",
"PENDING",
"COMMENT",
"REQUEST_CHANGES",
"REQUEST_REVIEW"
],
"x-go-enum-desc": "APPROVED ReviewStateApproved ReviewStateApproved pr is approved\nPENDING ReviewStatePending ReviewStatePending pr state is pending\nCOMMENT ReviewStateComment ReviewStateComment is a comment review\nREQUEST_CHANGES ReviewStateRequestChanges ReviewStateRequestChanges changes for pr are requested\nREQUEST_REVIEW ReviewStateRequestReview ReviewStateRequestReview review is requested from user",
"x-go-name": "Event"
} }
}, },
"x-go-package": "code.gitea.io/gitea/modules/structs" "x-go-package": "code.gitea.io/gitea/modules/structs"
+2 -2
View File
@@ -44,7 +44,7 @@ func TestAPIIssuesReactions(t *testing.T) {
req = NewRequestWithJSON(t, "DELETE", urlStr, &api.EditReactionOption{ req = NewRequestWithJSON(t, "DELETE", urlStr, &api.EditReactionOption{
Reaction: "zzz", Reaction: "zzz",
}).AddTokenAuth(token) }).AddTokenAuth(token)
MakeRequest(t, req, http.StatusOK) MakeRequest(t, req, http.StatusNoContent)
// Add allowed reaction // Add allowed reaction
req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{
@@ -111,7 +111,7 @@ func TestAPICommentReactions(t *testing.T) {
req = NewRequestWithJSON(t, "DELETE", urlStr, &api.EditReactionOption{ req = NewRequestWithJSON(t, "DELETE", urlStr, &api.EditReactionOption{
Reaction: "eyes", Reaction: "eyes",
}).AddTokenAuth(token) }).AddTokenAuth(token)
MakeRequest(t, req, http.StatusOK) MakeRequest(t, req, http.StatusNoContent)
t.Run("UnrelatedCommentID", func(t *testing.T) { t.Run("UnrelatedCommentID", func(t *testing.T) {
// Using the ID of a comment that does not belong to the repository must fail // Using the ID of a comment that does not belong to the repository must fail