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
This commit is contained in:
		
							parent
							
								
									55d9ddf24a
								
							
						
					
					
						commit
						ef6813abc9
					
				
					 12 changed files with 258 additions and 90 deletions
				
			
		|  | @ -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_remove = "removed the due date %s %s" | ||||
| 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.new = New Pull Request | ||||
|  |  | |||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							|  | @ -2447,14 +2447,44 @@ function initTopicbar() { | |||
|             } | ||||
|         }); | ||||
| } | ||||
| function toggleDuedateForm() { | ||||
|     $('#add_deadline_form').fadeToggle(150); | ||||
| function toggleDeadlineForm() { | ||||
|     $('#deadlineForm').fadeToggle(150); | ||||
| } | ||||
| 
 | ||||
| function deleteDueDate(url) { | ||||
|     $.post(url, { | ||||
|         '_csrf': csrf, | ||||
|     },function( data ) { | ||||
| 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(); | ||||
|         } | ||||
|     }); | ||||
| } | ||||
|  |  | |||
|  | @ -98,6 +98,13 @@ | |||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         #deadlineForm input{ | ||||
|           width: 12.8rem; | ||||
|           border-radius: 4px 0 0 4px; | ||||
|           border-right: 0; | ||||
|           white-space: nowrap; | ||||
|         } | ||||
|     } | ||||
|     .header-wrapper { | ||||
|         background-color: #FAFAFA; | ||||
|  |  | |||
							
								
								
									
										95
									
								
								public/swagger.v1.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										95
									
								
								public/swagger.v1.json
									
									
									
									
										vendored
									
									
								
							|  | @ -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": { | ||||
|       "get": { | ||||
|         "produces": [ | ||||
|  | @ -6145,6 +6207,21 @@ | |||
|       }, | ||||
|       "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": { | ||||
|       "description": "EditHookOption options when modify one hook", | ||||
|       "type": "object", | ||||
|  | @ -6635,6 +6712,18 @@ | |||
|       }, | ||||
|       "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": { | ||||
|       "description": "IssueLabelsOption a collection of labels", | ||||
|       "type": "object", | ||||
|  | @ -7635,6 +7724,12 @@ | |||
|         "$ref": "#/definitions/Issue" | ||||
|       } | ||||
|     }, | ||||
|     "IssueDeadline": { | ||||
|       "description": "IssueDeadline", | ||||
|       "schema": { | ||||
|         "$ref": "#/definitions/IssueDeadline" | ||||
|       } | ||||
|     }, | ||||
|     "IssueList": { | ||||
|       "description": "IssueList", | ||||
|       "schema": { | ||||
|  |  | |||
|  | @ -447,6 +447,8 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| 							m.Combo("").Get(repo.ListTrackedTimes). | ||||
| 								Post(reqToken(), bind(api.AddTimeOption{}), repo.AddTime) | ||||
| 						}) | ||||
| 
 | ||||
| 						m.Combo("/deadline").Post(reqToken(), bind(api.EditDeadlineOption{}), repo.UpdateIssueDeadline) | ||||
| 					}) | ||||
| 				}, mustEnableIssues) | ||||
| 				m.Group("/labels", func() { | ||||
|  |  | |||
|  | @ -278,7 +278,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) { | |||
| 
 | ||||
| 	// Update the deadline
 | ||||
| 	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()) | ||||
| 	} | ||||
| 
 | ||||
|  | @ -338,3 +338,72 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) { | |||
| 	} | ||||
| 	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}) | ||||
| } | ||||
|  |  | |||
|  | @ -77,3 +77,10 @@ type swaggerResponseTrackedTimeList struct { | |||
| 	// in:body
 | ||||
| 	Body []api.TrackedTime `json:"body"` | ||||
| } | ||||
| 
 | ||||
| // IssueDeadline
 | ||||
| // swagger:response IssueDeadline
 | ||||
| type swaggerIssueDeadline struct { | ||||
| 	// in:body
 | ||||
| 	Body api.IssueDeadline `json:"body"` | ||||
| } | ||||
|  |  | |||
|  | @ -32,6 +32,8 @@ type swaggerParameterBodies struct { | |||
| 	CreateIssueOption api.CreateIssueOption | ||||
| 	// in:body
 | ||||
| 	EditIssueOption api.EditIssueOption | ||||
| 	// in:body
 | ||||
| 	EditDeadlineOption api.EditDeadlineOption | ||||
| 
 | ||||
| 	// in:body
 | ||||
| 	CreateIssueCommentOption api.CreateIssueCommentOption | ||||
|  |  | |||
|  | @ -1490,51 +1490,3 @@ func ChangeCommentReaction(ctx *context.Context, form auth.ReactionForm) { | |||
| 		"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 | ||||
| } | ||||
|  |  | |||
|  | @ -532,8 +532,6 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| 					}) | ||||
| 				}) | ||||
| 				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) | ||||
|  |  | |||
|  | @ -211,6 +211,11 @@ | |||
| 
 | ||||
| 		<div class="ui divider"></div> | ||||
| 		<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}} | ||||
| 				<p> | ||||
| 					<span class="octicon octicon-calendar"></span> | ||||
|  | @ -220,8 +225,8 @@ | |||
| 					{{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> | ||||
| 						<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}} | ||||
|  | @ -229,10 +234,10 @@ | |||
| 			{{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"> | ||||
| 				<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}} | ||||
| 				<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;"> | ||||
| 						<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> | ||||
|  | @ -240,9 +245,9 @@ | |||
| 								<i class="plus icon"></i> | ||||
| 							{{end}} | ||||
| 						</button> | ||||
| 				</div> | ||||
| 					</form> | ||||
| 				</div> | ||||
| 			{{end}} | ||||
| 
 | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue