Issue due date api (#3890)

* Implemented basic api endpoint to manage deadlines

* Fixed checking for permissions

* Updating a deadline from the ui is now entirely done via the api

* cleanup

* Cosmetics

* fixed lint + fmt

* Added swagger model definition for deadline response

* Updated gitea-sdk

* Updated gitea-sdk

* More cleanup

* Generate swagger json

* Merge branch 'master' of https://github.com/go-gitea/gitea into issue-due-date-api

# Conflicts:
#	public/swagger.v1.json

* Fixed permission to update a deadline via api

* Re-added form to change a deadline

* Added client-side validation + not ignore error messages from the api

* Added locale for error message

* Merge branch 'master' of https://github.com/go-gitea/gitea

# Conflicts:
#	models/issue_comment.go

* Proper date validation

* Fixed indention

* moved css to css file

* added documentation for error codes

* after merge cleanup

* Added swagger description

* DO NOTHING BUT TRIGGER THAT F*CKIN CI SO IT PICKS UP THE LATEST COMMIT AS IT SHOULD

* DO NOTHING BUT TRIGGER THAT F*CKIN CI SO IT PICKS UP THE LATEST COMMIT AS IT SHOULD

* regenerated stylesheets
release/v1.15
kolaente 2018-07-16 14:43:00 +02:00 committed by Jonas Franz
parent 55d9ddf24a
commit ef6813abc9
12 changed files with 258 additions and 90 deletions

View File

@ -781,6 +781,7 @@ issues.due_date_added = "added the due date %s %s"
issues.due_date_modified = "modified the due date to %s from %s %s" issues.due_date_modified = "modified the due date to %s from %s %s"
issues.due_date_remove = "removed the due date %s %s" issues.due_date_remove = "removed the due date %s %s"
issues.due_date_overdue = "Overdue" issues.due_date_overdue = "Overdue"
issues.due_date_invalid = "The due date is invalid or out of range. Please use the format yyyy-mm-dd."
pulls.desc = Enable merge requests and code reviews. pulls.desc = Enable merge requests and code reviews.
pulls.new = New Pull Request pulls.new = New Pull Request

File diff suppressed because one or more lines are too long

View File

@ -2447,14 +2447,44 @@ function initTopicbar() {
} }
}); });
} }
function toggleDuedateForm() { function toggleDeadlineForm() {
$('#add_deadline_form').fadeToggle(150); $('#deadlineForm').fadeToggle(150);
} }
function deleteDueDate(url) { function setDeadline() {
$.post(url, { var deadline = $('#deadlineDate').val();
'_csrf': csrf, updateDeadline(deadline);
},function( data ) { }
function updateDeadline(deadlineString) {
$('#deadline-err-invalid-date').hide();
$('#deadline-loader').addClass('loading');
var realDeadline = null;
if (deadlineString !== '') {
var newDate = Date.parse(deadlineString)
if (isNaN(newDate)) {
$('#deadline-loader').removeClass('loading');
$('#deadline-err-invalid-date').show();
return false;
}
realDeadline = new Date(newDate);
}
$.ajax($('#update-issue-deadline-form').attr('action') + '/deadline', {
data: JSON.stringify({
'due_date': realDeadline,
}),
contentType: 'application/json',
type: 'POST',
success: function () {
window.location.reload(); window.location.reload();
},
error: function () {
$('#deadline-loader').removeClass('loading');
$('#deadline-err-invalid-date').show();
}
}); });
} }

View File

