* Make API EditIssue and EditPullRequest issue notifications Restructure models.UpdateIssueByAPI and EditIssue/EditPullRequest to issue notifications Fix #10014 Signed-off-by: Andrew Thornton <art27@cantab.net> * As per @6543 Signed-off-by: Andrew Thornton <art27@cantab.net> * update status! Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: techknowlogick <techknowlogick@gitea.io> Co-authored-by: John Olheiser <john.olheiser@gmail.com> Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com> Co-authored-by: Lauris BH <lauris@nix.lv>tags/v1.21.12.1
| @@ -580,8 +580,13 @@ func (issue *Issue) changeStatus(e *xorm.Session, doer *User, isClosed, isMergeP | |||||
| } | } | ||||
| } | } | ||||
| issue.IsClosed = isClosed | |||||
| return issue.doChangeStatus(e, doer, isMergePull) | |||||
| } | |||||
| func (issue *Issue) doChangeStatus(e *xorm.Session, doer *User, isMergePull bool) (*Comment, error) { | |||||
| // Check for open dependencies | // Check for open dependencies | ||||
| if isClosed && issue.Repo.isDependenciesEnabled(e) { | |||||
| if issue.IsClosed && issue.Repo.isDependenciesEnabled(e) { | |||||
| // only check if dependencies are enabled and we're about to close an issue, otherwise reopening an issue would fail when there are unsatisfied dependencies | // only check if dependencies are enabled and we're about to close an issue, otherwise reopening an issue would fail when there are unsatisfied dependencies | ||||
| noDeps, err := issueNoDependenciesLeft(e, issue) | noDeps, err := issueNoDependenciesLeft(e, issue) | ||||
| if err != nil { | if err != nil { | ||||
| @@ -593,23 +598,22 @@ func (issue *Issue) changeStatus(e *xorm.Session, doer *User, isClosed, isMergeP | |||||
| } | } | ||||
| } | } | ||||
| issue.IsClosed = isClosed | |||||
| if isClosed { | |||||
| if issue.IsClosed { | |||||
| issue.ClosedUnix = timeutil.TimeStampNow() | issue.ClosedUnix = timeutil.TimeStampNow() | ||||
| } else { | } else { | ||||
| issue.ClosedUnix = 0 | issue.ClosedUnix = 0 | ||||
| } | } | ||||
| if err = updateIssueCols(e, issue, "is_closed", "closed_unix"); err != nil { | |||||
| if err := updateIssueCols(e, issue, "is_closed", "closed_unix"); err != nil { | |||||
| return nil, err | return nil, err | ||||
| } | } | ||||
| // Update issue count of labels | // Update issue count of labels | ||||
| if err = issue.getLabels(e); err != nil { | |||||
| if err := issue.getLabels(e); err != nil { | |||||
| return nil, err | return nil, err | ||||
| } | } | ||||
| for idx := range issue.Labels { | for idx := range issue.Labels { | ||||
| if err = updateLabelCols(e, issue.Labels[idx], "num_issues", "num_closed_issue"); err != nil { | |||||
| if err := updateLabelCols(e, issue.Labels[idx], "num_issues", "num_closed_issue"); err != nil { | |||||
| return nil, err | return nil, err | ||||
| } | } | ||||
| } | } | ||||
| @@ -1607,28 +1611,59 @@ func SearchIssueIDsByKeyword(kw string, repoIDs []int64, limit, start int) (int6 | |||||
| } | } | ||||
| // UpdateIssueByAPI updates all allowed fields of given issue. | // UpdateIssueByAPI updates all allowed fields of given issue. | ||||
| func UpdateIssueByAPI(issue *Issue) error { | |||||
| // If the issue status is changed a statusChangeComment is returned | |||||
| // similarly if the title is changed the titleChanged bool is set to true | |||||
| func UpdateIssueByAPI(issue *Issue, doer *User) (statusChangeComment *Comment, titleChanged bool, err error) { | |||||
| sess := x.NewSession() | sess := x.NewSession() | ||||
| defer sess.Close() | defer sess.Close() | ||||
| if err := sess.Begin(); err != nil { | if err := sess.Begin(); err != nil { | ||||
| return err | |||||
| return nil, false, err | |||||
| } | |||||
| if err := issue.loadRepo(sess); err != nil { | |||||
| return nil, false, fmt.Errorf("loadRepo: %v", err) | |||||
| } | |||||
| // Reload the issue | |||||
| currentIssue, err := getIssueByID(sess, issue.ID) | |||||
| if err != nil { | |||||
| return nil, false, err | |||||
| } | } | ||||
| if _, err := sess.ID(issue.ID).Cols( | if _, err := sess.ID(issue.ID).Cols( | ||||
| "name", "is_closed", "content", "milestone_id", "priority", | |||||
| "deadline_unix", "updated_unix", "closed_unix", "is_locked"). | |||||
| "name", "content", "milestone_id", "priority", | |||||
| "deadline_unix", "updated_unix", "is_locked"). | |||||
| Update(issue); err != nil { | Update(issue); err != nil { | ||||
| return err | |||||
| return nil, false, err | |||||
| } | } | ||||
| if err := issue.loadPoster(sess); err != nil { | |||||
| return err | |||||
| titleChanged = currentIssue.Title != issue.Title | |||||
| if titleChanged { | |||||
| var opts = &CreateCommentOptions{ | |||||
| Type: CommentTypeChangeTitle, | |||||
| Doer: doer, | |||||
| Repo: issue.Repo, | |||||
| Issue: issue, | |||||
| OldTitle: currentIssue.Title, | |||||
| NewTitle: issue.Title, | |||||
| } | |||||
| _, err := createComment(sess, opts) | |||||
| if err != nil { | |||||
| return nil, false, fmt.Errorf("createComment: %v", err) | |||||
| } | |||||
| } | } | ||||
| if err := issue.addCrossReferences(sess, issue.Poster, true); err != nil { | |||||
| return err | |||||
| if currentIssue.IsClosed != issue.IsClosed { | |||||
| statusChangeComment, err = issue.doChangeStatus(sess, doer, false) | |||||
| if err != nil { | |||||
| return nil, false, err | |||||
| } | |||||
| } | } | ||||
| return sess.Commit() | |||||
| if err := issue.addCrossReferences(sess, doer, true); err != nil { | |||||
| return nil, false, err | |||||
| } | |||||
| return statusChangeComment, titleChanged, sess.Commit() | |||||
| } | } | ||||
| // UpdateIssueDeadline updates an issue deadline and adds comments. Setting a deadline to 0 means deleting it. | // UpdateIssueDeadline updates an issue deadline and adds comments. Setting a deadline to 0 means deleting it. | ||||
| @@ -17,6 +17,7 @@ import ( | |||||
| "code.gitea.io/gitea/modules/convert" | "code.gitea.io/gitea/modules/convert" | ||||
| issue_indexer "code.gitea.io/gitea/modules/indexer/issues" | issue_indexer "code.gitea.io/gitea/modules/indexer/issues" | ||||
| "code.gitea.io/gitea/modules/log" | "code.gitea.io/gitea/modules/log" | ||||
| "code.gitea.io/gitea/modules/notification" | |||||
| "code.gitea.io/gitea/modules/setting" | "code.gitea.io/gitea/modules/setting" | ||||
| api "code.gitea.io/gitea/modules/structs" | api "code.gitea.io/gitea/modules/structs" | ||||
| "code.gitea.io/gitea/modules/timeutil" | "code.gitea.io/gitea/modules/timeutil" | ||||
| @@ -544,6 +545,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) { | |||||
| return | return | ||||
| } | } | ||||
| oldTitle := issue.Title | |||||
| if len(form.Title) > 0 { | if len(form.Title) > 0 { | ||||
| issue.Title = form.Title | issue.Title = form.Title | ||||
| } | } | ||||
| @@ -598,20 +600,25 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) { | |||||
| return | return | ||||
| } | } | ||||
| } | } | ||||
| if err = models.UpdateIssueByAPI(issue); err != nil { | |||||
| ctx.Error(http.StatusInternalServerError, "UpdateIssueByAPI", err) | |||||
| return | |||||
| } | |||||
| if form.State != nil { | if form.State != nil { | ||||
| if err = issue_service.ChangeStatus(issue, ctx.User, api.StateClosed == api.StateType(*form.State)); err != nil { | |||||
| if models.IsErrDependenciesLeft(err) { | |||||
| ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies") | |||||
| return | |||||
| } | |||||
| ctx.Error(http.StatusInternalServerError, "ChangeStatus", err) | |||||
| issue.IsClosed = (api.StateClosed == api.StateType(*form.State)) | |||||
| } | |||||
| statusChangeComment, titleChanged, err := models.UpdateIssueByAPI(issue, ctx.User) | |||||
| if err != nil { | |||||
| if models.IsErrDependenciesLeft(err) { | |||||
| ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies") | |||||
| return | return | ||||
| } | } | ||||
| ctx.Error(http.StatusInternalServerError, "UpdateIssueByAPI", err) | |||||
| return | |||||
| } | |||||
| if titleChanged { | |||||
| notification.NotifyIssueChangeTitle(ctx.User, issue, oldTitle) | |||||
| } | |||||
| if statusChangeComment != nil { | |||||
| notification.NotifyIssueChangeStatus(ctx.User, issue, statusChangeComment, issue.IsClosed) | |||||
| } | } | ||||
| // Refetch from database to assign some automatic values | // Refetch from database to assign some automatic values | ||||
| @@ -16,6 +16,7 @@ import ( | |||||
| "code.gitea.io/gitea/modules/convert" | "code.gitea.io/gitea/modules/convert" | ||||
| "code.gitea.io/gitea/modules/git" | "code.gitea.io/gitea/modules/git" | ||||
| "code.gitea.io/gitea/modules/log" | "code.gitea.io/gitea/modules/log" | ||||
| "code.gitea.io/gitea/modules/notification" | |||||
| api "code.gitea.io/gitea/modules/structs" | api "code.gitea.io/gitea/modules/structs" | ||||
| "code.gitea.io/gitea/modules/timeutil" | "code.gitea.io/gitea/modules/timeutil" | ||||
| "code.gitea.io/gitea/routers/api/v1/utils" | "code.gitea.io/gitea/routers/api/v1/utils" | ||||
| @@ -409,6 +410,7 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) { | |||||
| return | return | ||||
| } | } | ||||
| oldTitle := issue.Title | |||||
| if len(form.Title) > 0 { | if len(form.Title) > 0 { | ||||
| issue.Title = form.Title | issue.Title = form.Title | ||||
| } | } | ||||
| @@ -485,19 +487,25 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) { | |||||
| } | } | ||||
| } | } | ||||
| if err = models.UpdateIssueByAPI(issue); err != nil { | |||||
| ctx.Error(http.StatusInternalServerError, "UpdateIssueByAPI", err) | |||||
| return | |||||
| } | |||||
| if form.State != nil { | if form.State != nil { | ||||
| if err = issue_service.ChangeStatus(issue, ctx.User, api.StateClosed == api.StateType(*form.State)); err != nil { | |||||
| if models.IsErrDependenciesLeft(err) { | |||||
| ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies") | |||||
| return | |||||
| } | |||||
| ctx.Error(http.StatusInternalServerError, "ChangeStatus", err) | |||||
| issue.IsClosed = (api.StateClosed == api.StateType(*form.State)) | |||||
| } | |||||
| statusChangeComment, titleChanged, err := models.UpdateIssueByAPI(issue, ctx.User) | |||||
| if err != nil { | |||||
| if models.IsErrDependenciesLeft(err) { | |||||
| ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies") | |||||
| return | return | ||||
| } | } | ||||
| ctx.Error(http.StatusInternalServerError, "UpdateIssueByAPI", err) | |||||
| return | |||||
| } | |||||
| if titleChanged { | |||||
| notification.NotifyIssueChangeTitle(ctx.User, issue, oldTitle) | |||||
| } | |||||
| if statusChangeComment != nil { | |||||
| notification.NotifyIssueChangeStatus(ctx.User, issue, statusChangeComment, issue.IsClosed) | |||||
| } | } | ||||
| // Refetch from database | // Refetch from database | ||||