mirror of
https://github.com/go-gitea/gitea.git
synced 2026-04-04 03:35:05 +02:00
some improvements
This commit is contained in:
parent
75121570fb
commit
5c141d3c9b
@ -121,11 +121,3 @@ type EditProjectColumnOption struct {
|
||||
// Sorting order
|
||||
Sorting *int `json:"sorting,omitempty"`
|
||||
}
|
||||
|
||||
// AddIssueToProjectColumnOption represents options for adding an issue to a project column
|
||||
// swagger:model
|
||||
type AddIssueToProjectColumnOption struct {
|
||||
// Issue ID to add to the column
|
||||
// required: true
|
||||
IssueID int64 `json:"issue_id" binding:"Required"`
|
||||
}
|
||||
|
||||
@ -1585,7 +1585,9 @@ func Routes() *web.Router {
|
||||
m.Combo("").
|
||||
Patch(reqToken(), reqRepoWriter(unit.TypeProjects), mustNotBeArchived, bind(api.EditProjectColumnOption{}), repo.EditProjectColumn).
|
||||
Delete(reqToken(), reqRepoWriter(unit.TypeProjects), mustNotBeArchived, repo.DeleteProjectColumn)
|
||||
m.Post("/issues", reqToken(), reqRepoWriter(unit.TypeProjects), mustNotBeArchived, bind(api.AddIssueToProjectColumnOption{}), repo.AddIssueToProjectColumn)
|
||||
m.Get("/issues", repo.ListProjectColumnIssues)
|
||||
m.Post("/issues/{issue_id}", reqToken(), reqRepoWriter(unit.TypeProjects), mustNotBeArchived, repo.AddIssueToProjectColumn)
|
||||
m.Delete("/issues/{issue_id}", reqToken(), reqRepoWriter(unit.TypeProjects), mustNotBeArchived, repo.RemoveIssueFromProjectColumn)
|
||||
})
|
||||
}, reqRepoReader(unit.TypeProjects))
|
||||
}, repoAssignment(), checkTokenPublicOnly())
|
||||
|
||||
@ -572,9 +572,78 @@ func DeleteProjectColumn(ctx *context.APIContext) {
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// ListProjectColumnIssues lists all issues in a project column
|
||||
func ListProjectColumnIssues(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/projects/columns/{id}/issues repository repoListProjectColumnIssues
|
||||
// ---
|
||||
// summary: List issues in a project column
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: id
|
||||
// in: path
|
||||
// description: id of the column
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/IssueList"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
column := getRepoProjectColumn(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
issuesOpts := &issues_model.IssuesOptions{
|
||||
Paginator: &listOptions,
|
||||
RepoIDs: []int64{ctx.Repo.Repository.ID},
|
||||
ProjectID: column.ProjectID,
|
||||
ProjectColumnID: column.ID,
|
||||
SortType: "project-column-sorting",
|
||||
}
|
||||
|
||||
count, err := issues_model.CountIssues(ctx, issuesOpts)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
issues, err := issues_model.Issues(ctx, issuesOpts)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(count, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, ctx.Doer, issues))
|
||||
}
|
||||
|
||||
// AddIssueToProjectColumn adds an issue to a project column
|
||||
func AddIssueToProjectColumn(ctx *context.APIContext) {
|
||||
// swagger:operation POST /repos/{owner}/{repo}/projects/columns/{id}/issues repository repoAddIssueToProjectColumn
|
||||
// swagger:operation POST /repos/{owner}/{repo}/projects/columns/{id}/issues/{issue_id} repository repoAddIssueToProjectColumn
|
||||
// ---
|
||||
// summary: Add an issue to a project column
|
||||
// consumes:
|
||||
@ -598,10 +667,12 @@ func AddIssueToProjectColumn(ctx *context.APIContext) {
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/AddIssueToProjectColumnOption"
|
||||
// - name: issue_id
|
||||
// in: path
|
||||
// description: id of the issue
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// responses:
|
||||
// "201":
|
||||
// "$ref": "#/responses/empty"
|
||||
@ -617,9 +688,7 @@ func AddIssueToProjectColumn(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
form := web.GetForm(ctx).(*api.AddIssueToProjectColumnOption)
|
||||
|
||||
issue, err := issues_model.GetIssueByID(ctx, form.IssueID)
|
||||
issue, err := issues_model.GetIssueByID(ctx, ctx.PathParamInt64("issue_id"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrIssueNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "issue not found")
|
||||
@ -641,3 +710,74 @@ func AddIssueToProjectColumn(ctx *context.APIContext) {
|
||||
|
||||
ctx.Status(http.StatusCreated)
|
||||
}
|
||||
|
||||
// RemoveIssueFromProjectColumn remove an issue from a project column
|
||||
func RemoveIssueFromProjectColumn(ctx *context.APIContext) {
|
||||
// swagger:operation POST /repos/{owner}/{repo}/projects/columns/{id}/issues/{issue_id} repository repoAddIssueToProjectColumn
|
||||
// ---
|
||||
// summary: Add an issue to a project column
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: id
|
||||
// in: path
|
||||
// description: id of the column
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// - name: issue_id
|
||||
// in: path
|
||||
// description: id of the issue
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// responses:
|
||||
// "201":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
column := getRepoProjectColumn(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
issue, err := issues_model.GetIssueByID(ctx, ctx.PathParamInt64("issue_id"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrIssueNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "issue not found")
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if issue.RepoID != ctx.Repo.Repository.ID {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "issue does not belong to this repository")
|
||||
return
|
||||
}
|
||||
|
||||
// 0 means remove
|
||||
if err := issues_model.IssueAssignOrRemoveProject(ctx, issue, ctx.Doer, 0, column.ID); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
@ -240,7 +240,4 @@ type swaggerParameterBodies struct {
|
||||
CreateProjectColumnOption api.CreateProjectColumnOption
|
||||
// in:body
|
||||
EditProjectColumnOption api.EditProjectColumnOption
|
||||
|
||||
// in:body
|
||||
AddIssueToProjectColumnOption api.AddIssueToProjectColumnOption
|
||||
}
|
||||
|
||||
@ -538,9 +538,7 @@ func TestAPIAddIssueToProjectColumn(t *testing.T) {
|
||||
token := getUserToken(t, owner.Name, auth_model.AccessTokenScopeWriteIssue)
|
||||
|
||||
// Test adding issue to column
|
||||
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/%d/issues", owner.Name, repo.Name, column1.ID), &api.AddIssueToProjectColumnOption{
|
||||
IssueID: issue.ID,
|
||||
}).AddTokenAuth(token)
|
||||
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/%d/issues/%d", owner.Name, repo.Name, column1.ID, issue.ID), nil).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
// Verify issue is in the column
|
||||
@ -551,9 +549,7 @@ func TestAPIAddIssueToProjectColumn(t *testing.T) {
|
||||
assert.Equal(t, column1.ID, projectIssue.ProjectColumnID)
|
||||
|
||||
// Test moving issue to another column
|
||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/%d/issues", owner.Name, repo.Name, column2.ID), &api.AddIssueToProjectColumnOption{
|
||||
IssueID: issue.ID,
|
||||
}).AddTokenAuth(token)
|
||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/%d/issues/%d", owner.Name, repo.Name, column2.ID, issue.ID), nil).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
// Verify issue moved to new column
|
||||
@ -564,24 +560,129 @@ func TestAPIAddIssueToProjectColumn(t *testing.T) {
|
||||
assert.Equal(t, column2.ID, projectIssue.ProjectColumnID)
|
||||
|
||||
// Test adding same issue to same column (should be idempotent)
|
||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/%d/issues", owner.Name, repo.Name, column2.ID), &api.AddIssueToProjectColumnOption{
|
||||
IssueID: issue.ID,
|
||||
}).AddTokenAuth(token)
|
||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/%d/issues/%d", owner.Name, repo.Name, column2.ID, issue.ID), nil).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
// Test adding non-existent issue
|
||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/%d/issues", owner.Name, repo.Name, column1.ID), &api.AddIssueToProjectColumnOption{
|
||||
IssueID: 99999,
|
||||
}).AddTokenAuth(token)
|
||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/%d/issues/%d", owner.Name, repo.Name, column1.ID, 99999), nil).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusUnprocessableEntity)
|
||||
|
||||
// Test adding to non-existent column
|
||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/99999/issues", owner.Name, repo.Name), &api.AddIssueToProjectColumnOption{
|
||||
IssueID: issue.ID,
|
||||
}).AddTokenAuth(token)
|
||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/99999/issues/%d", owner.Name, repo.Name, issue.ID), nil).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
}
|
||||
|
||||
func TestAPIListProjectColumnIssues(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID, IsPull: false})
|
||||
pull := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID, IsPull: true})
|
||||
|
||||
project := &project_model.Project{
|
||||
Title: "Project for Column Issues",
|
||||
RepoID: repo.ID,
|
||||
Type: project_model.TypeRepository,
|
||||
CreatorID: owner.ID,
|
||||
TemplateType: project_model.TemplateTypeNone,
|
||||
}
|
||||
err := project_model.NewProject(t.Context(), project)
|
||||
assert.NoError(t, err)
|
||||
defer func() {
|
||||
_ = project_model.DeleteProjectByID(t.Context(), project.ID)
|
||||
}()
|
||||
|
||||
column := &project_model.Column{
|
||||
Title: "Column for Issues",
|
||||
ProjectID: project.ID,
|
||||
CreatorID: owner.ID,
|
||||
}
|
||||
err = project_model.NewColumn(t.Context(), column)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = issues_model.IssueAssignOrRemoveProject(t.Context(), issue, owner, project.ID, column.ID)
|
||||
assert.NoError(t, err)
|
||||
err = issues_model.IssueAssignOrRemoveProject(t.Context(), pull, owner, project.ID, column.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
token := getUserToken(t, owner.Name, auth_model.AccessTokenScopeReadIssue)
|
||||
|
||||
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/projects/columns/%d/issues", owner.Name, repo.Name, column.ID).
|
||||
AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
var issues []api.Issue
|
||||
DecodeJSON(t, resp, &issues)
|
||||
assert.Len(t, issues, 2)
|
||||
|
||||
issueIDs := make(map[int64]struct{}, len(issues))
|
||||
for _, apiIssue := range issues {
|
||||
issueIDs[apiIssue.ID] = struct{}{}
|
||||
}
|
||||
assert.Contains(t, issueIDs, issue.ID)
|
||||
assert.Contains(t, issueIDs, pull.ID)
|
||||
|
||||
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/projects/columns/%d/issues?type=issues", owner.Name, repo.Name, column.ID).
|
||||
AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
DecodeJSON(t, resp, &issues)
|
||||
assert.Len(t, issues, 1)
|
||||
assert.Equal(t, issue.ID, issues[0].ID)
|
||||
|
||||
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/projects/columns/%d/issues?type=pulls", owner.Name, repo.Name, column.ID).
|
||||
AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
DecodeJSON(t, resp, &issues)
|
||||
assert.Len(t, issues, 1)
|
||||
assert.Equal(t, pull.ID, issues[0].ID)
|
||||
}
|
||||
|
||||
func TestAPIRemoveIssueFromProjectColumn(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID})
|
||||
|
||||
project := &project_model.Project{
|
||||
Title: "Project for Issue Removal",
|
||||
RepoID: repo.ID,
|
||||
Type: project_model.TypeRepository,
|
||||
CreatorID: owner.ID,
|
||||
TemplateType: project_model.TemplateTypeNone,
|
||||
}
|
||||
err := project_model.NewProject(t.Context(), project)
|
||||
assert.NoError(t, err)
|
||||
defer func() {
|
||||
_ = project_model.DeleteProjectByID(t.Context(), project.ID)
|
||||
}()
|
||||
|
||||
column := &project_model.Column{
|
||||
Title: "Column for Issue Removal",
|
||||
ProjectID: project.ID,
|
||||
CreatorID: owner.ID,
|
||||
}
|
||||
err = project_model.NewColumn(t.Context(), column)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = issues_model.IssueAssignOrRemoveProject(t.Context(), issue, owner, project.ID, column.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
token := getUserToken(t, owner.Name, auth_model.AccessTokenScopeWriteIssue)
|
||||
|
||||
req := NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/%d/issues/%d", owner.Name, repo.Name, column.ID, issue.ID), nil).
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
unittest.AssertNotExistsBean(t, &project_model.ProjectIssue{
|
||||
ProjectID: project.ID,
|
||||
IssueID: issue.ID,
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIProjectPermissions(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user