@ -98,6 +98,13 @@
} }
} }
} }
#deadlineForm input{
width: 12.8rem;
border-radius: 4px 0 0 4px;
border-right: 0;
white-space: nowrap;
}
} }
.header-wrapper { .header-wrapper {
background-color: #FAFAFA; background-color: #FAFAFA;

View File

@ -2320,6 +2320,68 @@
} }
} }
}, },
"/repos/{owner}/{repo}/issues/{index}/deadline": {
"post": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"issue"
],
"summary": "Set an issue deadline. If set to null, the deadline is deleted.",
"operationId": "issueEditIssueDeadline",
"parameters": [
{
"type": "string",
"description": "owner of the repo",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the repo",
"name": "repo",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "index of the issue to create or update a deadline on",
"name": "index",
"in": "path",
"required": true
},
{
"name": "body",
"in": "body",
"schema": {
"$ref": "#/definitions/EditDeadlineOption"
}
}
],
"responses": {
"201": {
"$ref": "#/responses/IssueDeadline"
},
"403": {
"description": "Not repo writer",
"schema": {
"$ref": "#/responses/forbidden"
}
},
"404": {
"description": "Issue not found",
"schema": {
"$ref": "#/responses/empty"
}
}
}
}
},
"/repos/{owner}/{repo}/issues/{index}/labels": { "/repos/{owner}/{repo}/issues/{index}/labels": {
"get": { "get": {
"produces": [ "produces": [
@ -6145,6 +6207,21 @@
}, },
"x-go-package": "code.gitea.io/gitea/vendor/code.gitea.io/sdk/gitea" "x-go-package": "code.gitea.io/gitea/vendor/code.gitea.io/sdk/gitea"
}, },
"EditDeadlineOption": {
"description": "EditDeadlineOption options for creating a deadline",
"type": "object",
"required": [
"due_date"
],
"properties": {
"due_date": {
"type": "string",
"format": "date-time",
"x-go-name": "Deadline"
}
},
"x-go-package": "code.gitea.io/gitea/vendor/code.gitea.io/sdk/gitea"
},
"EditHookOption": { "EditHookOption": {
"description": "EditHookOption options when modify one hook", "description": "EditHookOption options when modify one hook",
"type": "object", "type": "object",
@ -6635,6 +6712,18 @@
}, },
"x-go-package": "code.gitea.io/gitea/vendor/code.gitea.io/sdk/gitea" "x-go-package": "code.gitea.io/gitea/vendor/code.gitea.io/sdk/gitea"
}, },
"IssueDeadline": {
"description": "IssueDeadline represents an issue deadline",
"type": "object",
"properties": {
"due_date": {
"type": "string",
"format": "date-time",
"x-go-name": "Deadline"
}
},
"x-go-package": "code.gitea.io/gitea/vendor/code.gitea.io/sdk/gitea"
},
"IssueLabelsOption": { "IssueLabelsOption": {
"description": "IssueLabelsOption a collection of labels", "description": "IssueLabelsOption a collection of labels",
"type": "object", "type": "object",
@ -7635,6 +7724,12 @@
"$ref": "#/definitions/Issue" "$ref": "#/definitions/Issue"
} }
}, },
"IssueDeadline": {
"description": "IssueDeadline",
"schema": {
"$ref": "#/definitions/IssueDeadline"
}
},
"IssueList": { "IssueList": {
"description": "IssueList", "description": "IssueList",
"schema": { "schema": {

View File

@ -447,6 +447,8 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Combo("").Get(repo.ListTrackedTimes). m.Combo("").Get(repo.ListTrackedTimes).
Post(reqToken(), bind(api.AddTimeOption{}), repo.AddTime) Post(reqToken(), bind(api.AddTimeOption{}), repo.AddTime)
}) })
m.Combo("/deadline").Post(reqToken(), bind(api.EditDeadlineOption{}), repo.UpdateIssueDeadline)
}) })
}, mustEnableIssues) }, mustEnableIssues)
m.Group("/labels", func() { m.Group("/labels", func() {

View File

@ -278,7 +278,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
// Update the deadline // Update the deadline
var deadlineUnix util.TimeStamp var deadlineUnix util.TimeStamp
if form.Deadline != nil && !form.Deadline.IsZero() { if form.Deadline != nil && !form.Deadline.IsZero() && ctx.Repo.IsWriter() {
deadlineUnix = util.TimeStamp(form.Deadline.Unix()) deadlineUnix = util.TimeStamp(form.Deadline.Unix())
} }
@ -338,3 +338,72 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
} }
ctx.JSON(201, issue.APIFormat()) ctx.JSON(201, issue.APIFormat())
} }
// UpdateIssueDeadline updates an issue deadline
func UpdateIssueDeadline(ctx *context.APIContext, form api.EditDeadlineOption) {
// swagger:operation POST /repos/{owner}/{repo}/issues/{index}/deadline issue issueEditIssueDeadline
// ---
// summary: Set an issue deadline. If set to null, the deadline is deleted.
// 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: index
// in: path
// description: index of the issue to create or update a deadline on
// type: integer
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/EditDeadlineOption"
// responses:
// "201":
// "$ref": "#/responses/IssueDeadline"
// "403":
// description: Not repo writer
// schema:
// "$ref": "#/responses/forbidden"
// "404":
// description: Issue not found
// schema:
// "$ref": "#/responses/empty"
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if models.IsErrIssueNotExist(err) {
ctx.Status(404)
} else {
ctx.Error(500, "GetIssueByIndex", err)
}
return
}
if !ctx.Repo.IsWriter() {
ctx.Status(403)
return
}
var deadlineUnix util.TimeStamp
if form.Deadline != nil && !form.Deadline.IsZero() {
deadlineUnix = util.TimeStamp(form.Deadline.Unix())
}
if err := models.UpdateIssueDeadline(issue, deadlineUnix, ctx.User); err != nil {
ctx.Error(500, "UpdateIssueDeadline", err)
return
}
ctx.JSON(201, api.IssueDeadline{Deadline: form.Deadline})
}

