mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-12 13:22:55 +02:00
some improvements
This commit is contained in:
parent
75121570fb
commit
5c141d3c9b
@ -121,11 +121,3 @@ type EditProjectColumnOption struct {
|
|||||||
// Sorting order
|
// Sorting order
|
||||||
Sorting *int `json:"sorting,omitempty"`
|
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("").
|
m.Combo("").
|
||||||
Patch(reqToken(), reqRepoWriter(unit.TypeProjects), mustNotBeArchived, bind(api.EditProjectColumnOption{}), repo.EditProjectColumn).
|
Patch(reqToken(), reqRepoWriter(unit.TypeProjects), mustNotBeArchived, bind(api.EditProjectColumnOption{}), repo.EditProjectColumn).
|
||||||
Delete(reqToken(), reqRepoWriter(unit.TypeProjects), mustNotBeArchived, repo.DeleteProjectColumn)
|
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))
|
}, reqRepoReader(unit.TypeProjects))
|
||||||
}, repoAssignment(), checkTokenPublicOnly())
|
}, repoAssignment(), checkTokenPublicOnly())
|
||||||
|
|||||||
@ -572,9 +572,78 @@ func DeleteProjectColumn(ctx *context.APIContext) {
|
|||||||
ctx.Status(http.StatusNoContent)
|
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
|
// AddIssueToProjectColumn adds an issue to a project column
|
||||||
func AddIssueToProjectColumn(ctx *context.APIContext) {
|
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
|
// summary: Add an issue to a project column
|
||||||
// consumes:
|
// consumes:
|
||||||
@ -598,10 +667,12 @@ func AddIssueToProjectColumn(ctx *context.APIContext) {
|
|||||||
// type: integer
|
// type: integer
|
||||||
// format: int64
|
// format: int64
|
||||||
// required: true
|
// required: true
|
||||||
// - name: body
|
// - name: issue_id
|
||||||
// in: body
|
// in: path
|
||||||
// schema:
|
// description: id of the issue
|
||||||
// "$ref": "#/definitions/AddIssueToProjectColumnOption"
|
// type: integer
|
||||||
|
// format: int64
|
||||||
|
// required: true
|
||||||
// responses:
|
// responses:
|
||||||
// "201":
|
// "201":
|
||||||
// "$ref": "#/responses/empty"
|
// "$ref": "#/responses/empty"
|
||||||
@ -617,9 +688,7 @@ func AddIssueToProjectColumn(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
form := web.GetForm(ctx).(*api.AddIssueToProjectColumnOption)
|
issue, err := issues_model.GetIssueByID(ctx, ctx.PathParamInt64("issue_id"))
|
||||||
|
|
||||||
issue, err := issues_model.GetIssueByID(ctx, form.IssueID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if issues_model.IsErrIssueNotExist(err) {
|
if issues_model.IsErrIssueNotExist(err) {
|
||||||
ctx.APIError(http.StatusUnprocessableEntity, "issue not found")
|
ctx.APIError(http.StatusUnprocessableEntity, "issue not found")
|
||||||
@ -641,3 +710,74 @@ func AddIssueToProjectColumn(ctx *context.APIContext) {
|
|||||||
|
|
||||||
ctx.Status(http.StatusCreated)
|
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
|
CreateProjectColumnOption api.CreateProjectColumnOption
|
||||||
// in:body
|
// in:body
|
||||||
EditProjectColumnOption api.EditProjectColumnOption
|
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)
|
token := getUserToken(t, owner.Name, auth_model.AccessTokenScopeWriteIssue)
|
||||||
|
|
||||||
// Test adding issue to column
|
// 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{
|
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)
|
||||||
IssueID: issue.ID,
|
|
||||||
}).AddTokenAuth(token)
|
|
||||||
MakeRequest(t, req, http.StatusCreated)
|
MakeRequest(t, req, http.StatusCreated)
|
||||||
|
|
||||||
// Verify issue is in the column
|
// Verify issue is in the column
|
||||||
@ -551,9 +549,7 @@ func TestAPIAddIssueToProjectColumn(t *testing.T) {
|
|||||||
assert.Equal(t, column1.ID, projectIssue.ProjectColumnID)
|
assert.Equal(t, column1.ID, projectIssue.ProjectColumnID)
|
||||||
|
|
||||||
// Test moving issue to another column
|
// 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{
|
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)
|
||||||
IssueID: issue.ID,
|
|
||||||
}).AddTokenAuth(token)
|
|
||||||
MakeRequest(t, req, http.StatusCreated)
|
MakeRequest(t, req, http.StatusCreated)
|
||||||
|
|
||||||
// Verify issue moved to new column
|
// Verify issue moved to new column
|
||||||
@ -564,24 +560,129 @@ func TestAPIAddIssueToProjectColumn(t *testing.T) {
|
|||||||
assert.Equal(t, column2.ID, projectIssue.ProjectColumnID)
|
assert.Equal(t, column2.ID, projectIssue.ProjectColumnID)
|
||||||
|
|
||||||
// Test adding same issue to same column (should be idempotent)
|
// 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{
|
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)
|
||||||
IssueID: issue.ID,
|
|
||||||
}).AddTokenAuth(token)
|
|
||||||
MakeRequest(t, req, http.StatusCreated)
|
MakeRequest(t, req, http.StatusCreated)
|
||||||
|
|
||||||
// Test adding non-existent issue
|
// 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{
|
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)
|
||||||
IssueID: 99999,
|
|
||||||
}).AddTokenAuth(token)
|
|
||||||
MakeRequest(t, req, http.StatusUnprocessableEntity)
|
MakeRequest(t, req, http.StatusUnprocessableEntity)
|
||||||
|
|
||||||
// Test adding to non-existent column
|
// 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{
|
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)
|
||||||
IssueID: issue.ID,
|
|
||||||
}).AddTokenAuth(token)
|
|
||||||
MakeRequest(t, req, http.StatusNotFound)
|
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) {
|
func TestAPIProjectPermissions(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user