* Started adding deadline to ui * Implemented basic issue due date managing * Improved UI for due date managing * Added at least write access to the repo in order to modify issue due dates * Ui improvements * Added issue comments creation when adding/modifying/removing a due date * Show due date in issue list * Added api support for issue due dates * Fixed lint suggestions * Added deadline to sdk * Updated css * Added support for adding/modifiying deadlines for pull requests via api * Fixed comments not created when updating or removing a deadline * update sdk (will do properly once go-gitea/go-sdk#103 is merged) * enhanced updateIssueDeadline * Removed unnessecary Issue.DeadlineString * UI improvements * Small improvments to comment creation + ui & validation improvements * Check if an issue is overdue is now a seperate function * Updated go-sdk with govendor as it was merged * Simplified isOverdue method * removed unessecary deadline to 0 set * Update swagger definitions * Added missing return * Added an explanary comment * Improved updateIssueDeadline method so it'll only update `deadline_unix` * Small changes and improvements * no need to explicitly load the issue when updating a deadline, just use whats already there * small optimisations * Added check if a deadline was modified before updating it * Moved comment creating logic into its own function * Code cleanup for creating deadline comment * locale improvement * When modifying a deadline, the old deadline is saved with the comment * small improvments to xorm session handling when updating an issue deadline + style nitpicks * style nitpicks * Moved checking for if the user has write acces to middlewaretags/v1.21.12.1
| @@ -47,9 +47,10 @@ type Issue struct { | |||||
| Ref string | Ref string | ||||
| DeadlineUnix util.TimeStamp `xorm:"INDEX"` | DeadlineUnix util.TimeStamp `xorm:"INDEX"` | ||||
| CreatedUnix util.TimeStamp `xorm:"INDEX created"` | |||||
| UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` | |||||
| ClosedUnix util.TimeStamp `xorm:"INDEX"` | |||||
| CreatedUnix util.TimeStamp `xorm:"INDEX created"` | |||||
| UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` | |||||
| ClosedUnix util.TimeStamp `xorm:"INDEX"` | |||||
| Attachments []*Attachment `xorm:"-"` | Attachments []*Attachment `xorm:"-"` | ||||
| Comments []*Comment `xorm:"-"` | Comments []*Comment `xorm:"-"` | ||||
| @@ -79,6 +80,11 @@ func (issue *Issue) loadTotalTimes(e Engine) (err error) { | |||||
| return nil | return nil | ||||
| } | } | ||||
| // IsOverdue checks if the issue is overdue | |||||
| func (issue *Issue) IsOverdue() bool { | |||||
| return util.TimeStampNow() >= issue.DeadlineUnix | |||||
| } | |||||
| func (issue *Issue) loadRepo(e Engine) (err error) { | func (issue *Issue) loadRepo(e Engine) (err error) { | ||||
| if issue.Repo == nil { | if issue.Repo == nil { | ||||
| issue.Repo, err = getRepositoryByID(e, issue.RepoID) | issue.Repo, err = getRepositoryByID(e, issue.RepoID) | ||||
| @@ -348,6 +354,9 @@ func (issue *Issue) APIFormat() *api.Issue { | |||||
| apiIssue.PullRequest.Merged = issue.PullRequest.MergedUnix.AsTimePtr() | apiIssue.PullRequest.Merged = issue.PullRequest.MergedUnix.AsTimePtr() | ||||
| } | } | ||||
| } | } | ||||
| if issue.DeadlineUnix != 0 { | |||||
| apiIssue.Deadline = issue.DeadlineUnix.AsTimePtr() | |||||
| } | |||||
| return apiIssue | return apiIssue | ||||
| } | } | ||||
| @@ -1522,3 +1531,30 @@ func updateIssue(e Engine, issue *Issue) error { | |||||
| func UpdateIssue(issue *Issue) error { | func UpdateIssue(issue *Issue) error { | ||||
| return updateIssue(x, issue) | return updateIssue(x, issue) | ||||
| } | } | ||||
| // UpdateIssueDeadline updates an issue deadline and adds comments. Setting a deadline to 0 means deleting it. | |||||
| func UpdateIssueDeadline(issue *Issue, deadlineUnix util.TimeStamp, doer *User) (err error) { | |||||
| // if the deadline hasn't changed do nothing | |||||
| if issue.DeadlineUnix == deadlineUnix { | |||||
| return nil | |||||
| } | |||||
| sess := x.NewSession() | |||||
| defer sess.Close() | |||||
| if err := sess.Begin(); err != nil { | |||||
| return err | |||||
| } | |||||
| // Update the deadline | |||||
| if err = updateIssueCols(sess, &Issue{ID: issue.ID, DeadlineUnix: deadlineUnix}, "deadline_unix"); err != nil { | |||||
| return err | |||||
| } | |||||
| // Make the comment | |||||
| if _, err = createDeadlineComment(sess, doer, issue, deadlineUnix); err != nil { | |||||
| return fmt.Errorf("createRemovedDueDateComment: %v", err) | |||||
| } | |||||
| return sess.Commit() | |||||
| } | |||||
| @@ -60,6 +60,12 @@ const ( | |||||
| CommentTypeAddTimeManual | CommentTypeAddTimeManual | ||||
| // Cancel a stopwatch for time tracking | // Cancel a stopwatch for time tracking | ||||
| CommentTypeCancelTracking | CommentTypeCancelTracking | ||||
| // Added a due date | |||||
| CommentTypeAddedDeadline | |||||
| // Modified the due date | |||||
| CommentTypeModifiedDeadline | |||||
| // Removed a due date | |||||
| CommentTypeRemovedDeadline | |||||
| ) | ) | ||||
| // CommentTag defines comment tag type | // CommentTag defines comment tag type | ||||
| @@ -485,6 +491,34 @@ func createAssigneeComment(e *xorm.Session, doer *User, repo *Repository, issue | |||||
| }) | }) | ||||
| } | } | ||||
| func createDeadlineComment(e *xorm.Session, doer *User, issue *Issue, newDeadlineUnix util.TimeStamp) (*Comment, error) { | |||||
| var content string | |||||
| var commentType CommentType | |||||
| // newDeadline = 0 means deleting | |||||
| if newDeadlineUnix == 0 { | |||||
| commentType = CommentTypeRemovedDeadline | |||||
| content = issue.DeadlineUnix.Format("2006-01-02") | |||||
| } else if issue.DeadlineUnix == 0 { | |||||
| // Check if the new date was added or modified | |||||
| // If the actual deadline is 0 => deadline added | |||||
| commentType = CommentTypeAddedDeadline | |||||
| content = newDeadlineUnix.Format("2006-01-02") | |||||
| } else { // Otherwise modified | |||||
| commentType = CommentTypeModifiedDeadline | |||||
| content = newDeadlineUnix.Format("2006-01-02") + "|" + issue.DeadlineUnix.Format("2006-01-02") | |||||
| } | |||||
| return createComment(e, &CreateCommentOptions{ | |||||
| Type: commentType, | |||||
| Doer: doer, | |||||
| Repo: issue.Repo, | |||||
| Issue: issue, | |||||
| Content: content, | |||||
| }) | |||||
| } | |||||
| func createChangeTitleComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue, oldTitle, newTitle string) (*Comment, error) { | func createChangeTitleComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue, oldTitle, newTitle string) (*Comment, error) { | ||||
| return createComment(e, &CreateCommentOptions{ | return createComment(e, &CreateCommentOptions{ | ||||
| Type: CommentTypeChangeTitle, | Type: CommentTypeChangeTitle, | ||||
| @@ -207,6 +207,7 @@ func (pr *PullRequest) APIFormat() *api.PullRequest { | |||||
| Base: apiBaseBranchInfo, | Base: apiBaseBranchInfo, | ||||
| Head: apiHeadBranchInfo, | Head: apiHeadBranchInfo, | ||||
| MergeBase: pr.MergeBase, | MergeBase: pr.MergeBase, | ||||
| Deadline: apiIssue.Deadline, | |||||
| Created: pr.Issue.CreatedUnix.AsTimePtr(), | Created: pr.Issue.CreatedUnix.AsTimePtr(), | ||||
| Updated: pr.Issue.UpdatedUnix.AsTimePtr(), | Updated: pr.Issue.UpdatedUnix.AsTimePtr(), | ||||
| } | } | ||||
| @@ -521,3 +521,13 @@ func (f *AddTimeManuallyForm) Validate(ctx *macaron.Context, errs binding.Errors | |||||
| type SaveTopicForm struct { | type SaveTopicForm struct { | ||||
| Topics []string `binding:"topics;Required;"` | Topics []string `binding:"topics;Required;"` | ||||
| } | } | ||||
| // DeadlineForm hold the validation rules for deadlines | |||||
| type DeadlineForm struct { | |||||
| DateString string `form:"date" binding:"Required;Size(10)"` | |||||
| } | |||||
| // Validate validates the fields | |||||
| func (f *DeadlineForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { | |||||
| return validate(errs, ctx.Data, f, ctx.Locale) | |||||
| } | |||||
| @@ -183,6 +183,9 @@ func NewFuncMap() []template.FuncMap { | |||||
| "Printf": fmt.Sprintf, | "Printf": fmt.Sprintf, | ||||
| "Escape": Escape, | "Escape": Escape, | ||||
| "Sec2Time": models.SecToTime, | "Sec2Time": models.SecToTime, | ||||
| "ParseDeadline": func(deadline string) []string { | |||||
| return strings.Split(deadline, "|") | |||||
| }, | |||||
| }} | }} | ||||
| } | } | ||||
| @@ -736,8 +736,22 @@ issues.add_time_minutes = Minutes | |||||
| issues.add_time_sum_to_small = No time was entered. | issues.add_time_sum_to_small = No time was entered. | ||||
| issues.cancel_tracking = Cancel | issues.cancel_tracking = Cancel | ||||
| issues.cancel_tracking_history = `cancelled time tracking %s` | issues.cancel_tracking_history = `cancelled time tracking %s` | ||||
| issues.time_spent_total = Total Time Spent | |||||
| issues.time_spent_from_all_authors = `Total Time Spent: %s` | issues.time_spent_from_all_authors = `Total Time Spent: %s` | ||||
| issues.due_date = Due date | |||||
| issues.invalid_due_date_format = "Due date format is invalid, must be 'yyyy-mm-dd'." | |||||
| issues.error_modifying_due_date = "An error occured while modifying the due date." | |||||
| issues.error_removing_due_date = "An error occured while remvoing the due date." | |||||
| issues.due_date_form = "Due date, format yyyy-mm-dd" | |||||
| issues.due_date_form_add = "Add due date" | |||||
| issues.due_date_form_update = "Update due date" | |||||
| issues.due_date_form_remove = "Remove due date" | |||||
| issues.due_date_not_writer = "You need to have at least write access to this repository in order to update the due date for this issue." | |||||
| issues.due_date_not_set = "No due date set." | |||||
| 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" | |||||
| 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 | ||||
| @@ -2193,4 +2193,15 @@ function initTopicbar() { | |||||
| }, | }, | ||||
| }, | }, | ||||
| }); | }); | ||||
| } | |||||
| } | |||||
| function toggleDuedateForm() { | |||||
| $('#add_deadline_form').fadeToggle(150); | |||||
| } | |||||
| function deleteDueDate(url) { | |||||
| $.post(url, { | |||||
| '_csrf': csrf, | |||||
| },function( data ) { | |||||
| window.location.reload(); | |||||
| }); | |||||
| } | |||||
| @@ -1545,6 +1545,9 @@ | |||||
| margin-top: -5px; | margin-top: -5px; | ||||
| margin-right: 5px; | margin-right: 5px; | ||||
| } | } | ||||
| .overdue{ | |||||
| color: red; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -5579,6 +5579,13 @@ | |||||
| "type": "string", | "type": "string", | ||||
| "x-go-name": "Assignee" | "x-go-name": "Assignee" | ||||
| }, | }, | ||||
| "assignees": { | |||||
| "type": "array", | |||||
| "items": { | |||||
| "type": "string" | |||||
| }, | |||||
| "x-go-name": "Assignees" | |||||
| }, | |||||
| "body": { | "body": { | ||||
| "type": "string", | "type": "string", | ||||
| "x-go-name": "Body" | "x-go-name": "Body" | ||||
| @@ -5587,6 +5594,11 @@ | |||||
| "type": "boolean", | "type": "boolean", | ||||
| "x-go-name": "Closed" | "x-go-name": "Closed" | ||||
| }, | }, | ||||
| "due_date": { | |||||
| "type": "string", | |||||
| "format": "date-time", | |||||
| "x-go-name": "Deadline" | |||||
| }, | |||||
| "labels": { | "labels": { | ||||
| "description": "list of label ids", | "description": "list of label ids", | ||||
| "type": "array", | "type": "array", | ||||
| @@ -5715,6 +5727,13 @@ | |||||
| "type": "string", | "type": "string", | ||||
| "x-go-name": "Assignee" | "x-go-name": "Assignee" | ||||
| }, | }, | ||||
| "assignees": { | |||||
| "type": "array", | |||||
| "items": { | |||||
| "type": "string" | |||||
| }, | |||||
| "x-go-name": "Assignees" | |||||
| }, | |||||
| "base": { | "base": { | ||||
| "type": "string", | "type": "string", | ||||
| "x-go-name": "Base" | "x-go-name": "Base" | ||||
| @@ -5723,6 +5742,11 @@ | |||||
| "type": "string", | "type": "string", | ||||
| "x-go-name": "Body" | "x-go-name": "Body" | ||||
| }, | }, | ||||
| "due_date": { | |||||
| "type": "string", | |||||
| "format": "date-time", | |||||
| "x-go-name": "Deadline" | |||||
| }, | |||||
| "head": { | "head": { | ||||
| "type": "string", | "type": "string", | ||||
| "x-go-name": "Head" | "x-go-name": "Head" | ||||
| @@ -6024,10 +6048,22 @@ | |||||
| "type": "string", | "type": "string", | ||||
| "x-go-name": "Assignee" | "x-go-name": "Assignee" | ||||
| }, | }, | ||||
| "assignees": { | |||||
| "type": "array", | |||||
| "items": { | |||||
| "type": "string" | |||||
| }, | |||||
| "x-go-name": "Assignees" | |||||
| }, | |||||
| "body": { | "body": { | ||||
| "type": "string", | "type": "string", | ||||
| "x-go-name": "Body" | "x-go-name": "Body" | ||||
| }, | }, | ||||
| "due_date": { | |||||
| "type": "string", | |||||
| "format": "date-time", | |||||
| "x-go-name": "Deadline" | |||||
| }, | |||||
| "milestone": { | "milestone": { | ||||
| "type": "integer", | "type": "integer", | ||||
| "format": "int64", | "format": "int64", | ||||
| @@ -6114,10 +6150,22 @@ | |||||
| "type": "string", | "type": "string", | ||||
| "x-go-name": "Assignee" | "x-go-name": "Assignee" | ||||
| }, | }, | ||||
| "assignees": { | |||||
| "type": "array", | |||||
| "items": { | |||||
| "type": "string" | |||||
| }, | |||||
| "x-go-name": "Assignees" | |||||
| }, | |||||
| "body": { | "body": { | ||||
| "type": "string", | "type": "string", | ||||
| "x-go-name": "Body" | "x-go-name": "Body" | ||||
| }, | }, | ||||
| "due_date": { | |||||
| "type": "string", | |||||
| "format": "date-time", | |||||
| "x-go-name": "Deadline" | |||||
| }, | |||||
| "labels": { | "labels": { | ||||
| "type": "array", | "type": "array", | ||||
| "items": { | "items": { | ||||
| @@ -6367,10 +6415,22 @@ | |||||
| "assignee": { | "assignee": { | ||||
| "$ref": "#/definitions/User" | "$ref": "#/definitions/User" | ||||
| }, | }, | ||||
| "assignees": { | |||||
| "type": "array", | |||||
| "items": { | |||||
| "$ref": "#/definitions/User" | |||||
| }, | |||||
| "x-go-name": "Assignees" | |||||
| }, | |||||
| "body": { | "body": { | ||||
| "type": "string", | "type": "string", | ||||
| "x-go-name": "Body" | "x-go-name": "Body" | ||||
| }, | }, | ||||
| "closed_at": { | |||||
| "type": "string", | |||||
| "format": "date-time", | |||||
| "x-go-name": "Closed" | |||||
| }, | |||||
| "comments": { | "comments": { | ||||
| "type": "integer", | "type": "integer", | ||||
| "format": "int64", | "format": "int64", | ||||
| @@ -6381,6 +6441,11 @@ | |||||
| "format": "date-time", | "format": "date-time", | ||||
| "x-go-name": "Created" | "x-go-name": "Created" | ||||
| }, | }, | ||||
| "due_date": { | |||||
| "type": "string", | |||||
| "format": "date-time", | |||||
| "x-go-name": "Deadline" | |||||
| }, | |||||
| "id": { | "id": { | ||||
| "type": "integer", | "type": "integer", | ||||
| "format": "int64", | "format": "int64", | ||||
| @@ -6778,6 +6843,13 @@ | |||||
| "assignee": { | "assignee": { | ||||
| "$ref": "#/definitions/User" | "$ref": "#/definitions/User" | ||||
| }, | }, | ||||
| "assignees": { | |||||
| "type": "array", | |||||
| "items": { | |||||
| "$ref": "#/definitions/User" | |||||
| }, | |||||
| "x-go-name": "Assignees" | |||||
| }, | |||||
| "base": { | "base": { | ||||
| "$ref": "#/definitions/PRBranchInfo" | "$ref": "#/definitions/PRBranchInfo" | ||||
| }, | }, | ||||
| @@ -6785,6 +6857,11 @@ | |||||
| "type": "string", | "type": "string", | ||||
| "x-go-name": "Body" | "x-go-name": "Body" | ||||
| }, | }, | ||||
| "closed_at": { | |||||
| "type": "string", | |||||
| "format": "date-time", | |||||
| "x-go-name": "Closed" | |||||
| }, | |||||
| "comments": { | "comments": { | ||||
| "type": "integer", | "type": "integer", | ||||
| "format": "int64", | "format": "int64", | ||||
| @@ -6799,6 +6876,11 @@ | |||||
| "type": "string", | "type": "string", | ||||
| "x-go-name": "DiffURL" | "x-go-name": "DiffURL" | ||||
| }, | }, | ||||
| "due_date": { | |||||
| "type": "string", | |||||
| "format": "date-time", | |||||
| "x-go-name": "Deadline" | |||||
| }, | |||||
| "head": { | "head": { | ||||
| "$ref": "#/definitions/PRBranchInfo" | "$ref": "#/definitions/PRBranchInfo" | ||||
| }, | }, | ||||
| @@ -163,12 +163,19 @@ func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) { | |||||
| // responses: | // responses: | ||||
| // "201": | // "201": | ||||
| // "$ref": "#/responses/Issue" | // "$ref": "#/responses/Issue" | ||||
| var deadlineUnix util.TimeStamp | |||||
| if form.Deadline != nil { | |||||
| deadlineUnix = util.TimeStamp(form.Deadline.Unix()) | |||||
| } | |||||
| issue := &models.Issue{ | issue := &models.Issue{ | ||||
| RepoID: ctx.Repo.Repository.ID, | |||||
| Title: form.Title, | |||||
| PosterID: ctx.User.ID, | |||||
| Poster: ctx.User, | |||||
| Content: form.Body, | |||||
| RepoID: ctx.Repo.Repository.ID, | |||||
| Title: form.Title, | |||||
| PosterID: ctx.User.ID, | |||||
| Poster: ctx.User, | |||||
| Content: form.Body, | |||||
| DeadlineUnix: deadlineUnix, | |||||
| } | } | ||||
| if ctx.Repo.IsWriter() { | if ctx.Repo.IsWriter() { | ||||
| @@ -265,6 +272,16 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) { | |||||
| issue.Content = *form.Body | issue.Content = *form.Body | ||||
| } | } | ||||
| 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 | |||||
| } | |||||
| if ctx.Repo.IsWriter() && form.Assignee != nil && | if ctx.Repo.IsWriter() && form.Assignee != nil && | ||||
| (issue.Assignee == nil || issue.Assignee.LowerName != strings.ToLower(*form.Assignee)) { | (issue.Assignee == nil || issue.Assignee.LowerName != strings.ToLower(*form.Assignee)) { | ||||
| if len(*form.Assignee) == 0 { | if len(*form.Assignee) == 0 { | ||||
| @@ -13,6 +13,7 @@ import ( | |||||
| "code.gitea.io/gitea/modules/auth" | "code.gitea.io/gitea/modules/auth" | ||||
| "code.gitea.io/gitea/modules/context" | "code.gitea.io/gitea/modules/context" | ||||
| "code.gitea.io/gitea/modules/log" | "code.gitea.io/gitea/modules/log" | ||||
| "code.gitea.io/gitea/modules/util" | |||||
| api "code.gitea.io/sdk/gitea" | api "code.gitea.io/sdk/gitea" | ||||
| ) | ) | ||||
| @@ -236,16 +237,22 @@ func CreatePullRequest(ctx *context.APIContext, form api.CreatePullRequestOption | |||||
| return | return | ||||
| } | } | ||||
| var deadlineUnix util.TimeStamp | |||||
| if form.Deadline != nil { | |||||
| deadlineUnix = util.TimeStamp(form.Deadline.Unix()) | |||||
| } | |||||
| prIssue := &models.Issue{ | prIssue := &models.Issue{ | ||||
| RepoID: repo.ID, | |||||
| Index: repo.NextIssueIndex(), | |||||
| Title: form.Title, | |||||
| PosterID: ctx.User.ID, | |||||
| Poster: ctx.User, | |||||
| MilestoneID: milestoneID, | |||||
| AssigneeID: assigneeID, | |||||
| IsPull: true, | |||||
| Content: form.Body, | |||||
| RepoID: repo.ID, | |||||
| Index: repo.NextIssueIndex(), | |||||
| Title: form.Title, | |||||
| PosterID: ctx.User.ID, | |||||
| Poster: ctx.User, | |||||
| MilestoneID: milestoneID, | |||||
| AssigneeID: assigneeID, | |||||
| IsPull: true, | |||||
| Content: form.Body, | |||||
| DeadlineUnix: deadlineUnix, | |||||
| } | } | ||||
| pr := &models.PullRequest{ | pr := &models.PullRequest{ | ||||
| HeadRepoID: headRepo.ID, | HeadRepoID: headRepo.ID, | ||||
| @@ -328,6 +335,16 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) { | |||||
| issue.Content = form.Body | issue.Content = form.Body | ||||
| } | } | ||||
| 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 | |||||
| } | |||||
| if ctx.Repo.IsWriter() && len(form.Assignee) > 0 && | if ctx.Repo.IsWriter() && len(form.Assignee) > 0 && | ||||
| (issue.Assignee == nil || issue.Assignee.LowerName != strings.ToLower(form.Assignee)) { | (issue.Assignee == nil || issue.Assignee.LowerName != strings.ToLower(form.Assignee)) { | ||||
| if len(form.Assignee) == 0 { | if len(form.Assignee) == 0 { | ||||
| @@ -1467,3 +1467,51 @@ 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 | |||||
| } | |||||
| @@ -496,6 +496,8 @@ 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) | ||||
| @@ -216,6 +216,10 @@ | |||||
| <span class="octicon octicon-milestone"></span> {{.Milestone.Name}} | <span class="octicon octicon-milestone"></span> {{.Milestone.Name}} | ||||
| </a> | </a> | ||||
| {{end}} | {{end}} | ||||
| {{if ne .DeadlineUnix 0}} | |||||
| <span class="octicon octicon-calendar"></span> | |||||
| <span{{if .IsOverdue}} class="overdue"{{end}}>{{.DeadlineUnix.FormatShort}}</span> | |||||
| {{end}} | |||||
| {{if .Assignee}} | {{if .Assignee}} | ||||
| <a class="ui right assignee poping up" href="{{.Assignee.HomeLink}}" data-content="{{.Assignee.Name}}" data-variation="inverted" data-position="left center"> | <a class="ui right assignee poping up" href="{{.Assignee.HomeLink}}" data-content="{{.Assignee.Name}}" data-variation="inverted" data-position="left center"> | ||||
| <img class="ui avatar image" src="{{.Assignee.RelAvatarLink}}"> | <img class="ui avatar image" src="{{.Assignee.RelAvatarLink}}"> | ||||
| @@ -56,7 +56,7 @@ | |||||
| {{else}} | {{else}} | ||||
| <span class="octicon octicon-calendar"></span> | <span class="octicon octicon-calendar"></span> | ||||
| {{if .DeadlineString}} | {{if .DeadlineString}} | ||||
| <span {{if .IsOverDue}}class="overdue"{{end}}>{{.DeadlineString}}</span> | |||||
| <span {{if .IsOverdue}}class="overdue"{{end}}>{{.DeadlineString}}</span> | |||||
| {{else}} | {{else}} | ||||
| {{$.i18n.Tr "repo.milestones.no_due_date"}} | {{$.i18n.Tr "repo.milestones.no_due_date"}} | ||||
| {{end}} | {{end}} | ||||
| @@ -1,7 +1,7 @@ | |||||
| {{range .Issue.Comments}} | {{range .Issue.Comments}} | ||||
| {{ $createdStr:= TimeSinceUnix .CreatedUnix $.Lang }} | {{ $createdStr:= TimeSinceUnix .CreatedUnix $.Lang }} | ||||
| <!-- 0 = COMMENT, 1 = REOPEN, 2 = CLOSE, 3 = ISSUE_REF, 4 = COMMIT_REF, 5 = COMMENT_REF, 6 = PULL_REF, 7 = COMMENT_LABEL, 12 = START_TRACKING, 13 = STOP_TRACKING, 14 = ADD_TIME_MANUAL --> | |||||
| <!-- 0 = COMMENT, 1 = REOPEN, 2 = CLOSE, 3 = ISSUE_REF, 4 = COMMIT_REF, 5 = COMMENT_REF, 6 = PULL_REF, 7 = COMMENT_LABEL, 12 = START_TRACKING, 13 = STOP_TRACKING, 14 = ADD_TIME_MANUAL, 16 = ADDED_DEADLINE, 17 = MODIFIED_DEADLINE, 18 = REMOVED_DEADLINE --> | |||||
| {{if eq .Type 0}} | {{if eq .Type 0}} | ||||
| <div class="comment" id="{{.HashTag}}"> | <div class="comment" id="{{.HashTag}}"> | ||||
| <a class="avatar" {{if gt .Poster.ID 0}}href="{{.Poster.HomeLink}}"{{end}}> | <a class="avatar" {{if gt .Poster.ID 0}}href="{{.Poster.HomeLink}}"{{end}}> | ||||
| @@ -189,5 +189,35 @@ | |||||
| </a> | </a> | ||||
| <span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a> {{$.i18n.Tr "repo.issues.cancel_tracking_history" $createdStr | Safe}}</span> | <span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a> {{$.i18n.Tr "repo.issues.cancel_tracking_history" $createdStr | Safe}}</span> | ||||
| </div> | </div> | ||||
| {{else if eq .Type 16}} | |||||
| <div class="event"> | |||||
| <span class="octicon octicon-primitive-dot"></span> | |||||
| <a class="ui avatar image" href="{{.Poster.HomeLink}}"> | |||||
| <img src="{{.Poster.RelAvatarLink}}"> | |||||
| </a> | |||||
| <span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a> | |||||
| {{$.i18n.Tr "repo.issues.due_date_added" .Content $createdStr | Safe}} | |||||
| </span> | |||||
| </div> | |||||
| {{else if eq .Type 17}} | |||||
| <div class="event"> | |||||
| <span class="octicon octicon-primitive-dot"></span> | |||||
| <a class="ui avatar image" href="{{.Poster.HomeLink}}"> | |||||
| <img src="{{.Poster.RelAvatarLink}}"> | |||||
| </a> | |||||
| <span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a> | |||||
| {{$.i18n.Tr "repo.issues.due_date_modified" (.Content | ParseDeadline) $createdStr | Safe}} | |||||
| </span> | |||||
| </div> | |||||
| {{else if eq .Type 18}} | |||||
| <div class="event"> | |||||
| <span class="octicon octicon-primitive-dot"></span> | |||||
| <a class="ui avatar image" href="{{.Poster.HomeLink}}"> | |||||
| <img src="{{.Poster.RelAvatarLink}}"> | |||||
| </a> | |||||
| <span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a> | |||||
| {{$.i18n.Tr "repo.issues.due_date_remove" .Content $createdStr | Safe}} | |||||
| </span> | |||||
| </div> | |||||
| {{end}} | {{end}} | ||||
| {{end}} | {{end}} | ||||
| @@ -191,5 +191,41 @@ | |||||
| </div> | </div> | ||||
| {{end}} | {{end}} | ||||
| {{end}} | {{end}} | ||||
| <div class="ui divider"></div> | |||||
| <span class="text"><strong>{{.i18n.Tr "repo.issues.due_date"}}</strong></span> | |||||
| {{if gt .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 gt .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 gt .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 gt .Issue.DeadlineUnix 0}} | |||||
| <i class="edit icon"></i> | |||||
| {{else}} | |||||
| <i class="plus icon"></i> | |||||
| {{end}} | |||||
| </button> | |||||
| </div> | |||||
| </form> | |||||
| {{end}} | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| @@ -35,7 +35,7 @@ func (c *Client) ListReleaseAttachments(user, repo string, release int64) ([]*At | |||||
| return attachments, err | return attachments, err | ||||
| } | } | ||||
| // ListReleaseAttachments list release's attachments | |||||
| // GetReleaseAttachment returns the requested attachment | |||||
| func (c *Client) GetReleaseAttachment(user, repo string, release int64, id int64) (*Attachment, error) { | func (c *Client) GetReleaseAttachment(user, repo string, release int64, id int64) (*Attachment, error) { | ||||
| a := new(Attachment) | a := new(Attachment) | ||||
| err := c.getParsedResponse("GET", | err := c.getParsedResponse("GET", | ||||
| @@ -39,6 +39,7 @@ type Issue struct { | |||||
| Labels []*Label `json:"labels"` | Labels []*Label `json:"labels"` | ||||
| Milestone *Milestone `json:"milestone"` | Milestone *Milestone `json:"milestone"` | ||||
| Assignee *User `json:"assignee"` | Assignee *User `json:"assignee"` | ||||
| Assignees []*User `json:"assignees"` | |||||
| // Whether the issue is open or closed | // Whether the issue is open or closed | ||||
| // | // | ||||
| // type: string | // type: string | ||||
| @@ -49,6 +50,10 @@ type Issue struct { | |||||
| Created time.Time `json:"created_at"` | Created time.Time `json:"created_at"` | ||||
| // swagger:strfmt date-time | // swagger:strfmt date-time | ||||
| Updated time.Time `json:"updated_at"` | Updated time.Time `json:"updated_at"` | ||||
| // swagger:strfmt date-time | |||||
| Closed *time.Time `json:"closed_at"` | |||||
| // swagger:strfmt date-time | |||||
| Deadline *time.Time `json:"due_date"` | |||||
| PullRequest *PullRequestMeta `json:"pull_request"` | PullRequest *PullRequestMeta `json:"pull_request"` | ||||
| } | } | ||||
| @@ -89,7 +94,10 @@ type CreateIssueOption struct { | |||||
| Title string `json:"title" binding:"Required"` | Title string `json:"title" binding:"Required"` | ||||
| Body string `json:"body"` | Body string `json:"body"` | ||||
| // username of assignee | // username of assignee | ||||
| Assignee string `json:"assignee"` | |||||
| Assignee string `json:"assignee"` | |||||
| Assignees []string `json:"assignees"` | |||||
| // swagger:strfmt date-time | |||||
| Deadline *time.Time `json:"due_date"` | |||||
| // milestone id | // milestone id | ||||
| Milestone int64 `json:"milestone"` | Milestone int64 `json:"milestone"` | ||||
| // list of label ids | // list of label ids | ||||
| @@ -110,11 +118,14 @@ func (c *Client) CreateIssue(owner, repo string, opt CreateIssueOption) (*Issue, | |||||
| // EditIssueOption options for editing an issue | // EditIssueOption options for editing an issue | ||||
| type EditIssueOption struct { | type EditIssueOption struct { | ||||
| Title string `json:"title"` | |||||
| Body *string `json:"body"` | |||||
| Assignee *string `json:"assignee"` | |||||
| Milestone *int64 `json:"milestone"` | |||||
| State *string `json:"state"` | |||||
| Title string `json:"title"` | |||||
| Body *string `json:"body"` | |||||
| Assignee *string `json:"assignee"` | |||||
| Assignees []string `json:"assignees"` | |||||
| Milestone *int64 `json:"milestone"` | |||||
| State *string `json:"state"` | |||||
| // swagger:strfmt date-time | |||||
| Deadline *time.Time `json:"due_date"` | |||||
| } | } | ||||
| // EditIssue modify an existing issue for a given repository | // EditIssue modify an existing issue for a given repository | ||||
| @@ -22,6 +22,7 @@ type PullRequest struct { | |||||
| Labels []*Label `json:"labels"` | Labels []*Label `json:"labels"` | ||||
| Milestone *Milestone `json:"milestone"` | Milestone *Milestone `json:"milestone"` | ||||
| Assignee *User `json:"assignee"` | Assignee *User `json:"assignee"` | ||||
| Assignees []*User `json:"assignees"` | |||||
| State StateType `json:"state"` | State StateType `json:"state"` | ||||
| Comments int `json:"comments"` | Comments int `json:"comments"` | ||||
| @@ -40,10 +41,15 @@ type PullRequest struct { | |||||
| Head *PRBranchInfo `json:"head"` | Head *PRBranchInfo `json:"head"` | ||||
| MergeBase string `json:"merge_base"` | MergeBase string `json:"merge_base"` | ||||
| // swagger:strfmt date-time | |||||
| Deadline *time.Time `json:"due_date"` | |||||
| // swagger:strfmt date-time | // swagger:strfmt date-time | ||||
| Created *time.Time `json:"created_at"` | Created *time.Time `json:"created_at"` | ||||
| // swagger:strfmt date-time | // swagger:strfmt date-time | ||||
| Updated *time.Time `json:"updated_at"` | Updated *time.Time `json:"updated_at"` | ||||
| // swagger:strfmt date-time | |||||
| Closed *time.Time `json:"closed_at"` | |||||
| } | } | ||||
| // PRBranchInfo information about a branch | // PRBranchInfo information about a branch | ||||
| @@ -79,13 +85,16 @@ func (c *Client) GetPullRequest(owner, repo string, index int64) (*PullRequest, | |||||
| // CreatePullRequestOption options when creating a pull request | // CreatePullRequestOption options when creating a pull request | ||||
| type CreatePullRequestOption struct { | type CreatePullRequestOption struct { | ||||
| Head string `json:"head" binding:"Required"` | |||||
| Base string `json:"base" binding:"Required"` | |||||
| Title string `json:"title" binding:"Required"` | |||||
| Body string `json:"body"` | |||||
| Assignee string `json:"assignee"` | |||||
| Milestone int64 `json:"milestone"` | |||||
| Labels []int64 `json:"labels"` | |||||
| Head string `json:"head" binding:"Required"` | |||||
| Base string `json:"base" binding:"Required"` | |||||
| Title string `json:"title" binding:"Required"` | |||||
| Body string `json:"body"` | |||||
| Assignee string `json:"assignee"` | |||||
| Assignees []string `json:"assignees"` | |||||
| Milestone int64 `json:"milestone"` | |||||
| Labels []int64 `json:"labels"` | |||||
| // swagger:strfmt date-time | |||||
| Deadline *time.Time `json:"due_date"` | |||||
| } | } | ||||
| // CreatePullRequest create pull request with options | // CreatePullRequest create pull request with options | ||||
| @@ -101,12 +110,15 @@ func (c *Client) CreatePullRequest(owner, repo string, opt CreatePullRequestOpti | |||||
| // EditPullRequestOption options when modify pull request | // EditPullRequestOption options when modify pull request | ||||
| type EditPullRequestOption struct { | type EditPullRequestOption struct { | ||||
| Title string `json:"title"` | |||||
| Body string `json:"body"` | |||||
| Assignee string `json:"assignee"` | |||||
| Milestone int64 `json:"milestone"` | |||||
| Labels []int64 `json:"labels"` | |||||
| State *string `json:"state"` | |||||
| Title string `json:"title"` | |||||
| Body string `json:"body"` | |||||
| Assignee string `json:"assignee"` | |||||
| Assignees []string `json:"assignees"` | |||||
| Milestone int64 `json:"milestone"` | |||||
| Labels []int64 `json:"labels"` | |||||
| State *string `json:"state"` | |||||
| // swagger:strfmt date-time | |||||
| Deadline *time.Time `json:"due_date"` | |||||
| } | } | ||||
| // EditPullRequest modify pull request with PR id and options | // EditPullRequest modify pull request with PR id and options | ||||
| @@ -9,10 +9,10 @@ | |||||
| "revisionTime": "2018-04-21T01:08:19Z" | "revisionTime": "2018-04-21T01:08:19Z" | ||||
| }, | }, | ||||
| { | { | ||||
| "checksumSHA1": "PWaIU7g1YSkETxka2DIS1EYsPK0=", | |||||
| "checksumSHA1": "xXzi8Xx7HA3M0z3lR/1wr1Vz1fc=", | |||||
| "path": "code.gitea.io/sdk/gitea", | "path": "code.gitea.io/sdk/gitea", | ||||
| "revision": "cdbef997666132599cc92dc22aa94de3db04adeb", | |||||
| "revisionTime": "2018-03-02T14:48:43Z" | |||||
| "revision": "142acef5ce79f78585afcce31748af46c72a3dea", | |||||
| "revisionTime": "2018-04-17T00:54:29Z" | |||||
| }, | }, | ||||
| { | { | ||||
| "checksumSHA1": "bOODD4Gbw3GfcuQPU2dI40crxxk=", | "checksumSHA1": "bOODD4Gbw3GfcuQPU2dI40crxxk=", | ||||