View File

@ -77,3 +77,10 @@ type swaggerResponseTrackedTimeList struct {
// in:body // in:body
Body []api.TrackedTime `json:"body"` Body []api.TrackedTime `json:"body"`
} }
// IssueDeadline
// swagger:response IssueDeadline
type swaggerIssueDeadline struct {
// in:body
Body api.IssueDeadline `json:"body"`
}

View File

@ -32,6 +32,8 @@ type swaggerParameterBodies struct {
CreateIssueOption api.CreateIssueOption CreateIssueOption api.CreateIssueOption
// in:body // in:body
EditIssueOption api.EditIssueOption EditIssueOption api.EditIssueOption
// in:body
EditDeadlineOption api.EditDeadlineOption
// in:body // in:body
CreateIssueCommentOption api.CreateIssueCommentOption CreateIssueCommentOption api.CreateIssueCommentOption

View File

@ -1490,51 +1490,3 @@ func ChangeCommentReaction(ctx *context.Context, form auth.ReactionForm) {
"html": html, "html": html,
}) })
} }
// UpdateDeadline adds or updates a deadline
func UpdateDeadline(ctx *context.Context, form auth.DeadlineForm) {
issue := GetActionIssue(ctx)
if ctx.Written() {
return
}
if ctx.HasError() {
ctx.ServerError("ChangeIssueDeadline", errors.New(ctx.GetErrMsg()))
return
}
// Make unix of deadline string
deadline, err := time.ParseInLocation("2006-01-02", form.DateString, time.Local)
if err != nil {
ctx.Flash.Error(ctx.Tr("repo.issues.invalid_due_date_format"))
ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issue.Index))
return
}
if err = models.UpdateIssueDeadline(issue, util.TimeStamp(deadline.Unix()), ctx.User); err != nil {
ctx.Flash.Error(ctx.Tr("repo.issues.error_modifying_due_date"))
}
ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issue.Index))
return
}
// RemoveDeadline removes a deadline
func RemoveDeadline(ctx *context.Context) {
issue := GetActionIssue(ctx)
if ctx.Written() {
return
}
if ctx.HasError() {
ctx.ServerError("RemoveIssueDeadline", errors.New(ctx.GetErrMsg()))
return
}
if err := models.UpdateIssueDeadline(issue, 0, ctx.User); err != nil {
ctx.Flash.Error(ctx.Tr("repo.issues.error_removing_due_date"))
}
ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issue.Index))
return
}

View File

