* 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 stylesheetstags/v1.6.0-dev
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 |
} | } | ||||
}); | }); | ||||
} | } | ||||
function toggleDuedateForm() { | |||||
$('#add_deadline_form').fadeToggle(150); | |||||
function toggleDeadlineForm() { | |||||
$('#deadlineForm').fadeToggle(150); | |||||
} | } | ||||
function deleteDueDate(url) { | |||||
$.post(url, { | |||||
'_csrf': csrf, | |||||
},function( data ) { | |||||
window.location.reload(); | |||||
function setDeadline() { | |||||
var deadline = $('#deadlineDate').val(); | |||||
updateDeadline(deadline); | |||||
} | |||||
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(); | |||||
}, | |||||
error: function () { | |||||
$('#deadline-loader').removeClass('loading'); | |||||
$('#deadline-err-invalid-date').show(); | |||||
} | |||||
}); | }); | ||||
} | } |
} | } | ||||
} | } | ||||
} | } | ||||
#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; |
} | } | ||||
} | } | ||||
}, | }, | ||||
"/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": [ | ||||
}, | }, | ||||
"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", | ||||
}, | }, | ||||
"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", | ||||
"$ref": "#/definitions/Issue" | "$ref": "#/definitions/Issue" | ||||
} | } | ||||
}, | }, | ||||
"IssueDeadline": { | |||||
"description": "IssueDeadline", | |||||
"schema": { | |||||
"$ref": "#/definitions/IssueDeadline" | |||||
} | |||||
}, | |||||
"IssueList": { | "IssueList": { | ||||
"description": "IssueList", | "description": "IssueList", | ||||
"schema": { | "schema": { |
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() { |
// 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()) | ||||
} | } | ||||
} | } | ||||
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}) | |||||
} |
// 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"` | |||||
} |
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 |
"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 | |||||
} |
}) | }) | ||||
}) | }) | ||||
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) |
<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> | ||||
{{if ne .Issue.DeadlineUnix 0}} | |||||
<p> | |||||
<span class="octicon octicon-calendar"></span> | |||||
{{.Issue.DeadlineUnix.FormatShort}} | |||||
{{if .Issue.IsOverdue}} | |||||
<span style="color: red;">{{.i18n.Tr "repo.issues.due_date_overdue"}}</span> | |||||
{{end}} | |||||
{{if and .IsSigned .IsRepositoryWriter}} | |||||
<br/> | |||||
<a style="cursor:pointer;" onclick="toggleDuedateForm();"><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> | |||||
{{end}} | |||||
</p> | |||||
{{else}} | |||||
<p><i>{{.i18n.Tr "repo.issues.due_date_not_set"}}</i></p> | |||||
{{end}} | |||||
{{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"> | |||||
{{$.CsrfTokenHtml}} | |||||
<div class="ui fluid action input"> | |||||
<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"> | |||||
{{if ne .Issue.DeadlineUnix 0}} | |||||
<i class="edit icon"></i> | |||||
{{else}} | |||||
<i class="plus icon"></i> | |||||
<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}} | |||||
<p> | |||||
<span class="octicon octicon-calendar"></span> | |||||
{{.Issue.DeadlineUnix.FormatShort}} | |||||
{{if .Issue.IsOverdue}} | |||||
<span style="color: red;">{{.i18n.Tr "repo.issues.due_date_overdue"}}</span> | |||||
{{end}} | {{end}} | ||||
</button> | |||||
</div> | |||||
</form> | |||||
{{end}} | |||||
{{if and .IsSigned .IsRepositoryWriter}} | |||||
<br/> | |||||
<a style="cursor:pointer;" onclick="toggleDeadlineForm();"><i class="edit icon"></i>Edit</a> - | |||||
<a style="cursor:pointer;" onclick="updateDeadline('');"><i class="remove icon"></i>Remove</a> | |||||
{{end}} | |||||
</p> | |||||
{{else}} | |||||
<p><i>{{.i18n.Tr "repo.issues.due_date_not_set"}}</i></p> | |||||
{{end}} | |||||
{{if and .IsSigned .IsRepositoryWriter}} | |||||
<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}} | |||||
<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"> | |||||
<button class="ui green icon button"> | |||||
{{if ne .Issue.DeadlineUnix 0}} | |||||
<i class="edit icon"></i> | |||||
{{else}} | |||||
<i class="plus icon"></i> | |||||
{{end}} | |||||
</button> | |||||
</form> | |||||
</div> | |||||
{{end}} | |||||
</div> | |||||
</div> | </div> | ||||
</div> | </div> |