@ -532,8 +532,6 @@ func RegisterRoutes(m *macaron.Macaron) {
}) })
}) })
m.Post("/reactions/:action", bindIgnErr(auth.ReactionForm{}), repo.ChangeIssueReaction) m.Post("/reactions/:action", bindIgnErr(auth.ReactionForm{}), repo.ChangeIssueReaction)
m.Post("/deadline/update", reqRepoWriter, bindIgnErr(auth.DeadlineForm{}), repo.UpdateDeadline)
m.Post("/deadline/delete", reqRepoWriter, repo.RemoveDeadline)
}) })
m.Post("/labels", reqRepoWriter, repo.UpdateIssueLabel) m.Post("/labels", reqRepoWriter, repo.UpdateIssueLabel)

View File

@ -211,6 +211,11 @@
<div class="ui divider"></div> <div class="ui divider"></div>
<span class="text"><strong>{{.i18n.Tr "repo.issues.due_date"}}</strong></span> <span class="text"><strong>{{.i18n.Tr "repo.issues.due_date"}}</strong></span>
<div class="ui form" id="deadline-loader">
<div class="ui negative message" id="deadline-err-invalid-date" style="display: none;">
<i class="close icon"></i>
{{.i18n.Tr "repo.issues.due_date_invalid"}}
</div>
{{if ne .Issue.DeadlineUnix 0}} {{if ne .Issue.DeadlineUnix 0}}
<p> <p>
<span class="octicon octicon-calendar"></span> <span class="octicon octicon-calendar"></span>
@ -220,8 +225,8 @@
{{end}} {{end}}
{{if and .IsSigned .IsRepositoryWriter}} {{if and .IsSigned .IsRepositoryWriter}}
<br/> <br/>
<a style="cursor:pointer;" onclick="toggleDuedateForm();"><i class="edit icon"></i>Edit</a> - <a style="cursor:pointer;" onclick="toggleDeadlineForm();"><i class="edit icon"></i>Edit</a> -
<a style="cursor:pointer;" onclick="deleteDueDate('{{$.RepoLink}}/issues/{{.Issue.Index}}/deadline/delete');"><i class="remove icon"></i>Remove</a> <a style="cursor:pointer;" onclick="updateDeadline('');"><i class="remove icon"></i>Remove</a>
{{end}} {{end}}
</p> </p>
{{else}} {{else}}
@ -229,10 +234,10 @@
{{end}} {{end}}
{{if and .IsSigned .IsRepositoryWriter}} {{if and .IsSigned .IsRepositoryWriter}}
<form method="POST" {{if ne .Issue.DeadlineUnix 0}}style="display: none;"{{end}} id="add_deadline_form" action="{{$.RepoLink}}/issues/{{.Issue.Index}}/deadline/update" class="ui action input fluid"> <div {{if ne .Issue.DeadlineUnix 0}} style="display: none;"{{end}} id="deadlineForm">
<form class="ui fluid action input" action="{{AppSubUrl}}/api/v1/repos/{{.Repository.Owner.Name}}/{{.Repository.Name}}/issues/{{.Issue.Index}}" method="post" id="update-issue-deadline-form" onsubmit="setDeadline();return false;">
{{$.CsrfTokenHtml}} {{$.CsrfTokenHtml}}
<div class="ui fluid action input"> <input required placeholder="{{.i18n.Tr "repo.issues.due_date_form"}}" {{if gt .Issue.DeadlineUnix 0}}value="{{.Issue.DeadlineUnix.Format "2006-01-02"}}"{{end}} type="date" name="deadlineDate" id="deadlineDate">
<input required placeholder="{{.i18n.Tr "repo.issues.due_date_form"}}" {{if ne .Issue.DeadlineUnix 0 }}value="{{.Issue.DeadlineUnix.Format "2006-01-02"}}"{{end}} type="date" name="date" style="min-width: 13.9rem;border-radius: 4px 0 0 4px;border-right: 0;white-space: nowrap;">
<button class="ui green icon button"> <button class="ui green icon button">
{{if ne .Issue.DeadlineUnix 0}} {{if ne .Issue.DeadlineUnix 0}}
<i class="edit icon"></i> <i class="edit icon"></i>
@ -240,9 +245,9 @@
<i class="plus icon"></i> <i class="plus icon"></i>
{{end}} {{end}}
</button> </button>
</div>
</form> </form>
</div>
{{end}} {{end}}
</div>
</div> </div>
</div> </div>