From 74774a41c322db8c78cc0dc9ce92402e673bda13 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Thu, 4 May 2017 17:58:40 +0200 Subject: [PATCH 01/88] Added comment's hashtag to url for mail notifications. Signed-off-by: Jonas --- models/issue_comment.go | 2 +- models/issue_mail.go | 84 ++++++++++++++++++++++++++++++----------- models/mail.go | 40 ++++++++++++++++++-- 3 files changed, 100 insertions(+), 26 deletions(-) diff --git a/models/issue_comment.go b/models/issue_comment.go index e133cc049bdcb..430bbdfa26508 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -289,7 +289,7 @@ func (c *Comment) MailParticipants(e Engine, opType ActionType, issue *Issue) (e case ActionReopenIssue: issue.Content = fmt.Sprintf("Reopened #%d", issue.Index) } - if err = mailIssueCommentToParticipants(issue, c.Poster, mentions); err != nil { + if err = mailIssueCommentToParticipants(issue, c.Poster, c, mentions); err != nil { log.Error(4, "mailIssueCommentToParticipants: %v", err) } diff --git a/models/issue_mail.go b/models/issue_mail.go index 4b6542ddb4c15..1ff407e0f2933 100644 --- a/models/issue_mail.go +++ b/models/issue_mail.go @@ -18,22 +18,73 @@ func (issue *Issue) mailSubject() string { return fmt.Sprintf("[%s] %s (#%d)", issue.Repo.Name, issue.Title, issue.Index) } -// mailIssueCommentToParticipants can be used for both new issue creation and comment. + +// mailIssueCommentToParticipants can be used for only for comment. // This function sends two list of emails: // 1. Repository watchers and users who are participated in comments. // 2. Users who are not in 1. but get mentioned in current issue/comment. -func mailIssueCommentToParticipants(issue *Issue, doer *User, mentions []string) error { +func mailIssueCommentToParticipants(issue *Issue, doer *User, comment *Comment, mentions []string) error { + + names, tos, err := prepareMailToParticipants(issue, doer) + + if(err != nil) { + return fmt.Errorf("PrepareMailToParticipants: %v", err) + } + + SendIssueCommentMail(issue, doer, comment, tos) + + // Mail mentioned people and exclude watchers. + names = append(names, doer.Name) + tos = make([]string, 0, len(mentions)) // list of user names. + for i := range mentions { + if com.IsSliceContainsStr(names, mentions[i]) { + continue + } + + tos = append(tos, mentions[i]) + } + SendIssueMentionMail(issue, doer, comment, GetUserEmailsByNames(tos)) + + return nil +} + +func mailIssueActionToParticipants(issue *Issue, doer *User, mentions []string) error { + names, tos, err := prepareMailToParticipants(issue, doer) + + if(err != nil) { + return fmt.Errorf("PrepareMailToParticipants: %v", err) + } + + SendIssueActionMail(issue, doer, tos) + + // Mail mentioned people and exclude watchers. + names = append(names, doer.Name) + tos = make([]string, 0, len(mentions)) // list of user names. + for i := range mentions { + if com.IsSliceContainsStr(names, mentions[i]) { + continue + } + + tos = append(tos, mentions[i]) + } + SendIssueMentionInActionMail(issue, doer, GetUserEmailsByNames(tos)) + + return nil +} + +// prepareMailToParticipants creates the tos and names list for an issue and the issue's creator. +func prepareMailToParticipants(issue *Issue, doer *User) ([]string, []string, error) { if !setting.Service.EnableNotifyMail { - return nil + return nil, nil, nil } watchers, err := GetWatchers(issue.RepoID) if err != nil { - return fmt.Errorf("GetWatchers [repo_id: %d]: %v", issue.RepoID, err) + return nil, nil, err } participants, err := GetParticipantsByIssueID(issue.ID) if err != nil { - return fmt.Errorf("GetParticipantsByIssueID [issue_id: %d]: %v", issue.ID, err) + return nil, nil, err } // In case the issue poster is not watching the repository, @@ -51,7 +102,7 @@ func mailIssueCommentToParticipants(issue *Issue, doer *User, mentions []string) to, err := GetUserByID(watchers[i].UserID) if err != nil { - return fmt.Errorf("GetUserByID [%d]: %v", watchers[i].UserID, err) + return nil, nil, err } if to.IsOrganization() { continue @@ -70,23 +121,10 @@ func mailIssueCommentToParticipants(issue *Issue, doer *User, mentions []string) tos = append(tos, participants[i].Email) names = append(names, participants[i].Name) } - SendIssueCommentMail(issue, doer, tos) - - // Mail mentioned people and exclude watchers. - names = append(names, doer.Name) - tos = make([]string, 0, len(mentions)) // list of user names. - for i := range mentions { - if com.IsSliceContainsStr(names, mentions[i]) { - continue - } - - tos = append(tos, mentions[i]) - } - SendIssueMentionMail(issue, doer, GetUserEmailsByNames(tos)) - - return nil + return tos, names, nil } + // MailParticipants sends new issue thread created emails to repository watchers // and mentioned people. func (issue *Issue) MailParticipants() (err error) { @@ -95,9 +133,11 @@ func (issue *Issue) MailParticipants() (err error) { return fmt.Errorf("UpdateIssueMentions [%d]: %v", issue.ID, err) } - if err = mailIssueCommentToParticipants(issue, issue.Poster, mentions); err != nil { + if err = mailIssueActionToParticipants(issue, issue.Poster, mentions); err != nil { log.Error(4, "mailIssueCommentToParticipants: %v", err) } return nil } + + diff --git a/models/mail.go b/models/mail.go index 16e8c9e2efbb1..165db65698cd5 100644 --- a/models/mail.go +++ b/models/mail.go @@ -148,6 +148,24 @@ func composeTplData(subject, body, link string) map[string]interface{} { return data } +func composeIssueCommentMessage(issue *Issue, doer *User, comment *Comment, tplName base.TplName, tos []string, info string) *mailer.Message { + subject := issue.mailSubject() + body := string(markdown.RenderString(issue.Content, issue.Repo.HTMLURL(), issue.Repo.ComposeMetas())) + data := composeTplData(subject, body, issue.HTMLURL() + comment.HashTag()) + + data["Doer"] = doer + + var content bytes.Buffer + + if err := templates.ExecuteTemplate(&content, string(tplName), data); err != nil { + log.Error(3, "Template: %v", err) + } + + msg := mailer.NewMessageFrom(tos, fmt.Sprintf(`"%s" <%s>`, doer.DisplayName(), setting.MailService.FromEmail), subject, content.String()) + msg.Info = fmt.Sprintf("Subject: %s, %s", subject, info) + return msg +} + func composeIssueMessage(issue *Issue, doer *User, tplName base.TplName, tos []string, info string) *mailer.Message { subject := issue.mailSubject() body := string(markdown.RenderString(issue.Content, issue.Repo.HTMLURL(), issue.Repo.ComposeMetas())) @@ -166,16 +184,32 @@ func composeIssueMessage(issue *Issue, doer *User, tplName base.TplName, tos []s } // SendIssueCommentMail composes and sends issue comment emails to target receivers. -func SendIssueCommentMail(issue *Issue, doer *User, tos []string) { +func SendIssueCommentMail(issue *Issue, doer *User, comment *Comment, tos []string) { if len(tos) == 0 { return } - mailer.SendAsync(composeIssueMessage(issue, doer, mailIssueComment, tos, "issue comment")) + mailer.SendAsync(composeIssueCommentMessage(issue, doer, comment, mailIssueComment, tos, "issue comment")) } // SendIssueMentionMail composes and sends issue mention emails to target receivers. -func SendIssueMentionMail(issue *Issue, doer *User, tos []string) { +func SendIssueMentionMail(issue *Issue, doer *User, comment *Comment, tos []string) { + if len(tos) == 0 { + return + } + mailer.SendAsync(composeIssueCommentMessage(issue, doer, comment, mailIssueMention, tos, "issue mention")) +} +// SendIssueActionMail composes and sends issue action emails to target receivers. +func SendIssueActionMail(issue *Issue, doer *User, tos []string) { + if len(tos) == 0 { + return + } + + mailer.SendAsync(composeIssueMessage(issue, doer, mailIssueComment, tos, "issue comment")) +} + +// SendIssueMentionInActionMail composes and sends issue mention emails to target receivers. Only applies if no comment is given. +func SendIssueMentionInActionMail(issue *Issue, doer *User, tos []string) { if len(tos) == 0 { return } From f385727aef6f0a777d7fedba56d701ff8b2a0b9e Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Thu, 4 May 2017 18:00:40 +0200 Subject: [PATCH 02/88] Added comment's hashtag to url for mail notifications. Added explanation to return statement. Signed-off-by: Jonas --- models/issue_mail.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/issue_mail.go b/models/issue_mail.go index 1ff407e0f2933..1fabde489a653 100644 --- a/models/issue_mail.go +++ b/models/issue_mail.go @@ -73,7 +73,7 @@ func mailIssueActionToParticipants(issue *Issue, doer *User, mentions []string) } // prepareMailToParticipants creates the tos and names list for an issue and the issue's creator. -func prepareMailToParticipants(issue *Issue, doer *User) ([]string, []string, error) { +func prepareMailToParticipants(issue *Issue, doer *User) (tos []string, names []string, error error) { if !setting.Service.EnableNotifyMail { return nil, nil, nil } From e05eb960a89631c6986c3f4a5096ff6a35b45c38 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Thu, 4 May 2017 18:13:31 +0200 Subject: [PATCH 03/88] Added comment's hashtag to url for mail notifications. Added explanation to return statement + documentation. Signed-off-by: Jonas --- models/issue_mail.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/models/issue_mail.go b/models/issue_mail.go index 1fabde489a653..bb41f228834ee 100644 --- a/models/issue_mail.go +++ b/models/issue_mail.go @@ -19,7 +19,7 @@ func (issue *Issue) mailSubject() string { } -// mailIssueCommentToParticipants can be used for only for comment. +// mailIssueCommentToParticipants can be used only for comment. // This function sends two list of emails: // 1. Repository watchers and users who are participated in comments. // 2. Users who are not in 1. but get mentioned in current issue/comment. @@ -48,6 +48,10 @@ func mailIssueCommentToParticipants(issue *Issue, doer *User, comment *Comment, return nil } +// mailIssueActionToParticipants can be used for creation or pull requests. +// This function sends two list of emails: +// 1. Repository watchers and users who are participated in comments. +// 2. Users who are not in 1. but get mentioned in current issue/comment. func mailIssueActionToParticipants(issue *Issue, doer *User, mentions []string) error { names, tos, err := prepareMailToParticipants(issue, doer) @@ -93,8 +97,8 @@ func prepareMailToParticipants(issue *Issue, doer *User) (tos []string, names [] participants = append(participants, issue.Poster) } - tos := make([]string, 0, len(watchers)) // List of email addresses. - names := make([]string, 0, len(watchers)) + tos = make([]string, 0, len(watchers)) // List of email addresses. + names = make([]string, 0, len(watchers)) for i := range watchers { if watchers[i].UserID == doer.ID { continue From 5cb36ca7bd650fda84a361febf3c26c77ef7f753 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Thu, 4 May 2017 18:30:37 +0200 Subject: [PATCH 04/88] Added comment's hashtag to url for mail notifications. Signed-off-by: Jonas Franz --- models/mail.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/mail.go b/models/mail.go index 165db65698cd5..8ac941a26747c 100644 --- a/models/mail.go +++ b/models/mail.go @@ -151,7 +151,7 @@ func composeTplData(subject, body, link string) map[string]interface{} { func composeIssueCommentMessage(issue *Issue, doer *User, comment *Comment, tplName base.TplName, tos []string, info string) *mailer.Message { subject := issue.mailSubject() body := string(markdown.RenderString(issue.Content, issue.Repo.HTMLURL(), issue.Repo.ComposeMetas())) - data := composeTplData(subject, body, issue.HTMLURL() + comment.HashTag()) + data := composeTplData(subject, body, issue.HTMLURL() + "#" + comment.HashTag()) data["Doer"] = doer From 2dd674e51dc120c70ca04f3153d246af35d5080b Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Thu, 4 May 2017 21:29:55 +0200 Subject: [PATCH 05/88] Replacing in-line link generation with HTMLURL. (+gofmt) Signed-off-by: Jonas Franz --- models/issue_mail.go | 10 +++------- models/mail.go | 3 ++- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/models/issue_mail.go b/models/issue_mail.go index bb41f228834ee..fc58b6f613795 100644 --- a/models/issue_mail.go +++ b/models/issue_mail.go @@ -18,7 +18,6 @@ func (issue *Issue) mailSubject() string { return fmt.Sprintf("[%s] %s (#%d)", issue.Repo.Name, issue.Title, issue.Index) } - // mailIssueCommentToParticipants can be used only for comment. // This function sends two list of emails: // 1. Repository watchers and users who are participated in comments. @@ -27,7 +26,7 @@ func mailIssueCommentToParticipants(issue *Issue, doer *User, comment *Comment, names, tos, err := prepareMailToParticipants(issue, doer) - if(err != nil) { + if err != nil { return fmt.Errorf("PrepareMailToParticipants: %v", err) } @@ -55,7 +54,7 @@ func mailIssueCommentToParticipants(issue *Issue, doer *User, comment *Comment, func mailIssueActionToParticipants(issue *Issue, doer *User, mentions []string) error { names, tos, err := prepareMailToParticipants(issue, doer) - if(err != nil) { + if err != nil { return fmt.Errorf("PrepareMailToParticipants: %v", err) } @@ -77,7 +76,7 @@ func mailIssueActionToParticipants(issue *Issue, doer *User, mentions []string) } // prepareMailToParticipants creates the tos and names list for an issue and the issue's creator. -func prepareMailToParticipants(issue *Issue, doer *User) (tos []string, names []string, error error) { +func prepareMailToParticipants(issue *Issue, doer *User) (tos []string, names []string, error error) { if !setting.Service.EnableNotifyMail { return nil, nil, nil } @@ -128,7 +127,6 @@ func prepareMailToParticipants(issue *Issue, doer *User) (tos []string, names [] return tos, names, nil } - // MailParticipants sends new issue thread created emails to repository watchers // and mentioned people. func (issue *Issue) MailParticipants() (err error) { @@ -143,5 +141,3 @@ func (issue *Issue) MailParticipants() (err error) { return nil } - - diff --git a/models/mail.go b/models/mail.go index 8ac941a26747c..4b1b32b1e9e9e 100644 --- a/models/mail.go +++ b/models/mail.go @@ -151,7 +151,7 @@ func composeTplData(subject, body, link string) map[string]interface{} { func composeIssueCommentMessage(issue *Issue, doer *User, comment *Comment, tplName base.TplName, tos []string, info string) *mailer.Message { subject := issue.mailSubject() body := string(markdown.RenderString(issue.Content, issue.Repo.HTMLURL(), issue.Repo.ComposeMetas())) - data := composeTplData(subject, body, issue.HTMLURL() + "#" + comment.HashTag()) + data := composeTplData(subject, body, comment.HTMLURL()) data["Doer"] = doer @@ -199,6 +199,7 @@ func SendIssueMentionMail(issue *Issue, doer *User, comment *Comment, tos []stri } mailer.SendAsync(composeIssueCommentMessage(issue, doer, comment, mailIssueMention, tos, "issue mention")) } + // SendIssueActionMail composes and sends issue action emails to target receivers. func SendIssueActionMail(issue *Issue, doer *User, tos []string) { if len(tos) == 0 { From 716bf811b4249215a49ac151f66d626cc32c4e44 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Thu, 4 May 2017 22:28:01 +0200 Subject: [PATCH 06/88] Replaced action-based model with nil-based model. (+gofmt) Signed-off-by: Jonas Franz --- models/issue_comment.go | 2 +- models/issue_mail.go | 91 ++++++++++++++--------------------------- models/mail.go | 39 +++--------------- 3 files changed, 36 insertions(+), 96 deletions(-) diff --git a/models/issue_comment.go b/models/issue_comment.go index 430bbdfa26508..a65abfe0124cb 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -153,7 +153,7 @@ func (c *Comment) HTMLURL() string { log.Error(4, "GetIssueByID(%d): %v", c.IssueID, err) return "" } - return fmt.Sprintf("%s#issuecomment-%d", issue.HTMLURL(), c.ID) + return fmt.Sprintf("%s#%s", issue.HTMLURL(), c.HashTag()) } // IssueURL formats a URL-string to the issue diff --git a/models/issue_mail.go b/models/issue_mail.go index fc58b6f613795..528a629f3d064 100644 --- a/models/issue_mail.go +++ b/models/issue_mail.go @@ -23,71 +23,17 @@ func (issue *Issue) mailSubject() string { // 1. Repository watchers and users who are participated in comments. // 2. Users who are not in 1. but get mentioned in current issue/comment. func mailIssueCommentToParticipants(issue *Issue, doer *User, comment *Comment, mentions []string) error { - - names, tos, err := prepareMailToParticipants(issue, doer) - - if err != nil { - return fmt.Errorf("PrepareMailToParticipants: %v", err) - } - - SendIssueCommentMail(issue, doer, comment, tos) - - // Mail mentioned people and exclude watchers. - names = append(names, doer.Name) - tos = make([]string, 0, len(mentions)) // list of user names. - for i := range mentions { - if com.IsSliceContainsStr(names, mentions[i]) { - continue - } - - tos = append(tos, mentions[i]) - } - SendIssueMentionMail(issue, doer, comment, GetUserEmailsByNames(tos)) - - return nil -} - -// mailIssueActionToParticipants can be used for creation or pull requests. -// This function sends two list of emails: -// 1. Repository watchers and users who are participated in comments. -// 2. Users who are not in 1. but get mentioned in current issue/comment. -func mailIssueActionToParticipants(issue *Issue, doer *User, mentions []string) error { - names, tos, err := prepareMailToParticipants(issue, doer) - - if err != nil { - return fmt.Errorf("PrepareMailToParticipants: %v", err) - } - - SendIssueActionMail(issue, doer, tos) - - // Mail mentioned people and exclude watchers. - names = append(names, doer.Name) - tos = make([]string, 0, len(mentions)) // list of user names. - for i := range mentions { - if com.IsSliceContainsStr(names, mentions[i]) { - continue - } - - tos = append(tos, mentions[i]) - } - SendIssueMentionInActionMail(issue, doer, GetUserEmailsByNames(tos)) - - return nil -} - -// prepareMailToParticipants creates the tos and names list for an issue and the issue's creator. -func prepareMailToParticipants(issue *Issue, doer *User) (tos []string, names []string, error error) { if !setting.Service.EnableNotifyMail { - return nil, nil, nil + return nil } watchers, err := GetWatchers(issue.RepoID) if err != nil { - return nil, nil, err + return fmt.Errorf("GetWatchers [repo_id: %d]: %v", issue.RepoID, err) } participants, err := GetParticipantsByIssueID(issue.ID) if err != nil { - return nil, nil, err + return fmt.Errorf("GetParticipantsByIssueID [issue_id: %d]: %v", issue.ID, err) } // In case the issue poster is not watching the repository, @@ -96,8 +42,8 @@ func prepareMailToParticipants(issue *Issue, doer *User) (tos []string, names [] participants = append(participants, issue.Poster) } - tos = make([]string, 0, len(watchers)) // List of email addresses. - names = make([]string, 0, len(watchers)) + tos := make([]string, 0, len(watchers)) // List of email addresses. + names := make([]string, 0, len(watchers)) for i := range watchers { if watchers[i].UserID == doer.ID { continue @@ -105,7 +51,7 @@ func prepareMailToParticipants(issue *Issue, doer *User) (tos []string, names [] to, err := GetUserByID(watchers[i].UserID) if err != nil { - return nil, nil, err + return fmt.Errorf("GetUserByID [%d]: %v", watchers[i].UserID, err) } if to.IsOrganization() { continue @@ -124,7 +70,30 @@ func prepareMailToParticipants(issue *Issue, doer *User) (tos []string, names [] tos = append(tos, participants[i].Email) names = append(names, participants[i].Name) } - return tos, names, nil + + SendIssueCommentMail(issue, doer, comment, tos) + + // Mail mentioned people and exclude watchers. + names = append(names, doer.Name) + tos = make([]string, 0, len(mentions)) // list of user names. + for i := range mentions { + if com.IsSliceContainsStr(names, mentions[i]) { + continue + } + + tos = append(tos, mentions[i]) + } + SendIssueMentionMail(issue, doer, comment, GetUserEmailsByNames(tos)) + + return nil +} + +// mailIssueActionToParticipants can be used for creation or pull requests. +// This function sends two list of emails: +// 1. Repository watchers and users who are participated in comments. +// 2. Users who are not in 1. but get mentioned in current issue/comment. +func mailIssueActionToParticipants(issue *Issue, doer *User, mentions []string) error { + return mailIssueCommentToParticipants(issue, doer, nil, mentions) } // MailParticipants sends new issue thread created emails to repository watchers diff --git a/models/mail.go b/models/mail.go index 4b1b32b1e9e9e..43536380d8e12 100644 --- a/models/mail.go +++ b/models/mail.go @@ -151,25 +151,13 @@ func composeTplData(subject, body, link string) map[string]interface{} { func composeIssueCommentMessage(issue *Issue, doer *User, comment *Comment, tplName base.TplName, tos []string, info string) *mailer.Message { subject := issue.mailSubject() body := string(markdown.RenderString(issue.Content, issue.Repo.HTMLURL(), issue.Repo.ComposeMetas())) - data := composeTplData(subject, body, comment.HTMLURL()) - data["Doer"] = doer - - var content bytes.Buffer - - if err := templates.ExecuteTemplate(&content, string(tplName), data); err != nil { - log.Error(3, "Template: %v", err) + data := make(map[string]interface{}, 10) + if comment != nil { + data = composeTplData(subject, body, issue.HTMLURL()+"#"+comment.HashTag()) + } else { + data = composeTplData(subject, body, issue.HTMLURL()) } - - msg := mailer.NewMessageFrom(tos, fmt.Sprintf(`"%s" <%s>`, doer.DisplayName(), setting.MailService.FromEmail), subject, content.String()) - msg.Info = fmt.Sprintf("Subject: %s, %s", subject, info) - return msg -} - -func composeIssueMessage(issue *Issue, doer *User, tplName base.TplName, tos []string, info string) *mailer.Message { - subject := issue.mailSubject() - body := string(markdown.RenderString(issue.Content, issue.Repo.HTMLURL(), issue.Repo.ComposeMetas())) - data := composeTplData(subject, body, issue.HTMLURL()) data["Doer"] = doer var content bytes.Buffer @@ -199,20 +187,3 @@ func SendIssueMentionMail(issue *Issue, doer *User, comment *Comment, tos []stri } mailer.SendAsync(composeIssueCommentMessage(issue, doer, comment, mailIssueMention, tos, "issue mention")) } - -// SendIssueActionMail composes and sends issue action emails to target receivers. -func SendIssueActionMail(issue *Issue, doer *User, tos []string) { - if len(tos) == 0 { - return - } - - mailer.SendAsync(composeIssueMessage(issue, doer, mailIssueComment, tos, "issue comment")) -} - -// SendIssueMentionInActionMail composes and sends issue mention emails to target receivers. Only applies if no comment is given. -func SendIssueMentionInActionMail(issue *Issue, doer *User, tos []string) { - if len(tos) == 0 { - return - } - mailer.SendAsync(composeIssueMessage(issue, doer, mailIssueMention, tos, "issue mention")) -} From edaa5761a6811eedde033d97f62c06aa96d591b1 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Thu, 4 May 2017 22:35:45 +0200 Subject: [PATCH 07/88] Replaced mailIssueActionToParticipants with mailIssueCommentToParticipants. Signed-off-by: Jonas Franz --- models/issue_mail.go | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/models/issue_mail.go b/models/issue_mail.go index 528a629f3d064..a9c5570c298cd 100644 --- a/models/issue_mail.go +++ b/models/issue_mail.go @@ -88,14 +88,6 @@ func mailIssueCommentToParticipants(issue *Issue, doer *User, comment *Comment, return nil } -// mailIssueActionToParticipants can be used for creation or pull requests. -// This function sends two list of emails: -// 1. Repository watchers and users who are participated in comments. -// 2. Users who are not in 1. but get mentioned in current issue/comment. -func mailIssueActionToParticipants(issue *Issue, doer *User, mentions []string) error { - return mailIssueCommentToParticipants(issue, doer, nil, mentions) -} - // MailParticipants sends new issue thread created emails to repository watchers // and mentioned people. func (issue *Issue) MailParticipants() (err error) { @@ -104,7 +96,7 @@ func (issue *Issue) MailParticipants() (err error) { return fmt.Errorf("UpdateIssueMentions [%d]: %v", issue.ID, err) } - if err = mailIssueActionToParticipants(issue, issue.Poster, mentions); err != nil { + if err = mailIssueCommentToParticipants(issue, issue.Poster, nil, mentions); err != nil { log.Error(4, "mailIssueCommentToParticipants: %v", err) } From b0e22aba71e4988c42d6598c5da18d3110e64cd3 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Fri, 5 May 2017 00:06:21 +0200 Subject: [PATCH 08/88] Updating comment for mailIssueCommentToParticipants Signed-off-by: Jonas Franz --- models/issue_mail.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/issue_mail.go b/models/issue_mail.go index a9c5570c298cd..615aa82f06090 100644 --- a/models/issue_mail.go +++ b/models/issue_mail.go @@ -18,7 +18,7 @@ func (issue *Issue) mailSubject() string { return fmt.Sprintf("[%s] %s (#%d)", issue.Repo.Name, issue.Title, issue.Index) } -// mailIssueCommentToParticipants can be used only for comment. +// mailIssueCommentToParticipants can be used for both new issue creation and comment. // This function sends two list of emails: // 1. Repository watchers and users who are participated in comments. // 2. Users who are not in 1. but get mentioned in current issue/comment. From 36ae73298333d068bf5c69dcf064db459806be09 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Thu, 15 Jun 2017 17:13:09 +0200 Subject: [PATCH 09/88] Added link to comment in "Dashboard" Deleting feed entry if a comment is going to be deleted Signed-off-by: Jonas Franz --- models/action.go | 24 ++++++++++++++++++++++++ models/issue_comment.go | 12 +++++++++++- routers/user/home.go | 16 ++++++++++++++++ templates/user/dashboard/feeds.tmpl | 2 +- 4 files changed, 52 insertions(+), 2 deletions(-) diff --git a/models/action.go b/models/action.go index bf3ce027cf3b7..3b47a7163b429 100644 --- a/models/action.go +++ b/models/action.go @@ -22,6 +22,7 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "strconv" ) // ActionType represents the type of an action. @@ -77,6 +78,8 @@ type Action struct { ActUser *User `xorm:"-"` RepoID int64 `xorm:"INDEX"` Repo *Repository `xorm:"-"` + CommentID int64 `xorm:"INDEX"` + Comment *Comment `xorm:"-"` RefName string IsPrivate bool `xorm:"INDEX NOT NULL DEFAULT false"` Content string `xorm:"TEXT"` @@ -191,6 +194,27 @@ func (a *Action) GetRepoLink() string { return "/" + a.GetRepoPath() } +// GetCommentLink returns link to action comment. +func (a *Action) GetCommentLink() string { + if a.Comment == nil { + //Return link to issue if comment is not set. + if len(a.GetIssueInfos()) > 1 { + issueIdString := a.GetIssueInfos()[0] + if issueId, err := strconv.ParseInt(issueIdString, 10, 64); err != nil { + issue, err := GetIssueByID(issueId) + if err != nil { + return "#" + } + return issue.HTMLURL() + }else { + return "#" + } + } + return "#" + } + return a.Comment.HTMLURL() +} + // GetBranch returns the action's repository branch. func (a *Action) GetBranch() string { return a.RefName diff --git a/models/issue_comment.go b/models/issue_comment.go index 69edf28f54326..5aec259955839 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -318,7 +318,8 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err OldTitle: opts.OldTitle, NewTitle: opts.NewTitle, } - if _, err = e.Insert(comment); err != nil { + var commentId int64 + if commentId, err = e.Insert(comment); err != nil { return nil, err } @@ -334,6 +335,8 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err Content: fmt.Sprintf("%d|%s", opts.Issue.Index, strings.Split(opts.Content, "\n")[0]), RepoID: opts.Repo.ID, Repo: opts.Repo, + Comment: comment, + CommentID: commentId, IsPrivate: opts.Repo.IsPrivate, } @@ -641,5 +644,12 @@ func DeleteComment(comment *Comment) error { } } + //Delete associated Action + if _, err := sess.Delete(&Action{ + CommentID: comment.ID, + }); err != nil { + return err + } + return sess.Commit() } diff --git a/routers/user/home.go b/routers/user/home.go index 9ff03cf04f21a..2b5779be632ef 100644 --- a/routers/user/home.go +++ b/routers/user/home.go @@ -74,6 +74,7 @@ func retrieveFeeds(ctx *context.Context, user *models.User, includePrivate, isPr if ctx.User != nil { userCache[ctx.User.ID] = ctx.User } + commentCache := map[int64]*models.Comment{} repoCache := map[int64]*models.Repository{} for _, act := range actions { // Cache results to reduce queries. @@ -104,6 +105,20 @@ func retrieveFeeds(ctx *context.Context, user *models.User, includePrivate, isPr } act.Repo = repo + + comment, ok := commentCache[act.CommentID] + if !ok { + comment, err = models.GetCommentByID(act.CommentID) + if err != nil { + if models.IsErrCommentNotExist(err) { + continue + } + ctx.Handle(500, "GetCommentByID", err) + return + } + } + act.Comment = comment + repoOwner, ok := userCache[repo.OwnerID] if !ok { repoOwner, err = models.GetUserByID(repo.OwnerID) @@ -115,6 +130,7 @@ func retrieveFeeds(ctx *context.Context, user *models.User, includePrivate, isPr return } } + repo.Owner = repoOwner } ctx.Data["Feeds"] = actions diff --git a/templates/user/dashboard/feeds.tmpl b/templates/user/dashboard/feeds.tmpl index 12768bca52858..855a79786a4ba 100644 --- a/templates/user/dashboard/feeds.tmpl +++ b/templates/user/dashboard/feeds.tmpl @@ -63,7 +63,7 @@ {{else if eq .GetOpType 7}} {{index .GetIssueInfos 1}} {{else if eq .GetOpType 10}} - {{.GetIssueTitle}} + {{.GetIssueTitle}}

{{index .GetIssueInfos 1}}

{{else if eq .GetOpType 11}}

{{index .GetIssueInfos 1}}

From 1826b2b03326c25d18076ef4050e38a58ada0b5d Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Sat, 17 Jun 2017 13:30:08 +0200 Subject: [PATCH 10/88] Added migration Signed-off-by: Jonas Franz --- models/migrations/migrations.go | 2 ++ models/migrations/v35.go | 46 +++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 models/migrations/v35.go diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 2a54c497c1f69..923984ac061f1 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -118,6 +118,8 @@ var migrations = []Migration{ NewMigration("remove columns from action", removeActionColumns), // v34 -> v35 NewMigration("give all units to owner teams", giveAllUnitsToOwnerTeams), + // v35 -> v36 + NewMigration("adds comment to an action", addCommentIdToAction), } // Migrate database to current version diff --git a/models/migrations/v35.go b/models/migrations/v35.go new file mode 100644 index 0000000000000..50450cd56738f --- /dev/null +++ b/models/migrations/v35.go @@ -0,0 +1,46 @@ +// Copyright 2017 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "fmt" + + "github.com/go-xorm/xorm" + "time" +) + +func addCommentIdToAction(x *xorm.Engine) error { + // Action see models/action.go + type Action struct { + ID int64 `xorm:"pk autoincr"` + UserID int64 `xorm:"INDEX"` // Receiver user id. + ActUserID int64 `xorm:"INDEX"` // Action user id. + RepoID int64 `xorm:"INDEX"` + CommentID int64 `xorm:"INDEX"` + RefName string + IsPrivate bool `xorm:"INDEX NOT NULL DEFAULT false"` + Content string `xorm:"TEXT"` + Created time.Time `xorm:"-"` + CreatedUnix int64 `xorm:"INDEX"` + } + + var actions []*Action + if err := x.Find(&actions); err != nil { + return fmt.Errorf("Find: %v", err) + } + + if err := x.DropTables(new(Action)); err != nil { + return fmt.Errorf("DropTables: %v", err) + } + + if err := x.Sync2(new(Action)); err != nil { + return fmt.Errorf("Sync2: %v", err) + } + + if _, err := x.Insert(actions); err != nil { + return fmt.Errorf("Insert: %v", err) + } + return nil +} From bb33f2eac22794e35eb569611311d26a0a06098d Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Sat, 17 Jun 2017 15:04:08 +0200 Subject: [PATCH 11/88] Added improved migration to add a CommentID column to action. Added improved links to comments in feed entries. Signed-off-by: Jonas Franz --- models/action.go | 41 ++++++++++++++++++----------- models/issue_comment.go | 5 ++-- models/migrations/v35.go | 23 +--------------- templates/user/dashboard/feeds.tmpl | 2 +- 4 files changed, 30 insertions(+), 41 deletions(-) diff --git a/models/action.go b/models/action.go index 3b47a7163b429..65a4ed5626b4a 100644 --- a/models/action.go +++ b/models/action.go @@ -196,23 +196,34 @@ func (a *Action) GetRepoLink() string { // GetCommentLink returns link to action comment. func (a *Action) GetCommentLink() string { - if a.Comment == nil { - //Return link to issue if comment is not set. - if len(a.GetIssueInfos()) > 1 { - issueIdString := a.GetIssueInfos()[0] - if issueId, err := strconv.ParseInt(issueIdString, 10, 64); err != nil { - issue, err := GetIssueByID(issueId) - if err != nil { - return "#" - } - return issue.HTMLURL() - }else { - return "#" - } - } + if a == nil { + return "#" + } + if a.Comment == nil && a.CommentID != 0 { + a.Comment, _ = GetCommentByID(a.CommentID) + } + if a.Comment != nil { + return a.Comment.HTMLURL() + } + if len(a.GetIssueInfos()) == 0 { + return "#" + } + //Return link to issue + issueIdString := a.GetIssueInfos()[0] + issueId, err := strconv.ParseInt(issueIdString, 10, 64) + + if err != nil { return "#" } - return a.Comment.HTMLURL() + + issue, err := GetIssueByID(issueId) + + if err != nil { + return "#" + } + + return issue.HTMLURL() + } // GetBranch returns the action's repository branch. diff --git a/models/issue_comment.go b/models/issue_comment.go index 5aec259955839..22286e0958f0b 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -318,8 +318,7 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err OldTitle: opts.OldTitle, NewTitle: opts.NewTitle, } - var commentId int64 - if commentId, err = e.Insert(comment); err != nil { + if _, err = e.Insert(comment); err != nil { return nil, err } @@ -336,7 +335,7 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err RepoID: opts.Repo.ID, Repo: opts.Repo, Comment: comment, - CommentID: commentId, + CommentID: comment.ID, IsPrivate: opts.Repo.IsPrivate, } diff --git a/models/migrations/v35.go b/models/migrations/v35.go index 50450cd56738f..daef9c49ffb76 100644 --- a/models/migrations/v35.go +++ b/models/migrations/v35.go @@ -8,39 +8,18 @@ import ( "fmt" "github.com/go-xorm/xorm" - "time" ) func addCommentIdToAction(x *xorm.Engine) error { // Action see models/action.go type Action struct { - ID int64 `xorm:"pk autoincr"` - UserID int64 `xorm:"INDEX"` // Receiver user id. - ActUserID int64 `xorm:"INDEX"` // Action user id. - RepoID int64 `xorm:"INDEX"` - CommentID int64 `xorm:"INDEX"` - RefName string - IsPrivate bool `xorm:"INDEX NOT NULL DEFAULT false"` - Content string `xorm:"TEXT"` - Created time.Time `xorm:"-"` - CreatedUnix int64 `xorm:"INDEX"` + CommentID int64 `xorm:"INDEX"` } - var actions []*Action - if err := x.Find(&actions); err != nil { - return fmt.Errorf("Find: %v", err) - } - - if err := x.DropTables(new(Action)); err != nil { - return fmt.Errorf("DropTables: %v", err) - } if err := x.Sync2(new(Action)); err != nil { return fmt.Errorf("Sync2: %v", err) } - if _, err := x.Insert(actions); err != nil { - return fmt.Errorf("Insert: %v", err) - } return nil } diff --git a/templates/user/dashboard/feeds.tmpl b/templates/user/dashboard/feeds.tmpl index 855a79786a4ba..9431d75c356cc 100644 --- a/templates/user/dashboard/feeds.tmpl +++ b/templates/user/dashboard/feeds.tmpl @@ -68,7 +68,7 @@ {{else if eq .GetOpType 11}}

{{index .GetIssueInfos 1}}

{{else if (or (or (eq .GetOpType 12) (eq .GetOpType 13)) (or (eq .GetOpType 14) (eq .GetOpType 15)))}} - {{.GetIssueTitle}} + {{.GetIssueTitle}} {{end}}

{{TimeSince .GetCreate $.i18n.Lang}}

From 3f1c85c40703a777223d7136ffa915938fb18f4b Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Sat, 17 Jun 2017 15:04:37 +0200 Subject: [PATCH 12/88] Added improved migration to add a CommentID column to action. Added improved links to comments in feed entries. Signed-off-by: Jonas Franz --- models/action.go | 4 ++-- models/migrations/v35.go | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/models/action.go b/models/action.go index 65a4ed5626b4a..4ab4746d1c36c 100644 --- a/models/action.go +++ b/models/action.go @@ -78,8 +78,8 @@ type Action struct { ActUser *User `xorm:"-"` RepoID int64 `xorm:"INDEX"` Repo *Repository `xorm:"-"` - CommentID int64 `xorm:"INDEX"` - Comment *Comment `xorm:"-"` + CommentID int64 `xorm:"INDEX"` + Comment *Comment `xorm:"-"` RefName string IsPrivate bool `xorm:"INDEX NOT NULL DEFAULT false"` Content string `xorm:"TEXT"` diff --git a/models/migrations/v35.go b/models/migrations/v35.go index daef9c49ffb76..b5f1f1c11dbc8 100644 --- a/models/migrations/v35.go +++ b/models/migrations/v35.go @@ -13,10 +13,9 @@ import ( func addCommentIdToAction(x *xorm.Engine) error { // Action see models/action.go type Action struct { - CommentID int64 `xorm:"INDEX"` + CommentID int64 `xorm:"INDEX"` } - if err := x.Sync2(new(Action)); err != nil { return fmt.Errorf("Sync2: %v", err) } From 5791853b077ebf3a21b9a465a0bace5c9fe12dfa Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Sat, 17 Jun 2017 15:13:27 +0200 Subject: [PATCH 13/88] Added improved migration to add a CommentID column to action. Added improved links to comments in feed entries. Signed-off-by: Jonas Franz --- models/action.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/models/action.go b/models/action.go index 4ab4746d1c36c..4ef070dbae570 100644 --- a/models/action.go +++ b/models/action.go @@ -209,14 +209,14 @@ func (a *Action) GetCommentLink() string { return "#" } //Return link to issue - issueIdString := a.GetIssueInfos()[0] - issueId, err := strconv.ParseInt(issueIdString, 10, 64) + issueIDString := a.GetIssueInfos()[0] + issueID, err := strconv.ParseInt(issueIDString, 10, 64) if err != nil { return "#" } - issue, err := GetIssueByID(issueId) + issue, err := GetIssueByID(issueID) if err != nil { return "#" From deddcdaa1b5efe53f52ff1ded1404987757b40df Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Sat, 17 Jun 2017 15:14:17 +0200 Subject: [PATCH 14/88] Added improved migration to add a CommentID column to action. Added improved links to comments in feed entries. (+ gofmt) Signed-off-by: Jonas Franz --- routers/user/home.go | 1 - 1 file changed, 1 deletion(-) diff --git a/routers/user/home.go b/routers/user/home.go index 2b5779be632ef..315301fc70267 100644 --- a/routers/user/home.go +++ b/routers/user/home.go @@ -105,7 +105,6 @@ func retrieveFeeds(ctx *context.Context, user *models.User, includePrivate, isPr } act.Repo = repo - comment, ok := commentCache[act.CommentID] if !ok { comment, err = models.GetCommentByID(act.CommentID) From 70d8dd910b5ec8e42f113b3dd0c7f557517e1055 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Sat, 17 Jun 2017 15:17:15 +0200 Subject: [PATCH 15/88] Added improved migration to add a CommentID column to action. Added improved links to comments in feed entries. (+ gofmt) Signed-off-by: Jonas Franz --- models/migrations/migrations.go | 2 +- models/migrations/v35.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 923984ac061f1..87f1850a6db80 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -119,7 +119,7 @@ var migrations = []Migration{ // v34 -> v35 NewMigration("give all units to owner teams", giveAllUnitsToOwnerTeams), // v35 -> v36 - NewMigration("adds comment to an action", addCommentIdToAction), + NewMigration("adds comment to an action", addCommentIDToAction), } // Migrate database to current version diff --git a/models/migrations/v35.go b/models/migrations/v35.go index b5f1f1c11dbc8..c716438f48aad 100644 --- a/models/migrations/v35.go +++ b/models/migrations/v35.go @@ -10,7 +10,7 @@ import ( "github.com/go-xorm/xorm" ) -func addCommentIdToAction(x *xorm.Engine) error { +func addCommentIDToAction(x *xorm.Engine) error { // Action see models/action.go type Action struct { CommentID int64 `xorm:"INDEX"` From 3b997264e903ba05ed0211f9ff8f2f5ca6b12a36 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Sat, 17 Jun 2017 15:24:13 +0200 Subject: [PATCH 16/88] Added improved migration to add a CommentID column to action. Added improved links to comments in feed entries. (+ gofmt) Signed-off-by: Jonas Franz --- models/action.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/action.go b/models/action.go index 4ef070dbae570..db3592536021b 100644 --- a/models/action.go +++ b/models/action.go @@ -9,6 +9,7 @@ import ( "fmt" "path" "regexp" + "strconv" "strings" "time" "unicode" @@ -22,7 +23,6 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "strconv" ) // ActionType represents the type of an action. From 9e18513430cdba60fb3402c4602f504931f68b72 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Sat, 17 Jun 2017 16:00:00 +0200 Subject: [PATCH 17/88] Added improved migration to add a CommentID column to action. Added improved links to comments in feed entries. (+ gofmt) Signed-off-by: Jonas Franz --- models/migrations/v35.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/migrations/v35.go b/models/migrations/v35.go index c716438f48aad..0bfcbc7aaa0b5 100644 --- a/models/migrations/v35.go +++ b/models/migrations/v35.go @@ -1,4 +1,4 @@ -// Copyright 2017 The Gogs Authors. All rights reserved. +// Copyright 2017 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. From 0e20e81957ce4436d441dfeb70b9686204bb1c66 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Sat, 17 Jun 2017 18:26:54 +0200 Subject: [PATCH 18/88] Added improved migration to add a CommentID column to action. Added improved links to comments in feed entries. (+ gofmt) Signed-off-by: Jonas Franz --- routers/user/home.go | 14 -------------- templates/user/dashboard/feeds.tmpl | 2 +- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/routers/user/home.go b/routers/user/home.go index 315301fc70267..3867d01a63371 100644 --- a/routers/user/home.go +++ b/routers/user/home.go @@ -74,7 +74,6 @@ func retrieveFeeds(ctx *context.Context, user *models.User, includePrivate, isPr if ctx.User != nil { userCache[ctx.User.ID] = ctx.User } - commentCache := map[int64]*models.Comment{} repoCache := map[int64]*models.Repository{} for _, act := range actions { // Cache results to reduce queries. @@ -105,19 +104,6 @@ func retrieveFeeds(ctx *context.Context, user *models.User, includePrivate, isPr } act.Repo = repo - comment, ok := commentCache[act.CommentID] - if !ok { - comment, err = models.GetCommentByID(act.CommentID) - if err != nil { - if models.IsErrCommentNotExist(err) { - continue - } - ctx.Handle(500, "GetCommentByID", err) - return - } - } - act.Comment = comment - repoOwner, ok := userCache[repo.OwnerID] if !ok { repoOwner, err = models.GetUserByID(repo.OwnerID) diff --git a/templates/user/dashboard/feeds.tmpl b/templates/user/dashboard/feeds.tmpl index 9431d75c356cc..855a79786a4ba 100644 --- a/templates/user/dashboard/feeds.tmpl +++ b/templates/user/dashboard/feeds.tmpl @@ -68,7 +68,7 @@ {{else if eq .GetOpType 11}}

{{index .GetIssueInfos 1}}

{{else if (or (or (eq .GetOpType 12) (eq .GetOpType 13)) (or (eq .GetOpType 14) (eq .GetOpType 15)))}} - {{.GetIssueTitle}} + {{.GetIssueTitle}} {{end}}

{{TimeSince .GetCreate $.i18n.Lang}}

From 8818bc7bd48773974a7d7a3c9c67d6ca267685bb Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Sun, 18 Jun 2017 09:44:34 +0200 Subject: [PATCH 19/88] Added improved links to comments in feed entries. (+ gofmt) Signed-off-by: Jonas Franz --- routers/user/home.go | 1 - 1 file changed, 1 deletion(-) diff --git a/routers/user/home.go b/routers/user/home.go index 3867d01a63371..9ff03cf04f21a 100644 --- a/routers/user/home.go +++ b/routers/user/home.go @@ -115,7 +115,6 @@ func retrieveFeeds(ctx *context.Context, user *models.User, includePrivate, isPr return } } - repo.Owner = repoOwner } ctx.Data["Feeds"] = actions From 3e1241c373bd4a9bad735394ab9d99ff11dbdb0a Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Thu, 22 Jun 2017 20:21:01 +0200 Subject: [PATCH 20/88] Fixes #1956 by filtering for deleted comments that are referenced in actions. Signed-off-by: Jonas Franz --- models/action.go | 17 ++++++++++++----- models/action_test.go | 27 +++++++++++++++------------ models/issue_comment.go | 7 ------- routers/user/home.go | 13 +++++++------ routers/user/profile.go | 2 +- 5 files changed, 35 insertions(+), 31 deletions(-) diff --git a/models/action.go b/models/action.go index db3592536021b..e1d51f5d6f253 100644 --- a/models/action.go +++ b/models/action.go @@ -223,7 +223,6 @@ func (a *Action) GetCommentLink() string { } return issue.HTMLURL() - } // GetBranch returns the action's repository branch. @@ -709,10 +708,11 @@ func MergePullRequestAction(actUser *User, repo *Repository, pull *Issue) error // GetFeedsOptions options for retrieving feeds type GetFeedsOptions struct { - RequestedUser *User - RequestingUserID int64 - IncludePrivate bool // include private actions - OnlyPerformedBy bool // only actions performed by requested user + RequestedUser *User + RequestingUserID int64 + IncludePrivate bool // include private actions + OnlyPerformedBy bool // only actions performed by requested user + IncludeDeletedComments bool // include comments that reference a deleted comment } // GetFeeds returns actions according to the provided options @@ -741,5 +741,12 @@ func GetFeeds(opts GetFeedsOptions) ([]*Action, error) { if opts.RequestedUser.IsOrganization() { sess.In("repo_id", repoIDs) } + + if !opts.IncludeDeletedComments { + // commentID != 0 AND comment exist + sess.And("(comment_id ISNULL OR comment_id = 0 OR EXISTS(SELECT NULL FROM comment c WHERE comment_id = c.id))") + + } + return actions, sess.Find(&actions) } diff --git a/models/action_test.go b/models/action_test.go index debc884b37f50..a3e382555e53a 100644 --- a/models/action_test.go +++ b/models/action_test.go @@ -306,10 +306,11 @@ func TestGetFeeds(t *testing.T) { user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) actions, err := GetFeeds(GetFeedsOptions{ - RequestedUser: user, - RequestingUserID: user.ID, - IncludePrivate: true, - OnlyPerformedBy: false, + RequestedUser: user, + RequestingUserID: user.ID, + IncludePrivate: true, + OnlyPerformedBy: false, + IncludeDeletedComments: true, }) assert.NoError(t, err) assert.Len(t, actions, 1) @@ -333,10 +334,11 @@ func TestGetFeeds2(t *testing.T) { userID := AssertExistsAndLoadBean(t, &OrgUser{OrgID: org.ID, IsOwner: true}).(*OrgUser).UID actions, err := GetFeeds(GetFeedsOptions{ - RequestedUser: org, - RequestingUserID: userID, - IncludePrivate: true, - OnlyPerformedBy: false, + RequestedUser: org, + RequestingUserID: userID, + IncludePrivate: true, + OnlyPerformedBy: false, + IncludeDeletedComments: true, }) assert.NoError(t, err) assert.Len(t, actions, 1) @@ -344,10 +346,11 @@ func TestGetFeeds2(t *testing.T) { assert.EqualValues(t, org.ID, actions[0].UserID) actions, err = GetFeeds(GetFeedsOptions{ - RequestedUser: org, - RequestingUserID: userID, - IncludePrivate: false, - OnlyPerformedBy: false, + RequestedUser: org, + RequestingUserID: userID, + IncludePrivate: false, + OnlyPerformedBy: false, + IncludeDeletedComments: true, }) assert.NoError(t, err) assert.Len(t, actions, 0) diff --git a/models/issue_comment.go b/models/issue_comment.go index 22286e0958f0b..8ed6c1eb01cde 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -643,12 +643,5 @@ func DeleteComment(comment *Comment) error { } } - //Delete associated Action - if _, err := sess.Delete(&Action{ - CommentID: comment.ID, - }); err != nil { - return err - } - return sess.Commit() } diff --git a/routers/user/home.go b/routers/user/home.go index 9ff03cf04f21a..c39e31bc64236 100644 --- a/routers/user/home.go +++ b/routers/user/home.go @@ -54,16 +54,17 @@ func getDashboardContextUser(ctx *context.Context) *models.User { } // retrieveFeeds loads feeds for the specified user -func retrieveFeeds(ctx *context.Context, user *models.User, includePrivate, isProfile bool) { +func retrieveFeeds(ctx *context.Context, user *models.User, includePrivate, isProfile bool, includeDeletedComments bool) { var requestingID int64 if ctx.User != nil { requestingID = ctx.User.ID } actions, err := models.GetFeeds(models.GetFeedsOptions{ - RequestedUser: user, - RequestingUserID: requestingID, - IncludePrivate: includePrivate, - OnlyPerformedBy: isProfile, + RequestedUser: user, + RequestingUserID: requestingID, + IncludePrivate: includePrivate, + OnlyPerformedBy: isProfile, + IncludeDeletedComments: includeDeletedComments, }) if err != nil { ctx.Handle(500, "GetFeeds", err) @@ -186,7 +187,7 @@ func Dashboard(ctx *context.Context) { ctx.Data["MirrorCount"] = len(mirrors) ctx.Data["Mirrors"] = mirrors - retrieveFeeds(ctx, ctxUser, true, false) + retrieveFeeds(ctx, ctxUser, true, false, false) if ctx.Written() { return } diff --git a/routers/user/profile.go b/routers/user/profile.go index 1768cec7b10bd..c7d761052e855 100644 --- a/routers/user/profile.go +++ b/routers/user/profile.go @@ -138,7 +138,7 @@ func Profile(ctx *context.Context) { ctx.Data["Keyword"] = keyword switch tab { case "activity": - retrieveFeeds(ctx, ctxUser, showPrivate, true) + retrieveFeeds(ctx, ctxUser, showPrivate, true, false) if ctx.Written() { return } From 3ee3dc3768fc74da52278790e0283c7f660ab278 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Fri, 23 Jun 2017 20:26:20 +0200 Subject: [PATCH 21/88] Fixes #1956 by filtering for deleted comments. Signed-off-by: Jonas Franz --- models/action.go | 34 ++++++++++++++++------------------ models/issue_comment.go | 2 ++ models/migrations/v35.go | 7 ++++++- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/models/action.go b/models/action.go index e1d51f5d6f253..a499f704ca951 100644 --- a/models/action.go +++ b/models/action.go @@ -71,20 +71,21 @@ func init() { // repository. It implemented interface base.Actioner so that can be // used in template render. type Action struct { - ID int64 `xorm:"pk autoincr"` - UserID int64 `xorm:"INDEX"` // Receiver user id. - OpType ActionType - ActUserID int64 `xorm:"INDEX"` // Action user id. - ActUser *User `xorm:"-"` - RepoID int64 `xorm:"INDEX"` - Repo *Repository `xorm:"-"` - CommentID int64 `xorm:"INDEX"` - Comment *Comment `xorm:"-"` - RefName string - IsPrivate bool `xorm:"INDEX NOT NULL DEFAULT false"` - Content string `xorm:"TEXT"` - Created time.Time `xorm:"-"` - CreatedUnix int64 `xorm:"INDEX"` + ID int64 `xorm:"pk autoincr"` + UserID int64 `xorm:"INDEX"` // Receiver user id. + OpType ActionType + ActUserID int64 `xorm:"INDEX"` // Action user id. + ActUser *User `xorm:"-"` + RepoID int64 `xorm:"INDEX"` + Repo *Repository `xorm:"-"` + CommentID int64 `xorm:"INDEX"` + Comment *Comment `xorm:"-"` + CommentDeleted bool `xorm:"default 0 not null"` + RefName string + IsPrivate bool `xorm:"INDEX NOT NULL DEFAULT false"` + Content string `xorm:"TEXT"` + Created time.Time `xorm:"-"` + CreatedUnix int64 `xorm:"INDEX"` } // BeforeInsert will be invoked by XORM before inserting a record @@ -211,13 +212,11 @@ func (a *Action) GetCommentLink() string { //Return link to issue issueIDString := a.GetIssueInfos()[0] issueID, err := strconv.ParseInt(issueIDString, 10, 64) - if err != nil { return "#" } issue, err := GetIssueByID(issueID) - if err != nil { return "#" } @@ -743,8 +742,7 @@ func GetFeeds(opts GetFeedsOptions) ([]*Action, error) { } if !opts.IncludeDeletedComments { - // commentID != 0 AND comment exist - sess.And("(comment_id ISNULL OR comment_id = 0 OR EXISTS(SELECT NULL FROM comment c WHERE comment_id = c.id))") + sess.And("comment_deleted = ?", false) } diff --git a/models/issue_comment.go b/models/issue_comment.go index 8ed6c1eb01cde..ecff06941dea2 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -643,5 +643,7 @@ func DeleteComment(comment *Comment) error { } } + sess.Exec("UPDATE `action` SET comment_deleted = 1 WHERE comment_id = ?", comment.ID) + return sess.Commit() } diff --git a/models/migrations/v35.go b/models/migrations/v35.go index 0bfcbc7aaa0b5..e5089f7a96506 100644 --- a/models/migrations/v35.go +++ b/models/migrations/v35.go @@ -13,12 +13,17 @@ import ( func addCommentIDToAction(x *xorm.Engine) error { // Action see models/action.go type Action struct { - CommentID int64 `xorm:"INDEX"` + CommentID int64 `xorm:"INDEX"` + CommentDeleted bool `xorm:"default 0 not null"` } if err := x.Sync2(new(Action)); err != nil { return fmt.Errorf("Sync2: %v", err) } + x.Where("comment_id = 0").Update(&Action{ + CommentDeleted: false, + }) + return nil } From 56a64e3e34f3187c167a594407ebade02aaa95cb Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Sun, 25 Jun 2017 18:17:50 +0200 Subject: [PATCH 22/88] Fixes #1956 by filtering for deleted comments. Introducing "IsDeleted" column to action. Signed-off-by: Jonas Franz --- models/action.go | 44 ++++++++++++++++++++-------------------- models/action_test.go | 30 +++++++++++++-------------- models/issue_comment.go | 3 +-- models/migrations/v35.go | 8 ++------ routers/user/home.go | 10 ++++----- 5 files changed, 45 insertions(+), 50 deletions(-) diff --git a/models/action.go b/models/action.go index a499f704ca951..9a12e9229bcf7 100644 --- a/models/action.go +++ b/models/action.go @@ -71,21 +71,21 @@ func init() { // repository. It implemented interface base.Actioner so that can be // used in template render. type Action struct { - ID int64 `xorm:"pk autoincr"` - UserID int64 `xorm:"INDEX"` // Receiver user id. - OpType ActionType - ActUserID int64 `xorm:"INDEX"` // Action user id. - ActUser *User `xorm:"-"` - RepoID int64 `xorm:"INDEX"` - Repo *Repository `xorm:"-"` - CommentID int64 `xorm:"INDEX"` - Comment *Comment `xorm:"-"` - CommentDeleted bool `xorm:"default 0 not null"` - RefName string - IsPrivate bool `xorm:"INDEX NOT NULL DEFAULT false"` - Content string `xorm:"TEXT"` - Created time.Time `xorm:"-"` - CreatedUnix int64 `xorm:"INDEX"` + ID int64 `xorm:"pk autoincr"` + UserID int64 `xorm:"INDEX"` // Receiver user id. + OpType ActionType + ActUserID int64 `xorm:"INDEX"` // Action user id. + ActUser *User `xorm:"-"` + RepoID int64 `xorm:"INDEX"` + Repo *Repository `xorm:"-"` + CommentID int64 `xorm:"INDEX"` + Comment *Comment `xorm:"-"` + IsDeleted bool `xorm:"INDEX NOT NULL DEFAULT false"` + RefName string + IsPrivate bool `xorm:"INDEX NOT NULL DEFAULT false"` + Content string `xorm:"TEXT"` + Created time.Time `xorm:"-"` + CreatedUnix int64 `xorm:"INDEX"` } // BeforeInsert will be invoked by XORM before inserting a record @@ -707,11 +707,11 @@ func MergePullRequestAction(actUser *User, repo *Repository, pull *Issue) error // GetFeedsOptions options for retrieving feeds type GetFeedsOptions struct { - RequestedUser *User - RequestingUserID int64 - IncludePrivate bool // include private actions - OnlyPerformedBy bool // only actions performed by requested user - IncludeDeletedComments bool // include comments that reference a deleted comment + RequestedUser *User + RequestingUserID int64 + IncludePrivate bool // include private actions + OnlyPerformedBy bool // only actions performed by requested user + IncludeDeleted bool // include deleted actions } // GetFeeds returns actions according to the provided options @@ -741,8 +741,8 @@ func GetFeeds(opts GetFeedsOptions) ([]*Action, error) { sess.In("repo_id", repoIDs) } - if !opts.IncludeDeletedComments { - sess.And("comment_deleted = ?", false) + if !opts.IncludeDeleted { + sess.And("is_deleted = ?", false) } diff --git a/models/action_test.go b/models/action_test.go index a3e382555e53a..96ee9a5f73bc8 100644 --- a/models/action_test.go +++ b/models/action_test.go @@ -306,11 +306,11 @@ func TestGetFeeds(t *testing.T) { user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) actions, err := GetFeeds(GetFeedsOptions{ - RequestedUser: user, - RequestingUserID: user.ID, - IncludePrivate: true, - OnlyPerformedBy: false, - IncludeDeletedComments: true, + RequestedUser: user, + RequestingUserID: user.ID, + IncludePrivate: true, + OnlyPerformedBy: false, + IncludeDeleted: true, }) assert.NoError(t, err) assert.Len(t, actions, 1) @@ -334,11 +334,11 @@ func TestGetFeeds2(t *testing.T) { userID := AssertExistsAndLoadBean(t, &OrgUser{OrgID: org.ID, IsOwner: true}).(*OrgUser).UID actions, err := GetFeeds(GetFeedsOptions{ - RequestedUser: org, - RequestingUserID: userID, - IncludePrivate: true, - OnlyPerformedBy: false, - IncludeDeletedComments: true, + RequestedUser: org, + RequestingUserID: userID, + IncludePrivate: true, + OnlyPerformedBy: false, + IncludeDeleted: true, }) assert.NoError(t, err) assert.Len(t, actions, 1) @@ -346,11 +346,11 @@ func TestGetFeeds2(t *testing.T) { assert.EqualValues(t, org.ID, actions[0].UserID) actions, err = GetFeeds(GetFeedsOptions{ - RequestedUser: org, - RequestingUserID: userID, - IncludePrivate: false, - OnlyPerformedBy: false, - IncludeDeletedComments: true, + RequestedUser: org, + RequestingUserID: userID, + IncludePrivate: false, + OnlyPerformedBy: false, + IncludeDeleted: true, }) assert.NoError(t, err) assert.Len(t, actions, 0) diff --git a/models/issue_comment.go b/models/issue_comment.go index ecff06941dea2..19bda28340279 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -642,8 +642,7 @@ func DeleteComment(comment *Comment) error { return err } } - - sess.Exec("UPDATE `action` SET comment_deleted = 1 WHERE comment_id = ?", comment.ID) + sess.Where("comment_id = ?", comment.ID).Cols("is_deleted").Update(&Action{IsDeleted: true}) return sess.Commit() } diff --git a/models/migrations/v35.go b/models/migrations/v35.go index e5089f7a96506..7746663a40a8a 100644 --- a/models/migrations/v35.go +++ b/models/migrations/v35.go @@ -13,17 +13,13 @@ import ( func addCommentIDToAction(x *xorm.Engine) error { // Action see models/action.go type Action struct { - CommentID int64 `xorm:"INDEX"` - CommentDeleted bool `xorm:"default 0 not null"` + CommentID int64 `xorm:"INDEX"` + IsDeleted bool `xorm:"INDEX NOT NULL DEFAULT false"` } if err := x.Sync2(new(Action)); err != nil { return fmt.Errorf("Sync2: %v", err) } - x.Where("comment_id = 0").Update(&Action{ - CommentDeleted: false, - }) - return nil } diff --git a/routers/user/home.go b/routers/user/home.go index c39e31bc64236..71288906908f6 100644 --- a/routers/user/home.go +++ b/routers/user/home.go @@ -60,11 +60,11 @@ func retrieveFeeds(ctx *context.Context, user *models.User, includePrivate, isPr requestingID = ctx.User.ID } actions, err := models.GetFeeds(models.GetFeedsOptions{ - RequestedUser: user, - RequestingUserID: requestingID, - IncludePrivate: includePrivate, - OnlyPerformedBy: isProfile, - IncludeDeletedComments: includeDeletedComments, + RequestedUser: user, + RequestingUserID: requestingID, + IncludePrivate: includePrivate, + OnlyPerformedBy: isProfile, + IncludeDeleted: includeDeletedComments, }) if err != nil { ctx.Handle(500, "GetFeeds", err) From c8d270b143aff14fcd80ed5ca1dc51df515ec447 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Mon, 24 Jul 2017 23:47:00 +0200 Subject: [PATCH 23/88] Adding design draft (not functional) Adding database models for stopwatches and trackedtimes See go-gitea/gitea#967 Signed-off-by: Jonas Franz --- models/error.go | 43 ++++++++++++ models/issue_stopwatch.go | 66 ++++++++++++++++++ models/issue_tracked_time.go | 67 +++++++++++++++++++ .../repo/issue/view_content/comments.tmpl | 20 ++++++ .../repo/issue/view_content/sidebar.tmpl | 46 +++++++++++++ 5 files changed, 242 insertions(+) create mode 100644 models/issue_stopwatch.go create mode 100644 models/issue_tracked_time.go diff --git a/models/error.go b/models/error.go index 74551ad8f7997..69a66b1aee347 100644 --- a/models/error.go +++ b/models/error.go @@ -770,6 +770,49 @@ func (err ErrCommentNotExist) Error() string { return fmt.Sprintf("comment does not exist [id: %d, issue_id: %d]", err.ID, err.IssueID) } +// _________ __ __ .__ +// / _____// |_ ____ ________ _ _______ _/ |_ ____ | |__ +// \_____ \\ __\/ _ \\____ \ \/ \/ /\__ \\ __\/ ___\| | \ +// / \| | ( <_> ) |_> > / / __ \| | \ \___| Y \ +// /_______ /|__| \____/| __/ \/\_/ (____ /__| \___ >___| / +// \/ |__| \/ \/ \/ + +// ErrStopwatchNotExist represents a "Stopwatch Not Exist" kind of error. +type ErrStopwatchNotExist struct { + ID int64 +} + +// IsErrStopwatchNotExist checks if an error is a ErrStopwatchNotExist. +func IsErrStopwatchNotExist(err error) bool { + _, ok := err.(ErrStopwatchNotExist) + return ok +} + +func (err ErrStopwatchNotExist) Error() string { + return fmt.Sprintf("stopwatch does not exist [id: %d]", err.ID) +} + +// ___________ __ .______________.__ +// \__ ___/___________ ____ | | __ ____ __| _/\__ ___/|__| _____ ____ +// | | \_ __ \__ \ _/ ___\| |/ // __ \ / __ | | | | |/ \_/ __ \ +// | | | | \// __ \\ \___| <\ ___// /_/ | | | | | Y Y \ ___/ +// |____| |__| (____ /\___ >__|_ \\___ >____ | |____| |__|__|_| /\___ > +// \/ \/ \/ \/ \/ \/ \/ + +// ErrStopwatchNotExist represents a "Stopwatch Not Exist" kind of error. +type ErrTrackedTimeNotExist struct { + ID int64 +} + +// IsErrStopwatchNotExist checks if an error is a ErrStopwatchNotExist. +func IsErrTrackedTimeNotExist(err error) bool { + _, ok := err.(ErrStopwatchNotExist) + return ok +} + +func (err ErrTrackedTimeNotExist) Error() string { + return fmt.Sprintf("tracked time does not exist [id: %d]", err.ID) +} // .____ ___. .__ // | | _____ \_ |__ ____ | | // | | \__ \ | __ \_/ __ \| | diff --git a/models/issue_stopwatch.go b/models/issue_stopwatch.go new file mode 100644 index 0000000000000..3f9dcd307bbdc --- /dev/null +++ b/models/issue_stopwatch.go @@ -0,0 +1,66 @@ +package models + +import ( + "time" + "github.com/go-xorm/xorm" + "code.gitea.io/gitea/modules/log" +) + +// Stopwatch represents a stopwatch for time tracking. +type Stopwatch struct { + ID int64 `xorm:"pk autoincr"` + IssueID int64 `xorm:"INDEX"` + Issue *Issue `xorm:"-"` + UserID int64 `xorm:"INDEX"` + User *User `xorm:"-"` + Created time.Time `xorm:"-"` + CreatedUnix int64 +} + +// AfterSet is invoked from XORM after setting the value of a field of this object. +func (s *Stopwatch) AfterSet(colName string, _ xorm.Cell) { + var err error + switch colName { + case "user_id": + s.User, err = GetUserByID(s.UserID) + if err != nil { + if IsErrUserNotExist(err) { + s.UserID = -1 + s.User = nil + } else { + log.Error(3, "GetUserByID[%d]: %v", s.UserID, err) + } + } + + case "issue_id": + s.Issue, err = GetIssueByID(s.IssueID) + if err != nil { + if IsErrIssueNotExist(err) { + s.IssueID = -1 + s.Issue = nil + } else { + log.Error(3, "GetIssueByID[%d]: %v", s.IssueID, err) + } + } + case "created_unix": + s.Created = time.Unix(s.CreatedUnix, 0).Local() + } +} + +// GetStopwatchByID returns the stopwatch by given ID. +func GetStopwatchByID(id int64) (*Stopwatch, error) { + c := new(Stopwatch) + has, err := x.Id(id).Get(c) + if err != nil { + return nil, err + } else if !has { + return nil, ErrStopwatchNotExist{id} + } + return c, nil +} + +// BeforeInsert will be invoked by XORM before inserting a record +// representing this object. +func (s *Stopwatch) BeforeInsert() { + s.CreatedUnix = time.Now().Unix() +} diff --git a/models/issue_tracked_time.go b/models/issue_tracked_time.go new file mode 100644 index 0000000000000..75397fed349a9 --- /dev/null +++ b/models/issue_tracked_time.go @@ -0,0 +1,67 @@ +package models + +import ( + "time" + "github.com/go-xorm/xorm" + "code.gitea.io/gitea/modules/log" +) + +// TrackedTime represents a time that was spent for a specific issue. +type TrackedTime struct { + ID int64 `xorm:"pk autoincr"` + IssueID int64 `xorm:"INDEX"` + Issue *Issue `xorm:"-"` + UserID int64 `xorm:"INDEX"` + User *User `xorm:"-"` + Created time.Time `xorm:"-"` + CreatedUnix int64 + Time int64 +} + +// AfterSet is invoked from XORM after setting the value of a field of this object. +func (t *TrackedTime) AfterSet(colName string, _ xorm.Cell) { + var err error + switch colName { + case "user_id": + t.User, err = GetUserByID(t.UserID) + if err != nil { + if IsErrUserNotExist(err) { + t.UserID = -1 + t.User = nil + } else { + log.Error(3, "GetUserByID[%d]: %v", t.UserID, err) + } + } + + case "issue_id": + t.Issue, err = GetIssueByID(t.IssueID) + if err != nil { + if IsErrIssueNotExist(err) { + t.IssueID = -1 + t.Issue = nil + } else { + log.Error(3, "GetIssueByID[%d]: %v", t.IssueID, err) + } + } + case "created_unix": + t.Created = time.Unix(t.CreatedUnix, 0).Local() + } +} + +// GetTrackedTimeByID returns the tracked time by given ID. +func GetTrackedTimeByID(id int64) (*TrackedTime, error) { + c := new(TrackedTime) + has, err := x.Id(id).Get(c) + if err != nil { + return nil, err + } else if !has { + return nil, ErrTrackedTimeNotExist{id} + } + return c, nil +} + +// BeforeInsert will be invoked by XORM before inserting a record +// representing this object. +func (t *TrackedTime) BeforeInsert() { + t.CreatedUnix = time.Now().Unix() +} diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl index c1b4e7f599609..20b43b96abac7 100644 --- a/templates/repo/issue/view_content/comments.tmpl +++ b/templates/repo/issue/view_content/comments.tmpl @@ -3,6 +3,25 @@ {{if eq .Type 0}} +
+ + + + + {{.Poster.Name}} started working 35min ago +
+
+ + + + + {{.Poster.Name}} finished working 1min ago + +
+ + 34min +
+
+ {{else if eq .Type 1}}
diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index cfb6f183b4bfe..ad70162a5473e 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -120,6 +120,52 @@
+
+
+ + Time tracker +
+
+ + {{$.CsrfTokenHtml}} + + {{if $.IssueWatch.IsWatching}} +
+ +
+ +
+ {{else}} +
+ + +
+ {{end}} +
+
+
+ +
+ +
{{end}} From 6db864a1c0801e6aa72984de820ffbc024d12df3 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Mon, 24 Jul 2017 23:48:23 +0200 Subject: [PATCH 24/88] Adding design draft (not functional) Adding database models for stopwatches and trackedtimes See go-gitea/gitea#967 +gofmt Signed-off-by: Jonas Franz --- models/error.go | 5 +++-- models/issue_stopwatch.go | 18 +++++++++--------- models/issue_tracked_time.go | 20 ++++++++++---------- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/models/error.go b/models/error.go index 69a66b1aee347..6e98beff86e5c 100644 --- a/models/error.go +++ b/models/error.go @@ -779,7 +779,7 @@ func (err ErrCommentNotExist) Error() string { // ErrStopwatchNotExist represents a "Stopwatch Not Exist" kind of error. type ErrStopwatchNotExist struct { - ID int64 + ID int64 } // IsErrStopwatchNotExist checks if an error is a ErrStopwatchNotExist. @@ -801,7 +801,7 @@ func (err ErrStopwatchNotExist) Error() string { // ErrStopwatchNotExist represents a "Stopwatch Not Exist" kind of error. type ErrTrackedTimeNotExist struct { - ID int64 + ID int64 } // IsErrStopwatchNotExist checks if an error is a ErrStopwatchNotExist. @@ -813,6 +813,7 @@ func IsErrTrackedTimeNotExist(err error) bool { func (err ErrTrackedTimeNotExist) Error() string { return fmt.Sprintf("tracked time does not exist [id: %d]", err.ID) } + // .____ ___. .__ // | | _____ \_ |__ ____ | | // | | \__ \ | __ \_/ __ \| | diff --git a/models/issue_stopwatch.go b/models/issue_stopwatch.go index 3f9dcd307bbdc..4668d4d2b0a31 100644 --- a/models/issue_stopwatch.go +++ b/models/issue_stopwatch.go @@ -1,20 +1,20 @@ package models import ( - "time" - "github.com/go-xorm/xorm" "code.gitea.io/gitea/modules/log" + "github.com/go-xorm/xorm" + "time" ) // Stopwatch represents a stopwatch for time tracking. type Stopwatch struct { - ID int64 `xorm:"pk autoincr"` - IssueID int64 `xorm:"INDEX"` - Issue *Issue `xorm:"-"` - UserID int64 `xorm:"INDEX"` - User *User `xorm:"-"` - Created time.Time `xorm:"-"` - CreatedUnix int64 + ID int64 `xorm:"pk autoincr"` + IssueID int64 `xorm:"INDEX"` + Issue *Issue `xorm:"-"` + UserID int64 `xorm:"INDEX"` + User *User `xorm:"-"` + Created time.Time `xorm:"-"` + CreatedUnix int64 } // AfterSet is invoked from XORM after setting the value of a field of this object. diff --git a/models/issue_tracked_time.go b/models/issue_tracked_time.go index 75397fed349a9..32d07bae8b4ab 100644 --- a/models/issue_tracked_time.go +++ b/models/issue_tracked_time.go @@ -1,21 +1,21 @@ package models import ( - "time" - "github.com/go-xorm/xorm" "code.gitea.io/gitea/modules/log" + "github.com/go-xorm/xorm" + "time" ) // TrackedTime represents a time that was spent for a specific issue. type TrackedTime struct { - ID int64 `xorm:"pk autoincr"` - IssueID int64 `xorm:"INDEX"` - Issue *Issue `xorm:"-"` - UserID int64 `xorm:"INDEX"` - User *User `xorm:"-"` - Created time.Time `xorm:"-"` - CreatedUnix int64 - Time int64 + ID int64 `xorm:"pk autoincr"` + IssueID int64 `xorm:"INDEX"` + Issue *Issue `xorm:"-"` + UserID int64 `xorm:"INDEX"` + User *User `xorm:"-"` + Created time.Time `xorm:"-"` + CreatedUnix int64 + Time int64 } // AfterSet is invoked from XORM after setting the value of a field of this object. From b213c993072938fb1803b5434fb09ee7c3743555 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Tue, 25 Jul 2017 00:22:29 +0200 Subject: [PATCH 25/88] Adding translations and improving design See go-gitea/gitea#967 Signed-off-by: Jonas Franz --- options/locale/locale_en-US.ini | 11 ++++ .../repo/issue/view_content/comments.tmpl | 54 ++++++++++++------- .../repo/issue/view_content/sidebar.tmpl | 34 ++++++------ 3 files changed, 62 insertions(+), 37 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 03d42c2140bdb..8cd1b074102a7 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -692,6 +692,17 @@ issues.attachment.open_tab = `Click to see "%s" in a new tab` issues.attachment.download = `Click to download "%s"` issues.subscribe = Subscribe issues.unsubscribe = Unsubscribe +issues.tracker = Time tracker +issues.start_tracking_short = Start +issues.start_tracking = Start time tracking +issues.start_tracking_history = `started working %s` +issues.stop_tracking = Finish +issues.stop_tracking_history = `finished working %s` +issues.add_time = Add time manual +issues.add_time_history = `added spent time manually %s` +issues.cancel_tracking = Cancel +issues.time_spent_total = Time spent total + pulls.desc = Pulls management your code review and merge requests pulls.new = New Pull Request diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl index 20b43b96abac7..18e8fb312eb1b 100644 --- a/templates/repo/issue/view_content/comments.tmpl +++ b/templates/repo/issue/view_content/comments.tmpl @@ -1,27 +1,8 @@ {{range .Issue.Comments}} {{ $createdStr:= TimeSince .Created $.Lang }} - + {{if eq .Type 0}} -
- - - - - {{.Poster.Name}} started working 35min ago -
-
- - - - - {{.Poster.Name}} finished working 1min ago - -
- - 34min -
-
@@ -160,5 +141,38 @@ {{.Poster.Name}} {{$.i18n.Tr "repo.issues.delete_branch_at" .CommitSHA $createdStr | Safe}} + {{else if eq .Type 12}} +
+ + + + + {{.Poster.Name}} {{$.i18n.Tr "repo.issues.start_tracking_history" $createdStr | Safe}} +
+ {{else if eq .Type 13}} +
+ + + + + {{.Poster.Name}} {{$.i18n.Tr "repo.issues.stop_tracking_history" $createdStr | Safe}} + +
+ + {{.Content}} +
+
+ {{else if eq .Type 14}} +
+ + + + + {{.Poster.Name}} {{$.i18n.Tr "repo.issues.add_time_history" $createdStr | Safe}} +
+ + {{.Content}} +
+
{{end}} {{end}} diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index ad70162a5473e..39e26a8a8676a 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -123,23 +123,23 @@
- Time tracker + {{.i18n.Tr "repo.issues.tracker"}}
-
- + + {{$.CsrfTokenHtml}} - {{if $.IssueWatch.IsWatching}} -
- -
- -
+ {{if $.Stopwatch.IsRunning}} +
+ +
+ +
{{else}} -
- - -
+
+ + +
{{end}}
@@ -148,17 +148,17 @@
- Time spent total + {{.i18n.Tr "repo.issues.time_spent_total"}}
- {{range .Participants}} + {{range $user, $trackedtime := .WorkingUsers}}
- {{.DisplayName}} + {{$user.DisplayName}}
- 1h 30min + {{$trackedtime}}
From 79d5c74cc344296baa97bd67d1e0c51939e018f1 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Tue, 25 Jul 2017 02:15:33 +0200 Subject: [PATCH 26/88] Implementing stopwatch (for timetracking) Make UI functional Add hints in timeline for time tracking events See go-gitea/gitea#967 Signed-off-by: Jonas Franz --- models/issue_comment.go | 8 + models/issue_stopwatch.go | 145 +++++++++++++++--- models/issue_tracked_time.go | 25 --- models/models.go | 2 + options/locale/locale_en-US.ini | 1 + routers/repo/issue.go | 3 + routers/repo/issue_stopwatch.go | 52 +++++++ routers/routes/routes.go | 2 + .../repo/issue/view_content/comments.tmpl | 8 + .../repo/issue/view_content/sidebar.tmpl | 13 +- 10 files changed, 203 insertions(+), 56 deletions(-) create mode 100644 routers/repo/issue_stopwatch.go diff --git a/models/issue_comment.go b/models/issue_comment.go index d11ea92796862..f4080233513f5 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -52,6 +52,14 @@ const ( CommentTypeChangeTitle // Delete Branch CommentTypeDeleteBranch + // Start a stopwatch for time tracking + CommentTypeStartTracking + // Stop a stopwatch for time tracking + CommentTypeStopTracking + // Add time manual for time tracking + CommentTypeAddTimeManual + // Cancel a stopwatch for time tracking + CommentTypeCancelTracking ) // CommentTag defines comment tag type diff --git a/models/issue_stopwatch.go b/models/issue_stopwatch.go index 4668d4d2b0a31..7989ccdc3ce2a 100644 --- a/models/issue_stopwatch.go +++ b/models/issue_stopwatch.go @@ -1,47 +1,25 @@ package models import ( - "code.gitea.io/gitea/modules/log" "github.com/go-xorm/xorm" "time" + "fmt" ) // Stopwatch represents a stopwatch for time tracking. type Stopwatch struct { ID int64 `xorm:"pk autoincr"` IssueID int64 `xorm:"INDEX"` - Issue *Issue `xorm:"-"` UserID int64 `xorm:"INDEX"` - User *User `xorm:"-"` Created time.Time `xorm:"-"` CreatedUnix int64 } // AfterSet is invoked from XORM after setting the value of a field of this object. func (s *Stopwatch) AfterSet(colName string, _ xorm.Cell) { - var err error + switch colName { - case "user_id": - s.User, err = GetUserByID(s.UserID) - if err != nil { - if IsErrUserNotExist(err) { - s.UserID = -1 - s.User = nil - } else { - log.Error(3, "GetUserByID[%d]: %v", s.UserID, err) - } - } - case "issue_id": - s.Issue, err = GetIssueByID(s.IssueID) - if err != nil { - if IsErrIssueNotExist(err) { - s.IssueID = -1 - s.Issue = nil - } else { - log.Error(3, "GetIssueByID[%d]: %v", s.IssueID, err) - } - } case "created_unix": s.Created = time.Unix(s.CreatedUnix, 0).Local() } @@ -59,6 +37,125 @@ func GetStopwatchByID(id int64) (*Stopwatch, error) { return c, nil } +func getStopwatch(e Engine, userID, issueID int64) (sw *Stopwatch, exists bool, err error) { + sw = new(Stopwatch) + exists, err = e. + Where("user_id = ?", userID). + And("issue_id = ?", issueID). + Get(sw) + return +} + +func StopwatchExists(userID int64, issueID int64) bool { + _, exists, _ := getStopwatch(x, userID, issueID) + return exists +} + +func CreateOrStopIssueStopwatch(userID int64, issueID int64) error { + sw, exists, err := getStopwatch(x, userID, issueID) + if err != nil { + return err + } + + if exists { + // Create tracked time out of the time difference between start date and actual date + timediff := time.Now().Unix() - sw.CreatedUnix + + // Create TrackedTime + tt := &TrackedTime{ + Created: time.Now(), + IssueID: issueID, + UserID: userID, + Time: timediff, + } + + if _, err := x.Insert(tt); err != nil { + return err + } + // Add comment referencing to the tracked time + comment := &Comment{ + IssueID: issueID, + PosterID: userID, + Type: CommentTypeStopTracking, + Content: secToTime(timediff), + } + + if _, err := x.Insert(comment); err != nil { + return err + } + + if _, err := x.Delete(sw); err != nil { + return err + } + }else { + // Create stopwatch + sw = &Stopwatch{ + UserID: userID, + IssueID: issueID, + Created: time.Now(), + } + + if _, err := x.Insert(sw); err != nil { + return err + } + + // Add comment referencing to the stopwatch + comment := &Comment{ + IssueID: issueID, + PosterID: userID, + Type: CommentTypeStartTracking, + } + + if _, err := x.Insert(comment); err != nil { + return err + } + } + return nil +} + +func CancelStopwatch(userID int64, issueID int64) error { + sw, exists, err := getStopwatch(x, userID, issueID) + if err != nil { + return err + } + + if exists { + if _, err := x.Delete(sw); err != nil { + return err + } + comment := &Comment{ + PosterID:userID, + IssueID: issueID, + Type: CommentTypeCancelTracking, + } + + if _, err := x.Insert(comment); err != nil { + return err + } + } + return nil +} + +func secToTime(duration int64) string{ + seconds := duration%60 + minutes := (duration/(60))%60 + hours := duration/(60*60) + + var hrs string + + if hours > 0 { + hrs = fmt.Sprintf("%dh", hours) + } + if minutes > 0 { + hrs = fmt.Sprintf("%s %dmin", hrs, minutes) + } + if seconds > 0 { + hrs = fmt.Sprintf("%s %ds", hrs, seconds) + } + + return hrs +} + // BeforeInsert will be invoked by XORM before inserting a record // representing this object. func (s *Stopwatch) BeforeInsert() { diff --git a/models/issue_tracked_time.go b/models/issue_tracked_time.go index 32d07bae8b4ab..806970bfede53 100644 --- a/models/issue_tracked_time.go +++ b/models/issue_tracked_time.go @@ -1,7 +1,6 @@ package models import ( - "code.gitea.io/gitea/modules/log" "github.com/go-xorm/xorm" "time" ) @@ -10,9 +9,7 @@ import ( type TrackedTime struct { ID int64 `xorm:"pk autoincr"` IssueID int64 `xorm:"INDEX"` - Issue *Issue `xorm:"-"` UserID int64 `xorm:"INDEX"` - User *User `xorm:"-"` Created time.Time `xorm:"-"` CreatedUnix int64 Time int64 @@ -20,29 +17,7 @@ type TrackedTime struct { // AfterSet is invoked from XORM after setting the value of a field of this object. func (t *TrackedTime) AfterSet(colName string, _ xorm.Cell) { - var err error switch colName { - case "user_id": - t.User, err = GetUserByID(t.UserID) - if err != nil { - if IsErrUserNotExist(err) { - t.UserID = -1 - t.User = nil - } else { - log.Error(3, "GetUserByID[%d]: %v", t.UserID, err) - } - } - - case "issue_id": - t.Issue, err = GetIssueByID(t.IssueID) - if err != nil { - if IsErrIssueNotExist(err) { - t.IssueID = -1 - t.Issue = nil - } else { - log.Error(3, "GetIssueByID[%d]: %v", t.IssueID, err) - } - } case "created_unix": t.Created = time.Unix(t.CreatedUnix, 0).Local() } diff --git a/models/models.go b/models/models.go index 1688a636497c1..bdaeb18c17931 100644 --- a/models/models.go +++ b/models/models.go @@ -112,6 +112,8 @@ func init() { new(UserOpenID), new(IssueWatch), new(CommitStatus), + new(Stopwatch), + new(TrackedTime), ) gonicNames := []string{"SSL", "UID"} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 8cd1b074102a7..6cdadc383c027 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -701,6 +701,7 @@ issues.stop_tracking_history = `finished working %s` issues.add_time = Add time manual issues.add_time_history = `added spent time manually %s` issues.cancel_tracking = Cancel +issues.cancel_tracking_history = `cancelled time tracking %s` issues.time_spent_total = Time spent total diff --git a/routers/repo/issue.go b/routers/repo/issue.go index f3799fdb993fe..d60be63fc9c5c 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -580,6 +580,9 @@ func ViewIssue(ctx *context.Context) { participants = make([]*models.User, 1, 10) ) + // Deal with the stopwatch + ctx.Data["IsStopwatchRunning"] = models.StopwatchExists(ctx.User.ID, issue.ID) + // Render comments and and fetch participants. participants[0] = issue.Poster for _, comment = range issue.Comments { diff --git a/routers/repo/issue_stopwatch.go b/routers/repo/issue_stopwatch.go new file mode 100644 index 0000000000000..ffd148e8e25d9 --- /dev/null +++ b/routers/repo/issue_stopwatch.go @@ -0,0 +1,52 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "net/http" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/context" +) + +// IssueStopwatch manges the stopwatch +func IssueStopwatch(c *context.Context) { + + issueIndex := c.ParamsInt64("index") + issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, issueIndex) + + if err != nil { + c.Handle(http.StatusInternalServerError, "GetIssueByIndex", err) + return + } + + if err := models.CreateOrStopIssueStopwatch(c.User.ID, issue.ID); err != nil { + c.Handle(http.StatusInternalServerError, "CreateOrStopIssueStopwatch", err) + return + } + + url := issue.HTMLURL() + c.Redirect(url, http.StatusSeeOther) +} + +// CancelStopwatch cancel the stopwatch +func CancelStopwatch(c *context.Context) { + + issueIndex := c.ParamsInt64("index") + issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, issueIndex) + + if err != nil { + c.Handle(http.StatusInternalServerError, "GetIssueByIndex", err) + return + } + + if err := models.CancelStopwatch(c.User.ID, issue.ID); err != nil { + c.Handle(http.StatusInternalServerError, "CancelStopwatch", err) + return + } + + url := issue.HTMLURL() + c.Redirect(url, http.StatusSeeOther) +} diff --git a/routers/routes/routes.go b/routers/routes/routes.go index d085a0910edff..51a4a008577ac 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -466,6 +466,8 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/title", repo.UpdateIssueTitle) m.Post("/content", repo.UpdateIssueContent) m.Post("/watch", repo.IssueWatch) + m.Get("/stopwatch", repo.IssueStopwatch) + m.Get("/cancel", repo.CancelStopwatch) m.Combo("/comments").Post(bindIgnErr(auth.CreateCommentForm{}), repo.NewComment) }) diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl index 18e8fb312eb1b..34609ceb31734 100644 --- a/templates/repo/issue/view_content/comments.tmpl +++ b/templates/repo/issue/view_content/comments.tmpl @@ -174,5 +174,13 @@ {{.Content}}
+ {{else if eq .Type 15}} +
+ + + + + {{.Poster.Name}} {{$.i18n.Tr "repo.issues.cancel_tracking_history" $createdStr | Safe}} +
{{end}} {{end}} diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index 39e26a8a8676a..642b2f43765c2 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -125,23 +125,22 @@ {{.i18n.Tr "repo.issues.tracker"}}
-
- + {{$.CsrfTokenHtml}} - {{if $.Stopwatch.IsRunning}} + {{if $.IsStopwatchRunning}}
- + {{.i18n.Tr "repo.issues.stop_tracking"}}
- + {{.i18n.Tr "repo.issues.cancel_tracking"}}
{{else}}
- + {{.i18n.Tr "repo.issues.start_tracking_short"}}
{{end}} -
+
From 286dd9b76459ddf1274a9149e713782a8e82d0b4 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Tue, 25 Jul 2017 03:29:17 +0200 Subject: [PATCH 27/88] Implementing timetracking feature Adding "Add time manual" option Improved stopwatch Created report of total spent time by user See go-gitea/gitea#967 Signed-off-by: Jonas Franz --- models/error.go | 4 +- models/issue_stopwatch.go | 39 +++++++------- models/issue_tracked_time.go | 52 +++++++++++++++++++ options/locale/locale_en-US.ini | 2 + public/js/index.js | 10 ++++ routers/repo/issue.go | 4 ++ routers/repo/issue_timetrack.go | 46 ++++++++++++++++ routers/routes/routes.go | 1 + .../repo/issue/view_content/sidebar.tmpl | 21 ++++++-- 9 files changed, 156 insertions(+), 23 deletions(-) create mode 100644 routers/repo/issue_timetrack.go diff --git a/models/error.go b/models/error.go index 6e98beff86e5c..3b8b38402c6ce 100644 --- a/models/error.go +++ b/models/error.go @@ -799,12 +799,12 @@ func (err ErrStopwatchNotExist) Error() string { // |____| |__| (____ /\___ >__|_ \\___ >____ | |____| |__|__|_| /\___ > // \/ \/ \/ \/ \/ \/ \/ -// ErrStopwatchNotExist represents a "Stopwatch Not Exist" kind of error. +// ErrTrackedTimeNotExist represents a "TrackedTime Not Exist" kind of error. type ErrTrackedTimeNotExist struct { ID int64 } -// IsErrStopwatchNotExist checks if an error is a ErrStopwatchNotExist. +// IsErrTrackedTimeNotExist checks if an error is a ErrTrackedTimeNotExist. func IsErrTrackedTimeNotExist(err error) bool { _, ok := err.(ErrStopwatchNotExist) return ok diff --git a/models/issue_stopwatch.go b/models/issue_stopwatch.go index 7989ccdc3ce2a..dbc9ef9579c32 100644 --- a/models/issue_stopwatch.go +++ b/models/issue_stopwatch.go @@ -1,9 +1,9 @@ package models import ( + "fmt" "github.com/go-xorm/xorm" "time" - "fmt" ) // Stopwatch represents a stopwatch for time tracking. @@ -40,17 +40,19 @@ func GetStopwatchByID(id int64) (*Stopwatch, error) { func getStopwatch(e Engine, userID, issueID int64) (sw *Stopwatch, exists bool, err error) { sw = new(Stopwatch) exists, err = e. - Where("user_id = ?", userID). + Where("user_id = ?", userID). And("issue_id = ?", issueID). Get(sw) return } +// StopwatchExists returns true if the stopwatch exists func StopwatchExists(userID int64, issueID int64) bool { _, exists, _ := getStopwatch(x, userID, issueID) return exists } +// CreateOrStopIssueStopwatch will create or remove a stopwatch and will log it into issue's timeline. func CreateOrStopIssueStopwatch(userID int64, issueID int64) error { sw, exists, err := getStopwatch(x, userID, issueID) if err != nil { @@ -65,8 +67,8 @@ func CreateOrStopIssueStopwatch(userID int64, issueID int64) error { tt := &TrackedTime{ Created: time.Now(), IssueID: issueID, - UserID: userID, - Time: timediff, + UserID: userID, + Time: timediff, } if _, err := x.Insert(tt); err != nil { @@ -74,10 +76,10 @@ func CreateOrStopIssueStopwatch(userID int64, issueID int64) error { } // Add comment referencing to the tracked time comment := &Comment{ - IssueID: issueID, + IssueID: issueID, PosterID: userID, - Type: CommentTypeStopTracking, - Content: secToTime(timediff), + Type: CommentTypeStopTracking, + Content: secToTime(timediff), } if _, err := x.Insert(comment); err != nil { @@ -87,10 +89,10 @@ func CreateOrStopIssueStopwatch(userID int64, issueID int64) error { if _, err := x.Delete(sw); err != nil { return err } - }else { + } else { // Create stopwatch sw = &Stopwatch{ - UserID: userID, + UserID: userID, IssueID: issueID, Created: time.Now(), } @@ -101,9 +103,9 @@ func CreateOrStopIssueStopwatch(userID int64, issueID int64) error { // Add comment referencing to the stopwatch comment := &Comment{ - IssueID: issueID, + IssueID: issueID, PosterID: userID, - Type: CommentTypeStartTracking, + Type: CommentTypeStartTracking, } if _, err := x.Insert(comment); err != nil { @@ -113,6 +115,7 @@ func CreateOrStopIssueStopwatch(userID int64, issueID int64) error { return nil } +// CancelStopwatch removes the given stopwatch and logs it into issue's timeline. func CancelStopwatch(userID int64, issueID int64) error { sw, exists, err := getStopwatch(x, userID, issueID) if err != nil { @@ -124,9 +127,9 @@ func CancelStopwatch(userID int64, issueID int64) error { return err } comment := &Comment{ - PosterID:userID, - IssueID: issueID, - Type: CommentTypeCancelTracking, + PosterID: userID, + IssueID: issueID, + Type: CommentTypeCancelTracking, } if _, err := x.Insert(comment); err != nil { @@ -136,10 +139,10 @@ func CancelStopwatch(userID int64, issueID int64) error { return nil } -func secToTime(duration int64) string{ - seconds := duration%60 - minutes := (duration/(60))%60 - hours := duration/(60*60) +func secToTime(duration int64) string { + seconds := duration % 60 + minutes := (duration / (60)) % 60 + hours := duration / (60 * 60) var hrs string diff --git a/models/issue_tracked_time.go b/models/issue_tracked_time.go index 806970bfede53..dfc50be1500b8 100644 --- a/models/issue_tracked_time.go +++ b/models/issue_tracked_time.go @@ -40,3 +40,55 @@ func GetTrackedTimeByID(id int64) (*TrackedTime, error) { func (t *TrackedTime) BeforeInsert() { t.CreatedUnix = time.Now().Unix() } + +// AddTime will add the given time (in seconds) to the issue +func AddTime(userID int64, issueID int64, time int64) error { + tt := &TrackedTime{ + IssueID: issueID, + UserID: userID, + Time: time, + } + if _, err := x.Insert(tt); err != nil { + return err + } + comment := &Comment{ + IssueID: issueID, + PosterID: userID, + Type: CommentTypeAddTimeManual, + Content: secToTime(time), + } + if _, err := x.Insert(comment); err != nil { + return err + } + return nil +} + +// TotalTimes returns the spent time for each user by an issue +func TotalTimes(issueID int64) (map[*User]string, error) { + var trackedTimes []TrackedTime + if err := x. + Where("issue_id = ?", issueID). + Find(&trackedTimes); err != nil { + return nil, err + } + //Adding total time per user ID + totalTimesByUser := make(map[int64]int64) + for _, t := range trackedTimes { + if total, ok := totalTimesByUser[t.UserID]; !ok { + totalTimesByUser[t.UserID] = t.Time + } else { + totalTimesByUser[t.UserID] = total + t.Time + } + } + + totalTimes := make(map[*User]string) + //Fetching User and making time human readable + for userID, total := range totalTimesByUser { + user, err := GetUserByID(userID) + if err != nil || user == nil { + continue + } + totalTimes[user] = secToTime(total) + } + return totalTimes, nil +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 6cdadc383c027..5636df772f61a 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -699,6 +699,8 @@ issues.start_tracking_history = `started working %s` issues.stop_tracking = Finish issues.stop_tracking_history = `finished working %s` issues.add_time = Add time manual +issues.add_time_short = Add +issues.add_time_cancel = Cancel issues.add_time_history = `added spent time manually %s` issues.cancel_tracking = Cancel issues.cancel_tracking_history = `cancelled time tracking %s` diff --git a/public/js/index.js b/public/js/index.js index d79b94b92c7bc..59a9c3d1fa23e 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -1715,3 +1715,13 @@ function initDashboardSearch() { } }); } +function timeAddManual() { + $('.mini.modal') + .modal({ + duration: 200, + onApprove: function() { + $('#add_time_manual_form').submit(); + } + }).modal('show') + ; +} diff --git a/routers/repo/issue.go b/routers/repo/issue.go index d60be63fc9c5c..3fca7be3da28a 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -582,6 +582,10 @@ func ViewIssue(ctx *context.Context) { // Deal with the stopwatch ctx.Data["IsStopwatchRunning"] = models.StopwatchExists(ctx.User.ID, issue.ID) + if ctx.Data["WorkingUsers"], err = models.TotalTimes(issue.ID); err != nil { + ctx.Handle(500, "TotalTimes", err) + return + } // Render comments and and fetch participants. participants[0] = issue.Poster diff --git a/routers/repo/issue_timetrack.go b/routers/repo/issue_timetrack.go new file mode 100644 index 0000000000000..51fa85e62a153 --- /dev/null +++ b/routers/repo/issue_timetrack.go @@ -0,0 +1,46 @@ +package repo + +import ( + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/context" + "net/http" + "strconv" +) + +// AddTimeManual tracks time manually +func AddTimeManual(c *context.Context) { + hours, err := strconv.ParseInt(c.Req.PostForm.Get("hours"), 10, 64) + if err != nil { + hours = 0 + } + minutes, err := strconv.ParseInt(c.Req.PostForm.Get("minutes"), 10, 64) + if err != nil { + minutes = 0 + } + seconds, err := strconv.ParseInt(c.Req.PostForm.Get("seconds"), 10, 64) + if err != nil { + seconds = 0 + } + + totalInSeconds := seconds + minutes*60 + hours*60*60 + + if totalInSeconds <= 0 { + c.Handle(http.StatusInternalServerError, "sum of seconds <= 0", nil) + return + } + + issueIndex := c.ParamsInt64("index") + issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, issueIndex) + if err != nil { + c.Handle(http.StatusInternalServerError, "GetIssueByIndex", err) + return + } + + if err := models.AddTime(c.User.ID, issue.ID, totalInSeconds); err != nil { + c.Handle(http.StatusInternalServerError, "AddTime", err) + return + } + + url := issue.HTMLURL() + c.Redirect(url, http.StatusSeeOther) +} diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 51a4a008577ac..9ecbf0c3013b0 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -466,6 +466,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/title", repo.UpdateIssueTitle) m.Post("/content", repo.UpdateIssueContent) m.Post("/watch", repo.IssueWatch) + m.Post("/add_time", repo.AddTimeManual) m.Get("/stopwatch", repo.IssueStopwatch) m.Get("/cancel", repo.CancelStopwatch) m.Combo("/comments").Post(bindIgnErr(auth.CreateCommentForm{}), repo.NewComment) diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index 642b2f43765c2..d7880806c8d13 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -126,7 +126,6 @@ {{.i18n.Tr "repo.issues.tracker"}}
- {{$.CsrfTokenHtml}} {{if $.IsStopwatchRunning}}
@@ -137,7 +136,23 @@ {{else}}
{{.i18n.Tr "repo.issues.start_tracking_short"}} - + +
{{end}} @@ -152,7 +167,7 @@ {{range $user, $trackedtime := .WorkingUsers}}
- +
{{$user.DisplayName}} From cff92342271215de2b84898e06b050569e40e50b Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Tue, 25 Jul 2017 03:51:21 +0200 Subject: [PATCH 28/88] Only showing total time spent if theire is something to show. See go-gitea/gitea#967 Signed-off-by: Jonas Franz --- .../repo/issue/view_content/sidebar.tmpl | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index d7880806c8d13..a48f4fe1a8245 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -159,27 +159,28 @@
-
- -
- {{.i18n.Tr "repo.issues.time_spent_total"}} -
- {{range $user, $trackedtime := .WorkingUsers}} -
- - - -
- {{$user.DisplayName}} -
- {{$trackedtime}} + {{if gt (len .WorkingUsers) 0}} +
+
+ {{.i18n.Tr "repo.issues.time_spent_total"}} +
+ {{range $user, $trackedtime := .WorkingUsers}} +
+ + + +
+ {{$user.DisplayName}} +
+ {{$trackedtime}} +
-
- {{end}} + {{end}} +
-
+ {{end}} {{end}}
From 615b3dd6ac121721633ed9da5050095f301032da Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Tue, 25 Jul 2017 04:05:35 +0200 Subject: [PATCH 29/88] Adding license headers. See go-gitea/gitea#967 Signed-off-by: Jonas Franz --- models/issue_stopwatch.go | 4 ++++ models/issue_tracked_time.go | 4 ++++ routers/repo/issue_timetrack.go | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/models/issue_stopwatch.go b/models/issue_stopwatch.go index dbc9ef9579c32..5b896e584f3a3 100644 --- a/models/issue_stopwatch.go +++ b/models/issue_stopwatch.go @@ -1,3 +1,7 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + package models import ( diff --git a/models/issue_tracked_time.go b/models/issue_tracked_time.go index dfc50be1500b8..ac5b8e833f840 100644 --- a/models/issue_tracked_time.go +++ b/models/issue_tracked_time.go @@ -1,3 +1,7 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + package models import ( diff --git a/routers/repo/issue_timetrack.go b/routers/repo/issue_timetrack.go index 51fa85e62a153..aadb7a68a471a 100644 --- a/routers/repo/issue_timetrack.go +++ b/routers/repo/issue_timetrack.go @@ -1,3 +1,7 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + package repo import ( From a1dff21c92d07dde4aa941c7aaa2595298d5a89b Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Tue, 25 Jul 2017 11:35:44 +0200 Subject: [PATCH 30/88] Improved error handling for "Add Time Manual" Signed-off-by: Jonas Franz --- routers/repo/issue_timetrack.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/routers/repo/issue_timetrack.go b/routers/repo/issue_timetrack.go index aadb7a68a471a..006444dc074dd 100644 --- a/routers/repo/issue_timetrack.go +++ b/routers/repo/issue_timetrack.go @@ -15,15 +15,21 @@ import ( func AddTimeManual(c *context.Context) { hours, err := strconv.ParseInt(c.Req.PostForm.Get("hours"), 10, 64) if err != nil { - hours = 0 + if c.Req.PostForm.Get("hours") != "" { + c.Handle(http.StatusInternalServerError, "hours is not numeric", err) + } } minutes, err := strconv.ParseInt(c.Req.PostForm.Get("minutes"), 10, 64) if err != nil { - minutes = 0 + if c.Req.PostForm.Get("minutes") != "" { + c.Handle(http.StatusInternalServerError, "minutes is not numeric", err) + } } seconds, err := strconv.ParseInt(c.Req.PostForm.Get("seconds"), 10, 64) if err != nil { - seconds = 0 + if c.Req.PostForm.Get("seconds") != "" { + c.Handle(http.StatusInternalServerError, "seconds is not numeric", err) + } } totalInSeconds := seconds + minutes*60 + hours*60*60 From 251d50ee726eb5e4385025c36859aa469f404c65 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Tue, 25 Jul 2017 11:37:05 +0200 Subject: [PATCH 31/88] Improved error handling for "Add Time Manual" Signed-off-by: Jonas Franz --- routers/repo/issue_timetrack.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/routers/repo/issue_timetrack.go b/routers/repo/issue_timetrack.go index 006444dc074dd..e79d1333a7470 100644 --- a/routers/repo/issue_timetrack.go +++ b/routers/repo/issue_timetrack.go @@ -17,18 +17,21 @@ func AddTimeManual(c *context.Context) { if err != nil { if c.Req.PostForm.Get("hours") != "" { c.Handle(http.StatusInternalServerError, "hours is not numeric", err) + return } } minutes, err := strconv.ParseInt(c.Req.PostForm.Get("minutes"), 10, 64) if err != nil { if c.Req.PostForm.Get("minutes") != "" { c.Handle(http.StatusInternalServerError, "minutes is not numeric", err) + return } } seconds, err := strconv.ParseInt(c.Req.PostForm.Get("seconds"), 10, 64) if err != nil { if c.Req.PostForm.Get("seconds") != "" { c.Handle(http.StatusInternalServerError, "seconds is not numeric", err) + return } } From 3945c0e1f89081428e8b4a117812816d0096a63e Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Tue, 25 Jul 2017 12:46:37 +0200 Subject: [PATCH 32/88] Adding @sapks 's changes, refactoring Signed-off-by: Jonas Franz --- routers/repo/issue_timetrack.go | 48 ++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/routers/repo/issue_timetrack.go b/routers/repo/issue_timetrack.go index e79d1333a7470..44b946e6eb896 100644 --- a/routers/repo/issue_timetrack.go +++ b/routers/repo/issue_timetrack.go @@ -8,37 +8,38 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" "net/http" - "strconv" + "time" + "fmt" ) // AddTimeManual tracks time manually func AddTimeManual(c *context.Context) { - hours, err := strconv.ParseInt(c.Req.PostForm.Get("hours"), 10, 64) + + h, err := parseTimeTrackingWithDuration(c.Req.PostForm.Get("hours"), "h") if err != nil { - if c.Req.PostForm.Get("hours") != "" { - c.Handle(http.StatusInternalServerError, "hours is not numeric", err) - return - } + fmt.Println("hours is not numeric", err) + c.Handle(http.StatusBadRequest, "hours is not numeric", err) + return } - minutes, err := strconv.ParseInt(c.Req.PostForm.Get("minutes"), 10, 64) + + m, err := parseTimeTrackingWithDuration(c.Req.PostForm.Get("minutes"), "m") if err != nil { - if c.Req.PostForm.Get("minutes") != "" { - c.Handle(http.StatusInternalServerError, "minutes is not numeric", err) - return - } + fmt.Println("minutes is not numeric", err) + c.Handle(http.StatusBadRequest, "minutes is not numeric", err) + return } - seconds, err := strconv.ParseInt(c.Req.PostForm.Get("seconds"), 10, 64) + + s, err := parseTimeTrackingWithDuration(c.Req.PostForm.Get("seconds"), "s") if err != nil { - if c.Req.PostForm.Get("seconds") != "" { - c.Handle(http.StatusInternalServerError, "seconds is not numeric", err) - return - } + fmt.Println("seconds is not numeric", err) + c.Handle(http.StatusBadRequest, "seconds is not numeric", err) + return } - totalInSeconds := seconds + minutes*60 + hours*60*60 + totalInSeconds := h.Seconds() + m.Seconds() + s.Seconds() if totalInSeconds <= 0 { - c.Handle(http.StatusInternalServerError, "sum of seconds <= 0", nil) + c.Handle(http.StatusBadRequest, "sum of seconds <= 0", nil) return } @@ -49,7 +50,7 @@ func AddTimeManual(c *context.Context) { return } - if err := models.AddTime(c.User.ID, issue.ID, totalInSeconds); err != nil { + if err := models.AddTime(c.User.ID, issue.ID, int64(totalInSeconds)); err != nil { c.Handle(http.StatusInternalServerError, "AddTime", err) return } @@ -57,3 +58,12 @@ func AddTimeManual(c *context.Context) { url := issue.HTMLURL() c.Redirect(url, http.StatusSeeOther) } + + +func parseTimeTrackingWithDuration(value, space string) (time.Duration, error) { + if value == "" { + return 0, nil + } + return time.ParseDuration(value + space) +} + From 0347bb309a83c0431185434091c79d5a679753cc Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Tue, 25 Jul 2017 15:58:29 +0200 Subject: [PATCH 33/88] Adding API for feature tracking Adding unit test Blocks go-sdk/pull/65 See go-sdk/pull/65 Signed-off-by: Jonas Franz --- models/error.go | 2 +- models/fixtures/stopwatch.yml | 11 ++ models/fixtures/tracked_time.yml | 34 +++++ models/issue_stopwatch.go | 12 +- models/issue_stopwatch_test.go | 53 ++++++++ models/issue_tracked_time.go | 22 +++- models/issue_tracked_time_test.go | 87 +++++++++++++ public/swagger.v1.json | 108 +++++++++++++++- routers/api/v1/api.go | 7 ++ routers/api/v1/repo/issue_tracked_time.go | 117 ++++++++++++++++++ routers/repo/issue_timetrack.go | 4 +- .../sdk/gitea/issue_tracked_time.go | 62 ++++++++++ 12 files changed, 506 insertions(+), 13 deletions(-) create mode 100644 models/fixtures/stopwatch.yml create mode 100644 models/fixtures/tracked_time.yml create mode 100644 models/issue_stopwatch_test.go create mode 100644 models/issue_tracked_time_test.go create mode 100644 routers/api/v1/repo/issue_tracked_time.go create mode 100644 vendor/code.gitea.io/sdk/gitea/issue_tracked_time.go diff --git a/models/error.go b/models/error.go index 3b8b38402c6ce..ece9612b6f04d 100644 --- a/models/error.go +++ b/models/error.go @@ -806,7 +806,7 @@ type ErrTrackedTimeNotExist struct { // IsErrTrackedTimeNotExist checks if an error is a ErrTrackedTimeNotExist. func IsErrTrackedTimeNotExist(err error) bool { - _, ok := err.(ErrStopwatchNotExist) + _, ok := err.(ErrTrackedTimeNotExist) return ok } diff --git a/models/fixtures/stopwatch.yml b/models/fixtures/stopwatch.yml new file mode 100644 index 0000000000000..397a8214d4570 --- /dev/null +++ b/models/fixtures/stopwatch.yml @@ -0,0 +1,11 @@ +- + id: 1 + user_id: 1 + issue_id: 1 + created_unix: 1500988502 + +- + id: 2 + user_id: 2 + issue_id: 2 + created_unix: 1500988502 diff --git a/models/fixtures/tracked_time.yml b/models/fixtures/tracked_time.yml new file mode 100644 index 0000000000000..06a71c5ad99dd --- /dev/null +++ b/models/fixtures/tracked_time.yml @@ -0,0 +1,34 @@ +- + id: 1 + user_id: 1 + issue_id: 1 + time: 400 + created_unix: 946684800 + +- + id: 2 + user_id: 2 + issue_id: 2 + time: 3661 + created_unix: 946684801 + +- + id: 3 + user_id: 2 + issue_id: 2 + time: 1 + created_unix: 946684802 + +- + id: 4 + user_id: -1 + issue_id: 4 + time: 1 + created_unix: 946684802 + +- + id: 5 + user_id: 2 + issue_id: 5 + time: 1 + created_unix: 946684802 diff --git a/models/issue_stopwatch.go b/models/issue_stopwatch.go index 5b896e584f3a3..8586c86d8c446 100644 --- a/models/issue_stopwatch.go +++ b/models/issue_stopwatch.go @@ -154,10 +154,18 @@ func secToTime(duration int64) string { hrs = fmt.Sprintf("%dh", hours) } if minutes > 0 { - hrs = fmt.Sprintf("%s %dmin", hrs, minutes) + if hours == 0 { + hrs = fmt.Sprintf("%dmin", minutes) + }else { + hrs = fmt.Sprintf("%s %dmin", hrs, minutes) + } } if seconds > 0 { - hrs = fmt.Sprintf("%s %ds", hrs, seconds) + if hours == 0 && minutes == 0 { + hrs = fmt.Sprintf("%ds", seconds) + }else { + hrs = fmt.Sprintf("%s %ds", hrs, seconds) + } } return hrs diff --git a/models/issue_stopwatch_test.go b/models/issue_stopwatch_test.go new file mode 100644 index 0000000000000..24d791deb08cb --- /dev/null +++ b/models/issue_stopwatch_test.go @@ -0,0 +1,53 @@ +package models + +import ( + "github.com/stretchr/testify/assert" + "testing" + "time" +) + +func TestGetStopwatchByID(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + + sw, err := GetStopwatchByID(1) + assert.Equal(t, sw.CreatedUnix, int64(1500988502)) + assert.Equal(t, sw.UserID, int64(1)) + // Tue Jul 25 13:15:02 2017 UTC + assert.Equal(t, sw.Created, time.Unix(1500988502, 0)) + assert.NoError(t, err) + + sw, err = GetStopwatchByID(3) + assert.Error(t, err) + assert.Equal(t, true, IsErrStopwatchNotExist(err)) +} + +func TestCancelStopwatch(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + + err := CancelStopwatch(1, 1) + assert.NoError(t, err) + AssertNotExistsBean(t, &Stopwatch{UserID: 1, IssueID: 1}) + + _ = AssertExistsAndLoadBean(t, &Comment{Type: CommentTypeCancelTracking, PosterID: 1, IssueID: 1}) + + assert.Nil(t, CancelStopwatch(1, 2)) +} + +func TestStopwatchExists(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + + assert.True(t, StopwatchExists(1, 1)) + assert.False(t, StopwatchExists(1, 2)) +} + +func TestCreateOrStopIssueStopwatch(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + + assert.NoError(t, CreateOrStopIssueStopwatch(3, 1)) + sw := AssertExistsAndLoadBean(t, &Stopwatch{UserID: 3, IssueID: 1}).(*Stopwatch) + assert.Equal(t, true, sw.Created.Before(time.Now())) + + assert.NoError(t, CreateOrStopIssueStopwatch(2, 2)) + AssertNotExistsBean(t, &Stopwatch{UserID: 2, IssueID: 2}) + AssertExistsAndLoadBean(t, &TrackedTime{UserID: 2, IssueID: 2}) +} diff --git a/models/issue_tracked_time.go b/models/issue_tracked_time.go index ac5b8e833f840..8e66fec3cc051 100644 --- a/models/issue_tracked_time.go +++ b/models/issue_tracked_time.go @@ -11,12 +11,12 @@ import ( // TrackedTime represents a time that was spent for a specific issue. type TrackedTime struct { - ID int64 `xorm:"pk autoincr"` - IssueID int64 `xorm:"INDEX"` - UserID int64 `xorm:"INDEX"` - Created time.Time `xorm:"-"` - CreatedUnix int64 - Time int64 + ID int64 `xorm:"pk autoincr" json:"id"` + IssueID int64 `xorm:"INDEX" json:"issue_id"` + UserID int64 `xorm:"INDEX" json:"user_id"` + Created time.Time `xorm:"-" json:"created"` + CreatedUnix int64 `json:"-"` + Time int64 `json:"time"` } // AfterSet is invoked from XORM after setting the value of a field of this object. @@ -39,6 +39,16 @@ func GetTrackedTimeByID(id int64) (*TrackedTime, error) { return c, nil } +func GetTrackedTimesByIssue(issueID int64) (trackedTimes []*TrackedTime, err error) { + err = x.Where("issue_id = ?", issueID).Find(&trackedTimes) + return +} + +func GetTrackedTimesByUser(userID int64) (trackedTimes []*TrackedTime, err error) { + err = x.Where("user_id = ?", userID).Find(&trackedTimes) + return +} + // BeforeInsert will be invoked by XORM before inserting a record // representing this object. func (t *TrackedTime) BeforeInsert() { diff --git a/models/issue_tracked_time_test.go b/models/issue_tracked_time_test.go new file mode 100644 index 0000000000000..f91947f397837 --- /dev/null +++ b/models/issue_tracked_time_test.go @@ -0,0 +1,87 @@ +package models + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestAddTime(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + //3661 = 1h 1min 1s + assert.NoError(t, AddTime(3, 1, 3661)) + tt := AssertExistsAndLoadBean(t, &TrackedTime{UserID: 3, IssueID: 1}).(*TrackedTime) + assert.Equal(t, tt.Time, int64(3661)) + + comment := AssertExistsAndLoadBean(t, &Comment{Type: CommentTypeAddTimeManual, PosterID: 3, IssueID: 1}).(*Comment) + assert.Equal(t, comment.Content, "1h 1min 1s") +} + +func TestGetTrackedTimesByIssue(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + + times, err := GetTrackedTimesByIssue(1) + assert.Len(t, times, 1) + assert.Equal(t, times[0].Time, int64(400)) + assert.NoError(t, err) + + times, err = GetTrackedTimesByIssue(3) + assert.Len(t, times, 0) + assert.NoError(t, err) +} + +func TestGetTrackedTimesByUser(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + + times, err := GetTrackedTimesByUser(1) + assert.Len(t, times, 1) + assert.Equal(t, times[0].Time, int64(400)) + assert.NoError(t, err) + + times, err = GetTrackedTimesByUser(3) + assert.Len(t, times, 0) + assert.NoError(t, err) +} + +func TestGetTrackedTimeByID(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + + times, err := GetTrackedTimeByID(1) + assert.Equal(t, times.Time, int64(400)) + assert.NoError(t, err) + + times, err = GetTrackedTimeByID(-1) + assert.Error(t, err) + assert.Equal(t, true, IsErrTrackedTimeNotExist(err)) +} + +func TestTotalTimes(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + + total, err := TotalTimes(1) + assert.Len(t, total, 1) + for user, time := range total { + assert.Equal(t, int64(1), user.ID) + assert.Equal(t, "6min 40s", time) + } + assert.NoError(t, err) + + total, err = TotalTimes(2) + assert.Len(t, total, 1) + for user, time := range total { + assert.Equal(t, int64(2), user.ID) + assert.Equal(t, "1h 1min 2s", time) + } + assert.NoError(t, err) + + total, err = TotalTimes(5) + assert.Len(t, total, 1) + for user, time := range total { + assert.Equal(t, int64(2), user.ID) + assert.Equal(t, "1s", time) + } + assert.NoError(t, err) + + total, err = TotalTimes(4) + assert.Len(t, total, 0) + assert.NoError(t, err) +} diff --git a/public/swagger.v1.json b/public/swagger.v1.json index 3ce534d44f6ba..60ef4521e9d83 100644 --- a/public/swagger.v1.json +++ b/public/swagger.v1.json @@ -280,6 +280,42 @@ } } }, + "/repos/{username}/{reponame}/issues/{issue}/times": { + "get": { + "produces": [ + "application/json" + ], + "operationId": "issueTrackedTimes", + "responses": { + "200": { + "$ref": "#/responses/TrackedTimes" + }, + "404": { + "$ref": "#/responses/error" + }, + "500": { + "$ref": "#/responses/error" + } + } + }, + "post": { + "produces": [ + "application/json" + ], + "operationId": "addTime", + "responses": { + "200": { + "$ref": "#/responses/AddTimeOption" + }, + "404": { + "$ref": "#/responses/error" + }, + "500": { + "$ref": "#/responses/error" + } + } + } + }, "/repos/{username}/{reponame}/mirror-sync": { "post": { "produces": [ @@ -675,6 +711,41 @@ } } }, + "/user/times": { + "get": { + "produces": [ + "application/json" + ], + "operationId": "userTrackedTimes", + "responses": { + "200": { + "$ref": "#/responses/TrackedTimes" + }, + "500": { + "$ref": "#/responses/error" + } + } + } + }, + "/user/{username}/times": { + "get": { + "produces": [ + "application/json" + ], + "operationId": "userTrackedTimes", + "responses": { + "200": { + "$ref": "#/responses/TrackedTimes" + }, + "404": { + "$ref": "#/responses/error" + }, + "500": { + "$ref": "#/responses/error" + } + } + } + }, "/users/:username/followers": { "get": { "produces": [ @@ -1106,6 +1177,15 @@ "AccessTokenList": { "description": "AccessTokenList represents a list of API access token." }, + "AddTimeOption": { + "description": "AddTimeOption adds time manually to an issue", + "headers": { + "time": { + "type": "integer", + "format": "int64" + } + } + }, "GPGKey": { "description": "GPGKey a user GPG key to sign commit and tag in repository", "headers": { @@ -1287,6 +1367,32 @@ } } }, + "TrackedTime": { + "description": "TrackedTime worked time for an issue / pr", + "headers": { + "created": {}, + "id": { + "type": "integer", + "format": "int64" + }, + "issue_id": { + "type": "integer", + "format": "int64" + }, + "time": { + "type": "integer", + "format": "int64", + "description": "Time in seconds" + }, + "user_id": { + "type": "integer", + "format": "int64" + } + } + }, + "TrackedTimes": { + "description": "TrackedTimes represent a list of tracked times" + }, "User": { "description": "User represents a API user.", "headers": { @@ -1373,4 +1479,4 @@ } } } -} +} \ No newline at end of file diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index bac5af7be6699..d494eea1a63f5 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -288,6 +288,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("/starred", user.GetStarredRepos) m.Get("/subscriptions", user.GetWatchedRepos) + m.Get("/times", repo.ListTrackedTimesByUser) }) }, reqToken()) @@ -328,6 +329,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Delete("", user.Unstar) }, repoAssignment()) }) + m.Get("/times", repo.ListMyTrackedTimes) m.Get("/subscriptions", user.GetMyWatchedRepos) }, reqToken()) @@ -400,6 +402,11 @@ func RegisterRoutes(m *macaron.Macaron) { m.Delete("/:id", reqToken(), repo.DeleteIssueLabel) }) + m.Group("/times", func() { + m.Combo("").Get(repo.ListTrackedTimes). + Post(reqToken(), bind(api.AddTimeOption{}), repo.AddTime) + }) + }) }, mustEnableIssues) m.Group("/labels", func() { diff --git a/routers/api/v1/repo/issue_tracked_time.go b/routers/api/v1/repo/issue_tracked_time.go new file mode 100644 index 0000000000000..6d235e52d3077 --- /dev/null +++ b/routers/api/v1/repo/issue_tracked_time.go @@ -0,0 +1,117 @@ +package repo + +import ( + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/context" + api "code.gitea.io/sdk/gitea" +) + +// ListTrackedTimes list all the tracked times of an issue +func ListTrackedTimes(ctx *context.APIContext) { + // swagger:route GET /repos/{username}/{reponame}/issues/{issue}/times issueTrackedTimes + // + // Produces: + // - application/json + // + // Responses: + // 200: TrackedTimes + // 404: error + // 500: error + issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) + if err != nil { + if models.IsErrIssueNotExist(err) { + ctx.Error(404, "GetIssueByIndex", err) + } else { + ctx.Error(500, "GetIssueByIndex", err) + } + return + } + + if trackedTimes, err := models.GetTrackedTimesByIssue(issue.ID); err != nil { + ctx.Error(500, "GetTrackedTimesByIssue", err) + } else { + ctx.JSON(200, &trackedTimes) + } +} + +// AddTime adds time manual to the given issue +func AddTime(ctx *context.APIContext, form api.AddTimeOption) { + // swagger:route Post /repos/{username}/{reponame}/issues/{issue}/times addTime + // + // Produces: + // - application/json + // + // Responses: + // 200: AddTimeOption + // 404: error + // 500: error + issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) + if err != nil { + if models.IsErrIssueNotExist(err) { + ctx.Error(404, "GetIssueByIndex", err) + } else { + ctx.Error(500, "GetIssueByIndex", err) + } + return + } + + if err := models.AddTime(ctx.User.ID, issue.ID, form.Time); err != nil { + ctx.Error(500, "AddTime", err) + return + } + ctx.JSON(200, form) +} + +// ListTrackedTimesByUser lists all tracked times of the user +func ListTrackedTimesByUser(ctx *context.APIContext) { + // swagger:route GET /user/{username}/times userTrackedTimes + // + // Produces: + // - application/json + // + // Responses: + // 200: TrackedTimes + // 404: error + // 500: error + user := GetUserByParamsName(ctx, ctx.Params(":username")) + if user == nil { + return + } + + if trackedTimes, err := models.GetTrackedTimesByUser(user.ID); err != nil { + ctx.Error(500, "GetTrackedTimesByUser", err) + } else { + ctx.JSON(200, &trackedTimes) + } +} + +// ListTrackedTimesByUser lists all tracked times of the current user +func ListMyTrackedTimes(ctx *context.APIContext) { + // swagger:route GET /user/times userTrackedTimes + // + // Produces: + // - application/json + // + // Responses: + // 200: TrackedTimes + // 500: error + if trackedTimes, err := models.GetTrackedTimesByUser(ctx.User.ID); err != nil { + ctx.Error(500, "GetTrackedTimesByUser", err) + } else { + ctx.JSON(200, &trackedTimes) + } +} + +// GetUserByParamsName get user by name +func GetUserByParamsName(ctx *context.APIContext, name string) *models.User { + user, err := models.GetUserByName(ctx.Params(name)) + if err != nil { + if models.IsErrUserNotExist(err) { + ctx.Error(404, "GetUserByName", err) + } else { + ctx.Error(500, "GetUserByName", err) + } + return nil + } + return user +} diff --git a/routers/repo/issue_timetrack.go b/routers/repo/issue_timetrack.go index 44b946e6eb896..f7688484de507 100644 --- a/routers/repo/issue_timetrack.go +++ b/routers/repo/issue_timetrack.go @@ -7,9 +7,9 @@ package repo import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" + "fmt" "net/http" "time" - "fmt" ) // AddTimeManual tracks time manually @@ -59,11 +59,9 @@ func AddTimeManual(c *context.Context) { c.Redirect(url, http.StatusSeeOther) } - func parseTimeTrackingWithDuration(value, space string) (time.Duration, error) { if value == "" { return 0, nil } return time.ParseDuration(value + space) } - diff --git a/vendor/code.gitea.io/sdk/gitea/issue_tracked_time.go b/vendor/code.gitea.io/sdk/gitea/issue_tracked_time.go new file mode 100644 index 0000000000000..f24c5ab6dc3b2 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/issue_tracked_time.go @@ -0,0 +1,62 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "time" +) + +// TrackedTime worked time for an issue / pr +// swagger:response TrackedTime +type TrackedTime struct { + ID int64 `json:"id"` + Created time.Time `json:"created"` + // Time in seconds + Time int64 `json:"time"` + UserID int64 `json:"user_id"` + IssueID int64 `json:"issue_id"` +} + +// TrackedTimes represent a list of tracked times +// swagger:response TrackedTimes +type TrackedTimes []*TrackedTime + +// GetUserTrackedTimes list tracked times of a user +func (c *Client) GetUserTrackedTimes(user string) ([]*TrackedTime, error) { + times := make([]*TrackedTime, 0, 10) + return times, c.getParsedResponse("GET", fmt.Sprintf("/users/%s/times", user), nil, nil, ×) +} + +// GetMyTrackedTimes list tracked times of the current user user +func (c *Client) GetMyTrackedTimes() ([]*TrackedTime, error) { + times := make([]*TrackedTime, 0, 10) + return times, c.getParsedResponse("GET", "/user/times", nil, nil, ×) +} + +// AddTimeOption adds time manually to an issue +// swagger:response AddTimeOption +type AddTimeOption struct { + Time int64 `json:"time" binding:"Required"` +} + +// AddTime adds time to issue with the given index +func (c *Client) AddTime(owner, repo string, index int64, opt AddTimeOption) (*TrackedTime, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + t := new(TrackedTime) + return t, c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/times", owner, repo, index), + jsonHeader, bytes.NewReader(body), t) +} + +// ListTrackedTimes get tracked times of one issue via issue id +func (c *Client) ListTrackedTimes(owner, repo string, index int64) ([]*TrackedTime, error) { + times := make([]*TrackedTime, 0, 5) + return times, c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/times", owner, repo, index), nil, nil, ×) +} From c1943c19e51cdcae1a2160bc8c2facc5084fa0ff Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Tue, 25 Jul 2017 16:35:46 +0200 Subject: [PATCH 34/88] Adding DISABLE/ENABLE option to Repository settings page (+gofmt) Signed-off-by: Jonas Franz --- conf/app.ini | 5 ++++- models/issue_stopwatch.go | 4 ++-- models/repo_unit.go | 2 +- models/unit.go | 3 +++ modules/auth/repo_form.go | 3 ++- modules/context/repo.go | 5 +++-- options/locale/locale_en-US.ini | 2 ++ routers/repo/setting.go | 9 +++++++++ templates/repo/issue/view_content/sidebar.tmpl | 4 ++-- templates/repo/settings/options.tmpl | 12 ++++++++++++ 10 files changed, 40 insertions(+), 9 deletions(-) diff --git a/conf/app.ini b/conf/app.ini index bb0654e2e27d3..595112e277199 100644 --- a/conf/app.ini +++ b/conf/app.ini @@ -156,7 +156,7 @@ LFS_START_SERVER = false ; Where your lfs files put on, default is data/lfs. LFS_CONTENT_PATH = data/lfs ; LFS authentication secret, changed this to yourself. -LFS_JWT_SECRET = +LFS_JWT_SECRET = ; Define allowed algorithms and their minimum key length (use -1 to disable a type) [ssh.minimum_key_sizes] @@ -261,6 +261,9 @@ DEFAULT_KEEP_EMAIL_PRIVATE = false ; Default value for AllowCreateOrganization ; New user will have rights set to create organizations depending on this setting DEFAULT_ALLOW_CREATE_ORGANIZATION = true +; Default value for EnableTimetracking +; Repositories will use timetracking by default depending on this setting +DEFAULT_ENABLE_TIMETRACKING = true ; Default value for the domain part of the user's email address in the git log ; if he has set KeepEmailPrivate true. The user's email replaced with a ; concatenation of the user name in lower case, "@" and NO_REPLY_ADDRESS. diff --git a/models/issue_stopwatch.go b/models/issue_stopwatch.go index 8586c86d8c446..87af797be934e 100644 --- a/models/issue_stopwatch.go +++ b/models/issue_stopwatch.go @@ -156,14 +156,14 @@ func secToTime(duration int64) string { if minutes > 0 { if hours == 0 { hrs = fmt.Sprintf("%dmin", minutes) - }else { + } else { hrs = fmt.Sprintf("%s %dmin", hrs, minutes) } } if seconds > 0 { if hours == 0 && minutes == 0 { hrs = fmt.Sprintf("%ds", seconds) - }else { + } else { hrs = fmt.Sprintf("%s %ds", hrs, seconds) } } diff --git a/models/repo_unit.go b/models/repo_unit.go index 2256ff7ef60aa..a51bd0e8aa374 100644 --- a/models/repo_unit.go +++ b/models/repo_unit.go @@ -76,7 +76,7 @@ func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) { case "type": switch UnitType(Cell2Int64(val)) { case UnitTypeCode, UnitTypeIssues, UnitTypePullRequests, UnitTypeReleases, - UnitTypeWiki: + UnitTypeWiki, UnitTypeTimeTracker: r.Config = new(UnitConfig) case UnitTypeExternalWiki: r.Config = new(ExternalWikiConfig) diff --git a/models/unit.go b/models/unit.go index a14edcec0c1e5..cc4a441c850ac 100644 --- a/models/unit.go +++ b/models/unit.go @@ -16,6 +16,7 @@ const ( UnitTypeWiki // 5 Wiki UnitTypeExternalWiki // 6 ExternalWiki UnitTypeExternalTracker // 7 ExternalTracker + UnitTypeTimeTracker // 8 Time tracker ) var ( @@ -28,6 +29,7 @@ var ( UnitTypeWiki, UnitTypeExternalWiki, UnitTypeExternalTracker, + UnitTypeTimeTracker, } // defaultRepoUnits contains the default unit types @@ -37,6 +39,7 @@ var ( UnitTypePullRequests, UnitTypeReleases, UnitTypeWiki, + UnitTypeTimeTracker, } // MustRepoUnits contains the units could not be disabled currently diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go index 58dcf468ef6ed..61d220215b7f2 100644 --- a/modules/auth/repo_form.go +++ b/modules/auth/repo_form.go @@ -12,7 +12,7 @@ import ( "code.gitea.io/gitea/models" "github.com/Unknwon/com" "github.com/go-macaron/binding" - macaron "gopkg.in/macaron.v1" + "gopkg.in/macaron.v1" ) // _______________________________________ _________.______________________ _______________.___. @@ -104,6 +104,7 @@ type RepoSettingForm struct { TrackerURLFormat string TrackerIssueStyle string EnablePulls bool + EnableTimetracker bool } // Validate validates the fields diff --git a/modules/context/repo.go b/modules/context/repo.go index 3dface7da77e4..37ca636449787 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -14,8 +14,8 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/setting" "github.com/Unknwon/com" - editorconfig "gopkg.in/editorconfig/editorconfig-core-go.v1" - macaron "gopkg.in/macaron.v1" + "gopkg.in/editorconfig/editorconfig-core-go.v1" + "gopkg.in/macaron.v1" ) // PullRequest contains informations to make a pull request @@ -556,5 +556,6 @@ func UnitTypes() macaron.Handler { ctx.Data["UnitTypeWiki"] = models.UnitTypeWiki ctx.Data["UnitTypeExternalWiki"] = models.UnitTypeExternalWiki ctx.Data["UnitTypeExternalTracker"] = models.UnitTypeExternalTracker + ctx.Data["UnitTypeTimeTracker"] = models.UnitTypeTimeTracker } } diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 5636df772f61a..950649e15176f 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -820,6 +820,8 @@ settings.tracker_issue_style = External Issue Tracker Naming Style: settings.tracker_issue_style.numeric = Numeric settings.tracker_issue_style.alphanumeric = Alphanumeric settings.tracker_url_format_desc = You can use placeholder {user} {repo} {index} for user name, repository name and issue index. +settings.timetracker = Time tracker +settings.enable_timetracker = Enable builtin time tracker settings.pulls_desc = Enable pull requests to accept public contributions settings.danger_zone = Danger Zone settings.new_owner_has_same_repo = The new owner already has a repository with same name. Please choose another name. diff --git a/routers/repo/setting.go b/routers/repo/setting.go index a50c6a6e22704..e957e13867bb5 100644 --- a/routers/repo/setting.go +++ b/routers/repo/setting.go @@ -215,6 +215,15 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { }) } + if form.EnableTimetracker { + units = append(units, models.RepoUnit{ + RepoID: repo.ID, + Type: models.UnitTypeTimeTracker, + Index: int(models.UnitTypeTimeTracker), + Config: new(models.UnitConfig), + }) + } + if err := models.UpdateRepositoryUnits(repo, units); err != nil { ctx.Handle(500, "UpdateRepositoryUnits", err) return diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index a48f4fe1a8245..10b2ee3cfeb67 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -120,9 +120,9 @@
+ {{if .Repository.EnableUnit $.UnitTypeTimeTracker}}
- {{.i18n.Tr "repo.issues.tracker"}}
@@ -130,7 +130,6 @@ {{if $.IsStopwatchRunning}} {{else}} @@ -182,5 +181,6 @@
{{end}} {{end}} + {{end}}
diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index 8ad09d4306d78..a636a31db55cf 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -187,6 +187,18 @@
{{end}} + {{if $isIssuesEnabled}} +
+ +
+ +
+ + +
+
+ {{end}} +
From edf850d0888fddeac56ca8e27a4f045aa01a3d72 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Tue, 25 Jul 2017 16:49:40 +0200 Subject: [PATCH 35/88] Improving translations + adding German translation Applying @sapk 's changes (+gofmt) Signed-off-by: Jonas Franz --- models/unit.go | 2 +- options/locale/locale_de-DE.ini | 15 +++++++++++++++ options/locale/locale_en-US.ini | 2 +- routers/repo/issue_timetrack.go | 10 +++------- routers/repo/setting.go | 4 ++-- 5 files changed, 22 insertions(+), 11 deletions(-) diff --git a/models/unit.go b/models/unit.go index cc4a441c850ac..3e22c69c9194f 100644 --- a/models/unit.go +++ b/models/unit.go @@ -16,7 +16,7 @@ const ( UnitTypeWiki // 5 Wiki UnitTypeExternalWiki // 6 ExternalWiki UnitTypeExternalTracker // 7 ExternalTracker - UnitTypeTimeTracker // 8 Time tracker + UnitTypeTimeTracker // 8 Time tracker ) var ( diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index 937f1a8cebe30..36391d0c4f7a9 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -688,6 +688,19 @@ issues.attachment.open_tab=`Klicken um "%s" in einem neuen Tab zu öffnen` issues.attachment.download=`Klicken um "%s" herunterzuladen` issues.subscribe=Abonnieren issues.unsubscribe=Abbestellen +issues.tracker = Zeiterfassung +issues.start_tracking_short = Start +issues.start_tracking = Starte Zeiterfassung +issues.start_tracking_history = `begann zu arbeiten %s` +issues.stop_tracking = Beenden +issues.stop_tracking_history = `stoppte zu arbeiten %s` +issues.add_time = Zeit manuell hinzufügen +issues.add_time_short = Hinzufügen +issues.add_time_cancel = Abbrechen +issues.add_time_history = `fügte Arbeitszeit hinzu %s` +issues.cancel_tracking = Abbrechen +issues.cancel_tracking_history = `brach die Zeiterfassung ab %s` +issues.time_spent_total = Erfasste Zeiten pulls.new=Neuer Pull-Request pulls.compare_changes=Änderungen vergleichen @@ -799,6 +812,8 @@ settings.tracker_issue_style=Namenskonvention des externen Issue-Trackers: settings.tracker_issue_style.numeric=Numerisch settings.tracker_issue_style.alphanumeric=Alphanumerisch settings.tracker_url_format_desc=Sie können die Platzhalter {user} {repo} {index} für den Benutzernamen, den Namen des Repositories und die Issue-Nummer verwenden. +settings.timetracker = Zeiterfassung +settings.enable_timetracker = Zeiterfassung verwenden settings.pulls_desc=Pull-Requests aktivieren, um öffentliche Mitwirkung zu ermöglichen settings.danger_zone=Gefahrenzone settings.new_owner_has_same_repo=Der neue Eigentümer hat bereits ein Repository mit dem gleichen Namen. Bitte wähle einen anderen Namen. diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 950649e15176f..3896cdb2f71d4 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -701,7 +701,7 @@ issues.stop_tracking_history = `finished working %s` issues.add_time = Add time manual issues.add_time_short = Add issues.add_time_cancel = Cancel -issues.add_time_history = `added spent time manually %s` +issues.add_time_history = `added spent time %s` issues.cancel_tracking = Cancel issues.cancel_tracking_history = `cancelled time tracking %s` issues.time_spent_total = Time spent total diff --git a/routers/repo/issue_timetrack.go b/routers/repo/issue_timetrack.go index f7688484de507..1b3bae46f5dcc 100644 --- a/routers/repo/issue_timetrack.go +++ b/routers/repo/issue_timetrack.go @@ -7,7 +7,6 @@ package repo import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" - "fmt" "net/http" "time" ) @@ -17,28 +16,25 @@ func AddTimeManual(c *context.Context) { h, err := parseTimeTrackingWithDuration(c.Req.PostForm.Get("hours"), "h") if err != nil { - fmt.Println("hours is not numeric", err) c.Handle(http.StatusBadRequest, "hours is not numeric", err) return } m, err := parseTimeTrackingWithDuration(c.Req.PostForm.Get("minutes"), "m") if err != nil { - fmt.Println("minutes is not numeric", err) c.Handle(http.StatusBadRequest, "minutes is not numeric", err) return } s, err := parseTimeTrackingWithDuration(c.Req.PostForm.Get("seconds"), "s") if err != nil { - fmt.Println("seconds is not numeric", err) c.Handle(http.StatusBadRequest, "seconds is not numeric", err) return } - totalInSeconds := h.Seconds() + m.Seconds() + s.Seconds() + total := h + m + s - if totalInSeconds <= 0 { + if total <= 0 { c.Handle(http.StatusBadRequest, "sum of seconds <= 0", nil) return } @@ -50,7 +46,7 @@ func AddTimeManual(c *context.Context) { return } - if err := models.AddTime(c.User.ID, issue.ID, int64(totalInSeconds)); err != nil { + if err := models.AddTime(c.User.ID, issue.ID, int64(total.Seconds())); err != nil { c.Handle(http.StatusInternalServerError, "AddTime", err) return } diff --git a/routers/repo/setting.go b/routers/repo/setting.go index e957e13867bb5..843a7a60371ab 100644 --- a/routers/repo/setting.go +++ b/routers/repo/setting.go @@ -218,8 +218,8 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { if form.EnableTimetracker { units = append(units, models.RepoUnit{ RepoID: repo.ID, - Type: models.UnitTypeTimeTracker, - Index: int(models.UnitTypeTimeTracker), + Type: models.UnitTypeTimeTracker, + Index: int(models.UnitTypeTimeTracker), Config: new(models.UnitConfig), }) } From 9bd4821ab3088211a334e31fa9a66a65a0385ff1 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Tue, 25 Jul 2017 18:27:25 +0200 Subject: [PATCH 36/88] Removing repo_unit and using IssuesSetting for disabling/enabling timetracker Adding DEFAULT_ENABLE_TIMETRACKER to config, installation and admin menu Improving documentation Signed-off-by: Jonas Franz --- models/issue_tracked_time.go | 2 ++ models/repo.go | 24 +++++++++----- models/repo_unit.go | 31 ++++++++++++++----- models/unit.go | 3 -- modules/auth/user_form.go | 1 + modules/context/repo.go | 1 - modules/setting/setting.go | 4 ++- options/locale/locale_de-DE.ini | 3 ++ options/locale/locale_en-US.ini | 8 +++-- public/js/index.js | 4 +++ routers/api/v1/repo/issue_tracked_time.go | 2 +- routers/install.go | 2 ++ routers/repo/repo.go | 3 ++ routers/repo/setting.go | 13 ++------ templates/admin/config.tmpl | 2 ++ templates/install.tmpl | 6 ++++ .../repo/issue/view_content/sidebar.tmpl | 8 ++--- templates/repo/settings/options.tmpl | 24 ++++++-------- .../sdk/gitea/issue_tracked_time.go | 2 +- 19 files changed, 92 insertions(+), 51 deletions(-) diff --git a/models/issue_tracked_time.go b/models/issue_tracked_time.go index 8e66fec3cc051..530c685c76999 100644 --- a/models/issue_tracked_time.go +++ b/models/issue_tracked_time.go @@ -39,11 +39,13 @@ func GetTrackedTimeByID(id int64) (*TrackedTime, error) { return c, nil } +// GetTrackedTimesByIssue will return all tracked times that are part of the issue func GetTrackedTimesByIssue(issueID int64) (trackedTimes []*TrackedTime, err error) { err = x.Where("issue_id = ?", issueID).Find(&trackedTimes) return } +// GetTrackedTimesByUser will return all tracked times which are created by the user func GetTrackedTimesByUser(userID int64) (trackedTimes []*TrackedTime, err error) { err = x.Where("user_id = ?", userID).Find(&trackedTimes) return diff --git a/models/repo.go b/models/repo.go index a651aae89b1af..ae765cf197b03 100644 --- a/models/repo.go +++ b/models/repo.go @@ -31,8 +31,8 @@ import ( "github.com/Unknwon/cae/zip" "github.com/Unknwon/com" "github.com/go-xorm/xorm" - version "github.com/mcuadros/go-version" - ini "gopkg.in/ini.v1" + "github.com/mcuadros/go-version" + "gopkg.in/ini.v1" ) const ( @@ -1221,11 +1221,21 @@ func createRepository(e *xorm.Session, u *User, repo *Repository) (err error) { // insert units for repo var units = make([]RepoUnit, 0, len(defaultRepoUnits)) for i, tp := range defaultRepoUnits { - units = append(units, RepoUnit{ - RepoID: repo.ID, - Type: tp, - Index: i, - }) + if tp == UnitTypeIssues { + units = append(units, RepoUnit{ + RepoID: repo.ID, + Type: tp, + Index: i, + Config: &IssuesConfig{EnableTimetracker: setting.Service.DefaultEnableTimetracking}, + }) + } else { + units = append(units, RepoUnit{ + RepoID: repo.ID, + Type: tp, + Index: i, + }) + } + } if _, err = e.Insert(&units); err != nil { diff --git a/models/repo_unit.go b/models/repo_unit.go index a51bd0e8aa374..5e7e451b34dc2 100644 --- a/models/repo_unit.go +++ b/models/repo_unit.go @@ -70,18 +70,35 @@ func (cfg *ExternalTrackerConfig) ToDB() ([]byte, error) { return json.Marshal(cfg) } +// IssuesConfig describes issues config +type IssuesConfig struct { + EnableTimetracker bool +} + +// FromDB fills up a IssuesConfig from serialized format. +func (cfg *IssuesConfig) FromDB(bs []byte) error { + return json.Unmarshal(bs, &cfg) +} + +// ToDB exports a IssuesConfig to a serialized format. +func (cfg *IssuesConfig) ToDB() ([]byte, error) { + return json.Marshal(cfg) +} + // BeforeSet is invoked from XORM before setting the value of a field of this object. func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) { switch colName { case "type": switch UnitType(Cell2Int64(val)) { - case UnitTypeCode, UnitTypeIssues, UnitTypePullRequests, UnitTypeReleases, - UnitTypeWiki, UnitTypeTimeTracker: + case UnitTypeCode, UnitTypePullRequests, UnitTypeReleases, + UnitTypeWiki: r.Config = new(UnitConfig) case UnitTypeExternalWiki: r.Config = new(ExternalWikiConfig) case UnitTypeExternalTracker: r.Config = new(ExternalTrackerConfig) + case UnitTypeIssues: + r.Config = new(IssuesConfig) default: panic("unrecognized repo unit type: " + com.ToStr(*val)) } @@ -106,11 +123,6 @@ func (r *RepoUnit) CodeConfig() *UnitConfig { return r.Config.(*UnitConfig) } -// IssuesConfig returns config for UnitTypeIssues -func (r *RepoUnit) IssuesConfig() *UnitConfig { - return r.Config.(*UnitConfig) -} - // PullRequestsConfig returns config for UnitTypePullRequests func (r *RepoUnit) PullRequestsConfig() *UnitConfig { return r.Config.(*UnitConfig) @@ -126,6 +138,11 @@ func (r *RepoUnit) ExternalWikiConfig() *ExternalWikiConfig { return r.Config.(*ExternalWikiConfig) } +// IssuesConfig returns config for UnitTypeIssues +func (r *RepoUnit) IssuesConfig() *IssuesConfig { + return r.Config.(*IssuesConfig) +} + // ExternalTrackerConfig returns config for UnitTypeExternalTracker func (r *RepoUnit) ExternalTrackerConfig() *ExternalTrackerConfig { return r.Config.(*ExternalTrackerConfig) diff --git a/models/unit.go b/models/unit.go index 3e22c69c9194f..a14edcec0c1e5 100644 --- a/models/unit.go +++ b/models/unit.go @@ -16,7 +16,6 @@ const ( UnitTypeWiki // 5 Wiki UnitTypeExternalWiki // 6 ExternalWiki UnitTypeExternalTracker // 7 ExternalTracker - UnitTypeTimeTracker // 8 Time tracker ) var ( @@ -29,7 +28,6 @@ var ( UnitTypeWiki, UnitTypeExternalWiki, UnitTypeExternalTracker, - UnitTypeTimeTracker, } // defaultRepoUnits contains the default unit types @@ -39,7 +37,6 @@ var ( UnitTypePullRequests, UnitTypeReleases, UnitTypeWiki, - UnitTypeTimeTracker, } // MustRepoUnits contains the units could not be disabled currently diff --git a/modules/auth/user_form.go b/modules/auth/user_form.go index 4f7b80f59037d..4dbb90cdb44a4 100644 --- a/modules/auth/user_form.go +++ b/modules/auth/user_form.go @@ -46,6 +46,7 @@ type InstallForm struct { RequireSignInView bool DefaultKeepEmailPrivate bool DefaultAllowCreateOrganization bool + DefaultEnableTimetracking bool NoReplyAddress string AdminName string `binding:"OmitEmpty;AlphaDashDot;MaxSize(30)" locale:"install.admin_name"` diff --git a/modules/context/repo.go b/modules/context/repo.go index 37ca636449787..595fd73cc87ed 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -556,6 +556,5 @@ func UnitTypes() macaron.Handler { ctx.Data["UnitTypeWiki"] = models.UnitTypeWiki ctx.Data["UnitTypeExternalWiki"] = models.UnitTypeExternalWiki ctx.Data["UnitTypeExternalTracker"] = models.UnitTypeExternalTracker - ctx.Data["UnitTypeTimeTracker"] = models.UnitTypeTimeTracker } } diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 82187e81be7ea..db6b8dab57926 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -34,7 +34,7 @@ import ( "github.com/go-macaron/session" _ "github.com/go-macaron/session/redis" // redis plugin for store session "github.com/go-xorm/core" - ini "gopkg.in/ini.v1" + "gopkg.in/ini.v1" "strk.kbt.io/projects/go/libravatar" ) @@ -1010,6 +1010,7 @@ var Service struct { EnableCaptcha bool DefaultKeepEmailPrivate bool DefaultAllowCreateOrganization bool + DefaultEnableTimetracking bool NoReplyAddress string // OpenID settings @@ -1031,6 +1032,7 @@ func newService() { Service.EnableCaptcha = sec.Key("ENABLE_CAPTCHA").MustBool() Service.DefaultKeepEmailPrivate = sec.Key("DEFAULT_KEEP_EMAIL_PRIVATE").MustBool() Service.DefaultAllowCreateOrganization = sec.Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").MustBool(true) + Service.DefaultEnableTimetracking = sec.Key("DEFAULT_ENABLE_TIMETRACKING").MustBool(true) Service.NoReplyAddress = sec.Key("NO_REPLY_ADDRESS").MustString("noreply.example.org") sec = Cfg.Section("openid") diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index 36391d0c4f7a9..183550927427a 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -698,6 +698,9 @@ issues.add_time = Zeit manuell hinzufügen issues.add_time_short = Hinzufügen issues.add_time_cancel = Abbrechen issues.add_time_history = `fügte Arbeitszeit hinzu %s` +issues.add_time_hours = Stunden +issues.add_time_minutes = Minuten +issues.add_time_seconds = Sekunden issues.cancel_tracking = Abbrechen issues.cancel_tracking_history = `brach die Zeiterfassung ab %s` issues.time_spent_total = Erfasste Zeiten diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 3896cdb2f71d4..5669757b739f2 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -134,6 +134,8 @@ default_keep_email_private = Default Value for Keep Email Private default_keep_email_private_popup = This is the default value for the visibility of the user's email address. If set to true the email address of all new users will be hidden until the user changes his setting. default_allow_create_organization = Default permission value for new users to create organizations default_allow_create_organization_popup = This is default permission value that will be assigned for new users. If set to true new users will be allowed to create Organizations. +default_enable_timetracking = Enable timetracking by default +default_enable_timetracking_popup = Repositories will use timetracking by default depending on this setting. no_reply_address = No-reply Address no_reply_address_helper = Domain for the user's email address in git logs if he keeps his email address private. E.g. user 'joe' and 'noreply.example.org' will be 'joe@noreply.example.org' @@ -702,11 +704,13 @@ issues.add_time = Add time manual issues.add_time_short = Add issues.add_time_cancel = Cancel issues.add_time_history = `added spent time %s` +issues.add_time_hours = Hours +issues.add_time_minutes = Minutes +issues.add_time_seconds = Seconds issues.cancel_tracking = Cancel issues.cancel_tracking_history = `cancelled time tracking %s` issues.time_spent_total = Time spent total - pulls.desc = Pulls management your code review and merge requests pulls.new = New Pull Request pulls.compare_changes = Compare Changes @@ -820,7 +824,6 @@ settings.tracker_issue_style = External Issue Tracker Naming Style: settings.tracker_issue_style.numeric = Numeric settings.tracker_issue_style.alphanumeric = Alphanumeric settings.tracker_url_format_desc = You can use placeholder {user} {repo} {index} for user name, repository name and issue index. -settings.timetracker = Time tracker settings.enable_timetracker = Enable builtin time tracker settings.pulls_desc = Enable pull requests to accept public contributions settings.danger_zone = Danger Zone @@ -1307,6 +1310,7 @@ config.active_code_lives = Active Code Lives config.reset_password_code_lives = Reset Password Code Expiry Time config.default_keep_email_private = Default Value for Keep Email Private config.default_allow_create_organization = Default permission to create organizations +config.default_enable_timetracking = Enable timetracking by default config.no_reply_address = No-reply Address config.webhook_config = Webhook Configuration diff --git a/public/js/index.js b/public/js/index.js index 59a9c3d1fa23e..4c774bbae3b6a 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -374,15 +374,19 @@ function initRepository() { $('.enable-system').change(function () { if (this.checked) { $($(this).data('target')).removeClass('disabled'); + if ($(this).data('context') != undefined) $($(this).data('context')).addClass('disabled'); } else { $($(this).data('target')).addClass('disabled'); + if ($(this).data('context') != undefined) $($(this).data('context')).removeClass('disabled'); } }); $('.enable-system-radio').change(function () { if (this.value == 'false') { $($(this).data('target')).addClass('disabled'); + if ($(this).data('context') != undefined) $($(this).data('context')).removeClass('disabled'); } else if (this.value == 'true') { $($(this).data('target')).removeClass('disabled'); + if ($(this).data('context') != undefined) $($(this).data('context')).addClass('disabled'); } }); } diff --git a/routers/api/v1/repo/issue_tracked_time.go b/routers/api/v1/repo/issue_tracked_time.go index 6d235e52d3077..f0575f9bab021 100644 --- a/routers/api/v1/repo/issue_tracked_time.go +++ b/routers/api/v1/repo/issue_tracked_time.go @@ -85,7 +85,7 @@ func ListTrackedTimesByUser(ctx *context.APIContext) { } } -// ListTrackedTimesByUser lists all tracked times of the current user +// ListMyTrackedTimes lists all tracked times of the current user func ListMyTrackedTimes(ctx *context.APIContext) { // swagger:route GET /user/times userTrackedTimes // diff --git a/routers/install.go b/routers/install.go index 3d051bd3769e2..a6554ac6c6e45 100644 --- a/routers/install.go +++ b/routers/install.go @@ -113,6 +113,7 @@ func Install(ctx *context.Context) { form.RequireSignInView = setting.Service.RequireSignInView form.DefaultKeepEmailPrivate = setting.Service.DefaultKeepEmailPrivate form.DefaultAllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization + form.DefaultEnableTimetracking = setting.Service.DefaultEnableTimetracking form.NoReplyAddress = setting.Service.NoReplyAddress auth.AssignForm(form, ctx.Data) @@ -297,6 +298,7 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) { cfg.Section("service").Key("REQUIRE_SIGNIN_VIEW").SetValue(com.ToStr(form.RequireSignInView)) cfg.Section("service").Key("DEFAULT_KEEP_EMAIL_PRIVATE").SetValue(com.ToStr(form.DefaultKeepEmailPrivate)) cfg.Section("service").Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").SetValue(com.ToStr(form.DefaultAllowCreateOrganization)) + cfg.Section("service").Key("DEFAULT_ENABLE_TIMETRACKING").SetValue(com.ToStr(form.DefaultEnableTimetracking)) cfg.Section("service").Key("NO_REPLY_ADDRESS").SetValue(com.ToStr(form.NoReplyAddress)) cfg.Section("").Key("RUN_MODE").SetValue("prod") diff --git a/routers/repo/repo.go b/routers/repo/repo.go index 5fcbb84b9a608..297a131c997b3 100644 --- a/routers/repo/repo.go +++ b/routers/repo/repo.go @@ -147,6 +147,9 @@ func CreatePost(ctx *context.Context, form auth.CreateRepoForm) { log.Error(4, "DeleteRepository: %v", errDelete) } } + if unit := repo.Units[models.UnitTypeIssues]; unit != nil { + unit.IssuesConfig().EnableTimetracker = setting.Service.DefaultEnableTimetracking + } handleCreateError(ctx, ctxUser, err, "CreatePost", tplCreate, &form) } diff --git a/routers/repo/setting.go b/routers/repo/setting.go index 843a7a60371ab..9d3fdf257982a 100644 --- a/routers/repo/setting.go +++ b/routers/repo/setting.go @@ -201,7 +201,9 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { RepoID: repo.ID, Type: models.UnitTypeIssues, Index: int(models.UnitTypeIssues), - Config: new(models.UnitConfig), + Config: &models.IssuesConfig{ + EnableTimetracker: form.EnableTimetracker, + }, }) } } @@ -215,15 +217,6 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { }) } - if form.EnableTimetracker { - units = append(units, models.RepoUnit{ - RepoID: repo.ID, - Type: models.UnitTypeTimeTracker, - Index: int(models.UnitTypeTimeTracker), - Config: new(models.UnitConfig), - }) - } - if err := models.UpdateRepositoryUnits(repo, units); err != nil { ctx.Handle(500, "UpdateRepositoryUnits", err) return diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl index f9038e1425a5e..08209cd03a99b 100644 --- a/templates/admin/config.tmpl +++ b/templates/admin/config.tmpl @@ -132,6 +132,8 @@
{{.i18n.Tr "admin.config.default_allow_create_organization"}}
+
{{.i18n.Tr "admin.config.default_enable_timetracking"}}
+
{{.i18n.Tr "admin.config.no_reply_address"}}
{{if .Service.NoReplyAddress}}{{.Service.NoReplyAddress}}{{else}}-{{end}}
diff --git a/templates/install.tmpl b/templates/install.tmpl index ede1d4399f382..3e54dd09f9f3f 100644 --- a/templates/install.tmpl +++ b/templates/install.tmpl @@ -218,6 +218,12 @@
+
+
+ + +
+
diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index 10b2ee3cfeb67..aabbf1eb9973d 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -120,7 +120,7 @@
- {{if .Repository.EnableUnit $.UnitTypeTimeTracker}} + {{if (.Repository.MustGetUnit $.UnitTypeIssues).IssuesConfig.EnableTimetracker}}
{{.i18n.Tr "repo.issues.tracker"}} @@ -141,9 +141,9 @@
{{$.CsrfTokenHtml}} - - - + + +
diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index a636a31db55cf..7a1c0b58edfe6 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -134,13 +134,21 @@
- +
+
+
+
+ + +
+
+
- +
@@ -187,18 +195,6 @@
{{end}} - {{if $isIssuesEnabled}} -
- -
- -
- - -
-
- {{end}} -
diff --git a/vendor/code.gitea.io/sdk/gitea/issue_tracked_time.go b/vendor/code.gitea.io/sdk/gitea/issue_tracked_time.go index f24c5ab6dc3b2..d3b30728b236d 100644 --- a/vendor/code.gitea.io/sdk/gitea/issue_tracked_time.go +++ b/vendor/code.gitea.io/sdk/gitea/issue_tracked_time.go @@ -32,7 +32,7 @@ func (c *Client) GetUserTrackedTimes(user string) ([]*TrackedTime, error) { return times, c.getParsedResponse("GET", fmt.Sprintf("/users/%s/times", user), nil, nil, ×) } -// GetMyTrackedTimes list tracked times of the current user user +// GetMyTrackedTimes list tracked times of the current user func (c *Client) GetMyTrackedTimes() ([]*TrackedTime, error) { times := make([]*TrackedTime, 0, 10) return times, c.getParsedResponse("GET", "/user/times", nil, nil, ×) From 867598681255e3e62a55072996090ec312a8366b Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Wed, 26 Jul 2017 11:01:34 +0200 Subject: [PATCH 37/88] Fixing vendor/ folder Signed-off-by: Jonas Franz --- vendor/code.gitea.io/sdk/gitea/status.go | 2 +- vendor/code.gitea.io/sdk/gitea/user_app.go | 2 +- vendor/code.gitea.io/sdk/gitea/user_gpgkey.go | 2 +- vendor/vendor.json | 14 +++++++++++--- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/vendor/code.gitea.io/sdk/gitea/status.go b/vendor/code.gitea.io/sdk/gitea/status.go index e694add1c397c..d5cdcd57b71c5 100644 --- a/vendor/code.gitea.io/sdk/gitea/status.go +++ b/vendor/code.gitea.io/sdk/gitea/status.go @@ -21,7 +21,7 @@ const ( // StatusSuccess is for when the Status is Success StatusSuccess StatusState = "success" // StatusError is for when the Status is Error - StatusError StatusState = "error" + StatusError StatusState = "error" // StatusFailure is for when the Status is Failure StatusFailure StatusState = "failure" // StatusWarning is for when the Status is Warning diff --git a/vendor/code.gitea.io/sdk/gitea/user_app.go b/vendor/code.gitea.io/sdk/gitea/user_app.go index 82d2a40462258..08e98513ee07f 100644 --- a/vendor/code.gitea.io/sdk/gitea/user_app.go +++ b/vendor/code.gitea.io/sdk/gitea/user_app.go @@ -26,7 +26,7 @@ type AccessToken struct { // AccessTokenList represents a list of API access token. // swagger:response AccessTokenList -type AccessTokenList []*AccessToken +type AccessTokenList []*AccessToken // ListAccessTokens lista all the access tokens of user func (c *Client) ListAccessTokens(user, pass string) ([]*AccessToken, error) { diff --git a/vendor/code.gitea.io/sdk/gitea/user_gpgkey.go b/vendor/code.gitea.io/sdk/gitea/user_gpgkey.go index 942478b9f1c2c..87dd749e6cee3 100644 --- a/vendor/code.gitea.io/sdk/gitea/user_gpgkey.go +++ b/vendor/code.gitea.io/sdk/gitea/user_gpgkey.go @@ -32,7 +32,7 @@ type GPGKey struct { Expires time.Time `json:"expires_at,omitempty"` } -// GPGKeyEmail a email attache to a GPGKey +// GPGKeyEmail an email attached to a GPGKey // swagger:model GPGKeyEmail type GPGKeyEmail struct { Email string `json:"email"` diff --git a/vendor/vendor.json b/vendor/vendor.json index 9d6a3ac314bbe..24a9204f487c4 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -9,10 +9,18 @@ "revisionTime": "2017-07-10T08:37:35Z" }, { - "checksumSHA1": "nLhT+bLMj8uLICP+EZbrdoQe6mM=", + "checksumSHA1": "h9l4H9NAK15odGqlHN9p9lo1EBk=", + "origin": "github.com/JonasFranzDEV/go-sdk", + "path": "code.gitea.io/sdk", + "revision": "b1438f6e34d19a033ed50438886a614b2fcbc344", + "revisionTime": "2017-07-26T08:54:01Z" + }, + { + "checksumSHA1": "cjUHgFv9CrADhoh2YnMgONkGkzM=", + "origin": "github.com/JonasFranzDEV/go-sdk/gitea", "path": "code.gitea.io/sdk/gitea", - "revision": "8cff72208aa458f4efa8fdfbad29b03aee485b8c", - "revisionTime": "2017-05-06T01:37:21Z" + "revision": "b1438f6e34d19a033ed50438886a614b2fcbc344", + "revisionTime": "2017-07-26T08:54:01Z" }, { "checksumSHA1": "bOODD4Gbw3GfcuQPU2dI40crxxk=", From 47d31877c810fe97fe1247d5d43b5f524b746a75 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Wed, 26 Jul 2017 11:10:48 +0200 Subject: [PATCH 38/88] Changing timtracking routes by adding subgroups /times and /times/stopwatch (Proposed by @lafriks ) Signed-off-by: Jonas Franz --- routers/routes/routes.go | 11 ++++++++--- templates/repo/issue/view_content/sidebar.tmpl | 8 ++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 9ecbf0c3013b0..22de9da35be56 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -466,10 +466,15 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/title", repo.UpdateIssueTitle) m.Post("/content", repo.UpdateIssueContent) m.Post("/watch", repo.IssueWatch) - m.Post("/add_time", repo.AddTimeManual) - m.Get("/stopwatch", repo.IssueStopwatch) - m.Get("/cancel", repo.CancelStopwatch) m.Combo("/comments").Post(bindIgnErr(auth.CreateCommentForm{}), repo.NewComment) + m.Group("/times", func() { + m.Post("/add", repo.AddTimeManual) + m.Group("/stopwatch", func() { + m.Get("/toggle", repo.IssueStopwatch) + m.Get("/cancel", repo.CancelStopwatch) + }) + + }) }) m.Post("/labels", repo.UpdateIssueLabel, reqRepoWriter) diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index aabbf1eb9973d..8dd365707070c 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -129,17 +129,17 @@ {{if $.IsStopwatchRunning}} {{else}}
- {{.i18n.Tr "repo.issues.start_tracking_short"}} + {{.i18n.Tr "repo.issues.start_tracking_short"}} - {{if (.Repository.MustGetUnit $.UnitTypeIssues).IssuesConfig.EnableTimetracker}} + {{if (.Repository.MustGetUnit $.UnitTypeIssues).IssuesConfig.EnableTimetracker }} + {{if .CanUseTimetracking }}
{{.i18n.Tr "repo.issues.tracker"}} @@ -157,6 +158,7 @@
+ {{end}} {{if gt (len .WorkingUsers) 0}}
diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index 7a1c0b58edfe6..38caba4314e29 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -141,10 +141,17 @@
- +
+
+
+ + + +
+
From 871a6eb8463bf3cfde606637b40542c51b530b32 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Wed, 26 Jul 2017 12:02:46 +0200 Subject: [PATCH 40/88] Restricting write access to timetracking based on the repo settings (Proposed by @lafriks ) +gofmt / documentation Signed-off-by: Jonas Franz --- models/repo_unit.go | 2 +- modules/auth/repo_form.go | 20 ++++++++++---------- modules/context/repo.go | 2 +- routers/api/v1/repo/issue_tracked_time.go | 2 +- routers/repo/issue_stopwatch.go | 1 - routers/repo/setting.go | 2 +- 6 files changed, 14 insertions(+), 15 deletions(-) diff --git a/models/repo_unit.go b/models/repo_unit.go index 362c143219378..1553fa771d1c0 100644 --- a/models/repo_unit.go +++ b/models/repo_unit.go @@ -72,7 +72,7 @@ func (cfg *ExternalTrackerConfig) ToDB() ([]byte, error) { // IssuesConfig describes issues config type IssuesConfig struct { - EnableTimetracker bool + EnableTimetracker bool AllowOnlyContributorsToTrackTime bool } diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go index 100dd48d63466..e2dee22e06a10 100644 --- a/modules/auth/repo_form.go +++ b/modules/auth/repo_form.go @@ -95,16 +95,16 @@ type RepoSettingForm struct { EnablePrune bool // Advanced settings - EnableWiki bool - EnableExternalWiki bool - ExternalWikiURL string - EnableIssues bool - EnableExternalTracker bool - ExternalTrackerURL string - TrackerURLFormat string - TrackerIssueStyle string - EnablePulls bool - EnableTimetracker bool + EnableWiki bool + EnableExternalWiki bool + ExternalWikiURL string + EnableIssues bool + EnableExternalTracker bool + ExternalTrackerURL string + TrackerURLFormat string + TrackerIssueStyle string + EnablePulls bool + EnableTimetracker bool AllowOnlyContributorsToTrackTime bool } diff --git a/modules/context/repo.go b/modules/context/repo.go index 891289f68d8a3..b008eae2eba71 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -500,7 +500,7 @@ func RequireRepoWriter() macaron.Handler { } } -// RequireRepoWriter returns a macaron middleware for requiring repository write permission +// RequireTimetrackingWriter returns a macaron middleware for requiring timetracking write permission func RequireTimetrackingWriter() macaron.Handler { return func(ctx *Context) { if !ctx.IsSigned || diff --git a/routers/api/v1/repo/issue_tracked_time.go b/routers/api/v1/repo/issue_tracked_time.go index b694ea8d3fc96..3f0dc835e88cd 100644 --- a/routers/api/v1/repo/issue_tracked_time.go +++ b/routers/api/v1/repo/issue_tracked_time.go @@ -46,7 +46,7 @@ func AddTime(ctx *context.APIContext, form api.AddTimeOption) { // 403: error // 404: error // 500: error - if !ctx.Repo.IsWriter() && ctx.Repo.Repository.MustGetUnit(models.UnitTypeIssues).IssuesConfig().AllowOnlyContributorsToTrackTime{ + if !ctx.Repo.IsWriter() && ctx.Repo.Repository.MustGetUnit(models.UnitTypeIssues).IssuesConfig().AllowOnlyContributorsToTrackTime { ctx.Status(403) return } diff --git a/routers/repo/issue_stopwatch.go b/routers/repo/issue_stopwatch.go index 5cb90711df7fe..ffd148e8e25d9 100644 --- a/routers/repo/issue_stopwatch.go +++ b/routers/repo/issue_stopwatch.go @@ -14,7 +14,6 @@ import ( // IssueStopwatch manges the stopwatch func IssueStopwatch(c *context.Context) { - issueIndex := c.ParamsInt64("index") issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, issueIndex) diff --git a/routers/repo/setting.go b/routers/repo/setting.go index 9dcb36025452a..ff20462a7247c 100644 --- a/routers/repo/setting.go +++ b/routers/repo/setting.go @@ -202,7 +202,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { Type: models.UnitTypeIssues, Index: int(models.UnitTypeIssues), Config: &models.IssuesConfig{ - EnableTimetracker: form.EnableTimetracker, + EnableTimetracker: form.EnableTimetracker, AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime, }, }) From 15faf72a2be828ebdd1c1213e3a038e15f0999ba Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Wed, 26 Jul 2017 13:13:10 +0200 Subject: [PATCH 41/88] Fixed minor permissions bug. (Proposed by @lafriks ) +gofmt / documentation Signed-off-by: Jonas Franz --- modules/context/repo.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/context/repo.go b/modules/context/repo.go index b008eae2eba71..20a5c6f5f6248 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -504,8 +504,8 @@ func RequireRepoWriter() macaron.Handler { func RequireTimetrackingWriter() macaron.Handler { return func(ctx *Context) { if !ctx.IsSigned || - ((!ctx.Repo.IsWriter() && !ctx.User.IsAdmin) || - !ctx.Repo.Repository.MustGetUnit(models.UnitTypeIssues).IssuesConfig().AllowOnlyContributorsToTrackTime) || + (!ctx.Repo.IsWriter() && !ctx.User.IsAdmin && + ctx.Repo.Repository.MustGetUnit(models.UnitTypeIssues).IssuesConfig().AllowOnlyContributorsToTrackTime) || !ctx.Repo.Repository.MustGetUnit(models.UnitTypeIssues).IssuesConfig().EnableTimetracker { ctx.Handle(404, ctx.Req.RequestURI, nil) return From 906b4c9589dc76e06e057328f02f227cecd3d806 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Wed, 26 Jul 2017 15:02:25 +0200 Subject: [PATCH 42/88] Removed German translations Signed-off-by: Jonas Franz --- options/locale/locale_de-DE.ini | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index 183550927427a..937f1a8cebe30 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -688,22 +688,6 @@ issues.attachment.open_tab=`Klicken um "%s" in einem neuen Tab zu öffnen` issues.attachment.download=`Klicken um "%s" herunterzuladen` issues.subscribe=Abonnieren issues.unsubscribe=Abbestellen -issues.tracker = Zeiterfassung -issues.start_tracking_short = Start -issues.start_tracking = Starte Zeiterfassung -issues.start_tracking_history = `begann zu arbeiten %s` -issues.stop_tracking = Beenden -issues.stop_tracking_history = `stoppte zu arbeiten %s` -issues.add_time = Zeit manuell hinzufügen -issues.add_time_short = Hinzufügen -issues.add_time_cancel = Abbrechen -issues.add_time_history = `fügte Arbeitszeit hinzu %s` -issues.add_time_hours = Stunden -issues.add_time_minutes = Minuten -issues.add_time_seconds = Sekunden -issues.cancel_tracking = Abbrechen -issues.cancel_tracking_history = `brach die Zeiterfassung ab %s` -issues.time_spent_total = Erfasste Zeiten pulls.new=Neuer Pull-Request pulls.compare_changes=Änderungen vergleichen @@ -815,8 +799,6 @@ settings.tracker_issue_style=Namenskonvention des externen Issue-Trackers: settings.tracker_issue_style.numeric=Numerisch settings.tracker_issue_style.alphanumeric=Alphanumerisch settings.tracker_url_format_desc=Sie können die Platzhalter {user} {repo} {index} für den Benutzernamen, den Namen des Repositories und die Issue-Nummer verwenden. -settings.timetracker = Zeiterfassung -settings.enable_timetracker = Zeiterfassung verwenden settings.pulls_desc=Pull-Requests aktivieren, um öffentliche Mitwirkung zu ermöglichen settings.danger_zone=Gefahrenzone settings.new_owner_has_same_repo=Der neue Eigentümer hat bereits ein Repository mit dem gleichen Namen. Bitte wähle einen anderen Namen. From 08b0e63f7abfeb227c330aae1a5e3869e3c3b34f Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Wed, 26 Jul 2017 21:44:54 +0200 Subject: [PATCH 43/88] Adding CanUseTimetracker and IsTimetrackerEnabled in ctx.Repo Allow assignees and authors to track there time too. Signed-off-by: Jonas Franz --- modules/context/repo.go | 31 +++++++++++-------- routers/api/v1/repo/issue_tracked_time.go | 14 ++++++--- routers/repo/issue.go | 6 ++-- routers/repo/setting.go | 1 + routers/routes/routes.go | 8 +++-- .../repo/issue/view_content/sidebar.tmpl | 4 +-- templates/repo/settings/options.tmpl | 4 +-- 7 files changed, 43 insertions(+), 25 deletions(-) diff --git a/modules/context/repo.go b/modules/context/repo.go index 20a5c6f5f6248..40cfbd40f825b 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -85,6 +85,24 @@ func (r *Repository) CanCommitToBranch() (bool, error) { return r.CanEnableEditor() && !protectedBranch, nil } +// IsTimetrackerEnabled returns whether or not the timetracker is enabled. It also returns false if an error occurs. +func (r *Repository) IsTimetrackerEnabled() bool{ + if unit, err := r.Repository.GetUnit(models.UnitTypeIssues); err != nil { + return false + }else { + return unit.IssuesConfig().EnableTimetracker + } +} + +// CanUseTimetracker returns whether or not a user can use the timetracker. +func (r *Repository) CanUseTimetracker(issue *models.Issue, user *models.User) bool { + // Checking for following: + // 1. Is timetracker enabled + // 2. Is the user a contributor, admin, poster or assignee and do the repository policies require this? + return r.IsTimetrackerEnabled() && ((!r.IsWriter() && !r.IsAdmin() && !issue.IsPoster(user.ID) && issue.AssigneeID != user.ID && + r.Repository.MustGetUnit(models.UnitTypeIssues).IssuesConfig().AllowOnlyContributorsToTrackTime)) +} + // GetEditorconfig returns the .editorconfig definition if found in the // HEAD of the default repo branch. func (r *Repository) GetEditorconfig() (*editorconfig.Editorconfig, error) { @@ -500,19 +518,6 @@ func RequireRepoWriter() macaron.Handler { } } -// RequireTimetrackingWriter returns a macaron middleware for requiring timetracking write permission -func RequireTimetrackingWriter() macaron.Handler { - return func(ctx *Context) { - if !ctx.IsSigned || - (!ctx.Repo.IsWriter() && !ctx.User.IsAdmin && - ctx.Repo.Repository.MustGetUnit(models.UnitTypeIssues).IssuesConfig().AllowOnlyContributorsToTrackTime) || - !ctx.Repo.Repository.MustGetUnit(models.UnitTypeIssues).IssuesConfig().EnableTimetracker { - ctx.Handle(404, ctx.Req.RequestURI, nil) - return - } - } -} - // LoadRepoUnits loads repsitory's units, it should be called after repository and user loaded func LoadRepoUnits() macaron.Handler { return func(ctx *Context) { diff --git a/routers/api/v1/repo/issue_tracked_time.go b/routers/api/v1/repo/issue_tracked_time.go index 3f0dc835e88cd..45b798ce075d9 100644 --- a/routers/api/v1/repo/issue_tracked_time.go +++ b/routers/api/v1/repo/issue_tracked_time.go @@ -17,6 +17,10 @@ func ListTrackedTimes(ctx *context.APIContext) { // 200: TrackedTimes // 404: error // 500: error + if !ctx.Repo.IsTimetrackerEnabled() { + ctx.Error(404, "IsTimetrackerEnabled", "Timetracker is diabled") + return + } issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) if err != nil { if models.IsErrIssueNotExist(err) { @@ -46,10 +50,6 @@ func AddTime(ctx *context.APIContext, form api.AddTimeOption) { // 403: error // 404: error // 500: error - if !ctx.Repo.IsWriter() && ctx.Repo.Repository.MustGetUnit(models.UnitTypeIssues).IssuesConfig().AllowOnlyContributorsToTrackTime { - ctx.Status(403) - return - } issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) if err != nil { if models.IsErrIssueNotExist(err) { @@ -59,6 +59,12 @@ func AddTime(ctx *context.APIContext, form api.AddTimeOption) { } return } + + if !ctx.Repo.CanUseTimetracker(issue, ctx.User) { + ctx.Status(403) + return + } + if err := models.AddTime(ctx.User.ID, issue.ID, form.Time); err != nil { ctx.Error(500, "AddTime", err) diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 1c62380885490..e101ad78813fe 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -586,7 +586,8 @@ func ViewIssue(ctx *context.Context) { ctx.Handle(500, "TotalTimes", err) return } - ctx.Data["CanUseTimetracking"] = !repo.MustGetUnit(models.UnitTypeIssues).IssuesConfig().AllowOnlyContributorsToTrackTime || ctx.Repo.IsWriter() + ctx.Data["IsTimetrackerEnabled"] = ctx.Repo.IsTimetrackerEnabled() + ctx.Data["CanUseTimetracker"] = ctx.Repo.CanUseTimetracker() // Render comments and and fetch participants. participants[0] = issue.Poster @@ -680,7 +681,8 @@ func ViewIssue(ctx *context.Context) { ctx.HTML(200, tplIssueView) } -func getActionIssue(ctx *context.Context) *models.Issue { +// GetActionIssue will return the issue which is used in the context. +func GetActionIssue(ctx *context.Context) *models.Issue { issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) if err != nil { if models.IsErrIssueNotExist(err) { diff --git a/routers/repo/setting.go b/routers/repo/setting.go index ff20462a7247c..2b04ac621b89d 100644 --- a/routers/repo/setting.go +++ b/routers/repo/setting.go @@ -31,6 +31,7 @@ const ( func Settings(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.settings") ctx.Data["PageIsSettingsOptions"] = true + ctx.Data["IsTimetrackerEnabled"] = ctx.Repo.IsTimetrackerEnabled() ctx.HTML(200, tplSettingsOptions) } diff --git a/routers/routes/routes.go b/routers/routes/routes.go index f7204b4e4813d..93c37bf9f6a9c 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -335,7 +335,6 @@ func RegisterRoutes(m *macaron.Macaron) { reqRepoAdmin := context.RequireRepoAdmin() reqRepoWriter := context.RequireRepoWriter() - reqTimetrackingWriter := context.RequireTimetrackingWriter() // ***** START: Organization ***** m.Group("/org", func() { @@ -473,7 +472,12 @@ func RegisterRoutes(m *macaron.Macaron) { m.Group("/stopwatch", func() { m.Get("/toggle", repo.IssueStopwatch) m.Get("/cancel", repo.CancelStopwatch) - }, reqTimetrackingWriter) + }, func(ctx *context.Context) { + if !ctx.Repo.CanUseTimetracker(repo.GetActionIssue(ctx), ctx.User) { + ctx.Handle(404, ctx.Req.RequestURI, nil) + return + } + }) }) }) diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index 2d0a994224371..438356549d15d 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -120,8 +120,8 @@
- {{if (.Repository.MustGetUnit $.UnitTypeIssues).IssuesConfig.EnableTimetracker }} - {{if .CanUseTimetracking }} + {{if (.IsTimetrackerEnabled }} + {{if .CanUseTimetracker }}
{{.i18n.Tr "repo.issues.tracker"}} diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index 38caba4314e29..f0f67b3b42eab 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -141,11 +141,11 @@
- +
-
+
From 3b25d8e22836b5937dfc8bb54da598dd671958e6 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Wed, 26 Jul 2017 21:55:49 +0200 Subject: [PATCH 44/88] Adding CanUseTimetracker and IsTimetrackerEnabled in ctx.Repo Allow assignees and authors to track there time too. Fixed some build-time-errors + logical errors. + gofmt Signed-off-by: Jonas Franz --- modules/context/repo.go | 8 ++++---- routers/api/v1/repo/issue_tracked_time.go | 3 +-- routers/repo/issue.go | 6 +++--- templates/repo/issue/view_content/sidebar.tmpl | 2 +- templates/repo/settings/options.tmpl | 2 +- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/modules/context/repo.go b/modules/context/repo.go index 40cfbd40f825b..6df9d3b9a0b14 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -86,10 +86,10 @@ func (r *Repository) CanCommitToBranch() (bool, error) { } // IsTimetrackerEnabled returns whether or not the timetracker is enabled. It also returns false if an error occurs. -func (r *Repository) IsTimetrackerEnabled() bool{ +func (r *Repository) IsTimetrackerEnabled() bool { if unit, err := r.Repository.GetUnit(models.UnitTypeIssues); err != nil { return false - }else { + } else { return unit.IssuesConfig().EnableTimetracker } } @@ -99,8 +99,8 @@ func (r *Repository) CanUseTimetracker(issue *models.Issue, user *models.User) b // Checking for following: // 1. Is timetracker enabled // 2. Is the user a contributor, admin, poster or assignee and do the repository policies require this? - return r.IsTimetrackerEnabled() && ((!r.IsWriter() && !r.IsAdmin() && !issue.IsPoster(user.ID) && issue.AssigneeID != user.ID && - r.Repository.MustGetUnit(models.UnitTypeIssues).IssuesConfig().AllowOnlyContributorsToTrackTime)) + return r.IsTimetrackerEnabled() && (r.IsWriter() || r.IsAdmin() || issue.IsPoster(user.ID) || issue.AssigneeID == user.ID || + !r.Repository.MustGetUnit(models.UnitTypeIssues).IssuesConfig().AllowOnlyContributorsToTrackTime) } // GetEditorconfig returns the .editorconfig definition if found in the diff --git a/routers/api/v1/repo/issue_tracked_time.go b/routers/api/v1/repo/issue_tracked_time.go index 45b798ce075d9..c85dfaeeed539 100644 --- a/routers/api/v1/repo/issue_tracked_time.go +++ b/routers/api/v1/repo/issue_tracked_time.go @@ -59,13 +59,12 @@ func AddTime(ctx *context.APIContext, form api.AddTimeOption) { } return } - + if !ctx.Repo.CanUseTimetracker(issue, ctx.User) { ctx.Status(403) return } - if err := models.AddTime(ctx.User.ID, issue.ID, form.Time); err != nil { ctx.Error(500, "AddTime", err) return diff --git a/routers/repo/issue.go b/routers/repo/issue.go index e101ad78813fe..da57ad2396082 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -587,7 +587,7 @@ func ViewIssue(ctx *context.Context) { return } ctx.Data["IsTimetrackerEnabled"] = ctx.Repo.IsTimetrackerEnabled() - ctx.Data["CanUseTimetracker"] = ctx.Repo.CanUseTimetracker() + ctx.Data["CanUseTimetracker"] = ctx.Repo.CanUseTimetracker(issue, ctx.User) // Render comments and and fetch participants. participants[0] = issue.Poster @@ -719,7 +719,7 @@ func getActionIssues(ctx *context.Context) []*models.Issue { // UpdateIssueTitle change issue's title func UpdateIssueTitle(ctx *context.Context) { - issue := getActionIssue(ctx) + issue := GetActionIssue(ctx) if ctx.Written() { return } @@ -747,7 +747,7 @@ func UpdateIssueTitle(ctx *context.Context) { // UpdateIssueContent change issue's content func UpdateIssueContent(ctx *context.Context) { - issue := getActionIssue(ctx) + issue := GetActionIssue(ctx) if ctx.Written() { return } diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index 438356549d15d..9db9b371aa3a3 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -120,7 +120,7 @@
- {{if (.IsTimetrackerEnabled }} + {{if .IsTimetrackerEnabled }} {{if .CanUseTimetracker }}
diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index f0f67b3b42eab..2a42fb78a38c9 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -141,7 +141,7 @@
- +
From b1dfaa7bb09c6d2f2bb9a77e49cd000a2421020a Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Wed, 26 Jul 2017 22:00:35 +0200 Subject: [PATCH 45/88] Removing unused Get...ByID functions Signed-off-by: Jonas Franz --- models/issue_stopwatch.go | 12 ------------ models/issue_stopwatch_test.go | 15 --------------- models/issue_tracked_time.go | 12 ------------ models/issue_tracked_time_test.go | 12 ------------ modules/context/repo.go | 12 +++++++----- 5 files changed, 7 insertions(+), 56 deletions(-) diff --git a/models/issue_stopwatch.go b/models/issue_stopwatch.go index 87af797be934e..6b0fe1dc350b9 100644 --- a/models/issue_stopwatch.go +++ b/models/issue_stopwatch.go @@ -29,18 +29,6 @@ func (s *Stopwatch) AfterSet(colName string, _ xorm.Cell) { } } -// GetStopwatchByID returns the stopwatch by given ID. -func GetStopwatchByID(id int64) (*Stopwatch, error) { - c := new(Stopwatch) - has, err := x.Id(id).Get(c) - if err != nil { - return nil, err - } else if !has { - return nil, ErrStopwatchNotExist{id} - } - return c, nil -} - func getStopwatch(e Engine, userID, issueID int64) (sw *Stopwatch, exists bool, err error) { sw = new(Stopwatch) exists, err = e. diff --git a/models/issue_stopwatch_test.go b/models/issue_stopwatch_test.go index 24d791deb08cb..089d288eaee5c 100644 --- a/models/issue_stopwatch_test.go +++ b/models/issue_stopwatch_test.go @@ -6,21 +6,6 @@ import ( "time" ) -func TestGetStopwatchByID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - - sw, err := GetStopwatchByID(1) - assert.Equal(t, sw.CreatedUnix, int64(1500988502)) - assert.Equal(t, sw.UserID, int64(1)) - // Tue Jul 25 13:15:02 2017 UTC - assert.Equal(t, sw.Created, time.Unix(1500988502, 0)) - assert.NoError(t, err) - - sw, err = GetStopwatchByID(3) - assert.Error(t, err) - assert.Equal(t, true, IsErrStopwatchNotExist(err)) -} - func TestCancelStopwatch(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) diff --git a/models/issue_tracked_time.go b/models/issue_tracked_time.go index 530c685c76999..dfad02e9a60e6 100644 --- a/models/issue_tracked_time.go +++ b/models/issue_tracked_time.go @@ -27,18 +27,6 @@ func (t *TrackedTime) AfterSet(colName string, _ xorm.Cell) { } } -// GetTrackedTimeByID returns the tracked time by given ID. -func GetTrackedTimeByID(id int64) (*TrackedTime, error) { - c := new(TrackedTime) - has, err := x.Id(id).Get(c) - if err != nil { - return nil, err - } else if !has { - return nil, ErrTrackedTimeNotExist{id} - } - return c, nil -} - // GetTrackedTimesByIssue will return all tracked times that are part of the issue func GetTrackedTimesByIssue(issueID int64) (trackedTimes []*TrackedTime, err error) { err = x.Where("issue_id = ?", issueID).Find(&trackedTimes) diff --git a/models/issue_tracked_time_test.go b/models/issue_tracked_time_test.go index f91947f397837..191939cdcfd07 100644 --- a/models/issue_tracked_time_test.go +++ b/models/issue_tracked_time_test.go @@ -42,18 +42,6 @@ func TestGetTrackedTimesByUser(t *testing.T) { assert.NoError(t, err) } -func TestGetTrackedTimeByID(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - - times, err := GetTrackedTimeByID(1) - assert.Equal(t, times.Time, int64(400)) - assert.NoError(t, err) - - times, err = GetTrackedTimeByID(-1) - assert.Error(t, err) - assert.Equal(t, true, IsErrTrackedTimeNotExist(err)) -} - func TestTotalTimes(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) diff --git a/modules/context/repo.go b/modules/context/repo.go index 6df9d3b9a0b14..ab3bdfacf2b4a 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -87,11 +87,13 @@ func (r *Repository) CanCommitToBranch() (bool, error) { // IsTimetrackerEnabled returns whether or not the timetracker is enabled. It also returns false if an error occurs. func (r *Repository) IsTimetrackerEnabled() bool { - if unit, err := r.Repository.GetUnit(models.UnitTypeIssues); err != nil { + var u *models.RepoUnit + var err error + if u, err = r.Repository.GetUnit(models.UnitTypeIssues); err != nil { return false - } else { - return unit.IssuesConfig().EnableTimetracker } + return u.IssuesConfig().EnableTimetracker + } // CanUseTimetracker returns whether or not a user can use the timetracker. @@ -99,8 +101,8 @@ func (r *Repository) CanUseTimetracker(issue *models.Issue, user *models.User) b // Checking for following: // 1. Is timetracker enabled // 2. Is the user a contributor, admin, poster or assignee and do the repository policies require this? - return r.IsTimetrackerEnabled() && (r.IsWriter() || r.IsAdmin() || issue.IsPoster(user.ID) || issue.AssigneeID == user.ID || - !r.Repository.MustGetUnit(models.UnitTypeIssues).IssuesConfig().AllowOnlyContributorsToTrackTime) + return r.IsTimetrackerEnabled() && (!r.Repository.MustGetUnit(models.UnitTypeIssues).IssuesConfig().AllowOnlyContributorsToTrackTime || + r.IsWriter() || issue.IsPoster(user.ID) || issue.AssigneeID == user.ID) } // GetEditorconfig returns the .editorconfig definition if found in the From fcec52825256ae6a56cb0a45c274915f077fab52 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Wed, 26 Jul 2017 22:10:40 +0200 Subject: [PATCH 46/88] Moving IsTimetrackerEnabled from context.Repository to models.Repository Signed-off-by: Jonas Franz --- models/repo.go | 18 ++++++++++++++++++ modules/context/repo.go | 13 +------------ routers/api/v1/repo/issue_tracked_time.go | 2 +- routers/repo/issue.go | 1 - routers/repo/setting.go | 1 - templates/repo/issue/view_content/sidebar.tmpl | 2 +- templates/repo/settings/options.tmpl | 4 ++-- 7 files changed, 23 insertions(+), 18 deletions(-) diff --git a/models/repo.go b/models/repo.go index 1057c57941c72..0b329d062bc93 100644 --- a/models/repo.go +++ b/models/repo.go @@ -2377,3 +2377,21 @@ func (repo *Repository) CreateNewBranch(doer *User, oldBranchName, branchName st return nil } + +// ___________.__ ___________ __ +// \__ ___/|__| _____ ___\__ ___/___________ ____ | | __ ___________ +// | | | |/ \_/ __ \| | \_ __ \__ \ _/ ___\| |/ // __ \_ __ \ +// | | | | Y Y \ ___/| | | | \// __ \\ \___| <\ ___/| | \/ +// |____| |__|__|_| /\___ >____| |__| (____ /\___ >__|_ \\___ >__| +// \/ \/ \/ \/ \/ \/ + +// IsTimetrackerEnabled returns whether or not the timetracker is enabled. It also returns false if an error occurs. +func (repo *Repository) IsTimetrackerEnabled() bool { + var u *RepoUnit + var err error + if u, err = repo.GetUnit(UnitTypeIssues); err != nil { + return false + } + return u.IssuesConfig().EnableTimetracker + +} diff --git a/modules/context/repo.go b/modules/context/repo.go index ab3bdfacf2b4a..c1798a8650faa 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -85,23 +85,12 @@ func (r *Repository) CanCommitToBranch() (bool, error) { return r.CanEnableEditor() && !protectedBranch, nil } -// IsTimetrackerEnabled returns whether or not the timetracker is enabled. It also returns false if an error occurs. -func (r *Repository) IsTimetrackerEnabled() bool { - var u *models.RepoUnit - var err error - if u, err = r.Repository.GetUnit(models.UnitTypeIssues); err != nil { - return false - } - return u.IssuesConfig().EnableTimetracker - -} - // CanUseTimetracker returns whether or not a user can use the timetracker. func (r *Repository) CanUseTimetracker(issue *models.Issue, user *models.User) bool { // Checking for following: // 1. Is timetracker enabled // 2. Is the user a contributor, admin, poster or assignee and do the repository policies require this? - return r.IsTimetrackerEnabled() && (!r.Repository.MustGetUnit(models.UnitTypeIssues).IssuesConfig().AllowOnlyContributorsToTrackTime || + return r.Repository.IsTimetrackerEnabled() && (!r.Repository.MustGetUnit(models.UnitTypeIssues).IssuesConfig().AllowOnlyContributorsToTrackTime || r.IsWriter() || issue.IsPoster(user.ID) || issue.AssigneeID == user.ID) } diff --git a/routers/api/v1/repo/issue_tracked_time.go b/routers/api/v1/repo/issue_tracked_time.go index c85dfaeeed539..d4a7fd57517c0 100644 --- a/routers/api/v1/repo/issue_tracked_time.go +++ b/routers/api/v1/repo/issue_tracked_time.go @@ -17,7 +17,7 @@ func ListTrackedTimes(ctx *context.APIContext) { // 200: TrackedTimes // 404: error // 500: error - if !ctx.Repo.IsTimetrackerEnabled() { + if !ctx.Repo.Repository.IsTimetrackerEnabled() { ctx.Error(404, "IsTimetrackerEnabled", "Timetracker is diabled") return } diff --git a/routers/repo/issue.go b/routers/repo/issue.go index da57ad2396082..b342a7c7b48af 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -586,7 +586,6 @@ func ViewIssue(ctx *context.Context) { ctx.Handle(500, "TotalTimes", err) return } - ctx.Data["IsTimetrackerEnabled"] = ctx.Repo.IsTimetrackerEnabled() ctx.Data["CanUseTimetracker"] = ctx.Repo.CanUseTimetracker(issue, ctx.User) // Render comments and and fetch participants. diff --git a/routers/repo/setting.go b/routers/repo/setting.go index 2b04ac621b89d..ff20462a7247c 100644 --- a/routers/repo/setting.go +++ b/routers/repo/setting.go @@ -31,7 +31,6 @@ const ( func Settings(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.settings") ctx.Data["PageIsSettingsOptions"] = true - ctx.Data["IsTimetrackerEnabled"] = ctx.Repo.IsTimetrackerEnabled() ctx.HTML(200, tplSettingsOptions) } diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index 9db9b371aa3a3..e065ac6238672 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -120,7 +120,7 @@
- {{if .IsTimetrackerEnabled }} + {{if .Repository.IsTimetrackerEnabled }} {{if .CanUseTimetracker }}
diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index 2a42fb78a38c9..c3fdb0ceabcf2 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -141,11 +141,11 @@
- +
-
+
From 2fa55dee5f07f82f2669a75b03b47f9b023a13cb Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Wed, 26 Jul 2017 22:20:33 +0200 Subject: [PATCH 47/88] Adding a seperate file for issue related repo functions Adding license headers Signed-off-by: Jonas Franz --- models/repo.go | 18 ------------------ models/repo_issue.go | 22 ++++++++++++++++++++++ routers/api/v1/repo/issue_tracked_time.go | 4 ++++ 3 files changed, 26 insertions(+), 18 deletions(-) create mode 100644 models/repo_issue.go diff --git a/models/repo.go b/models/repo.go index 0b329d062bc93..1057c57941c72 100644 --- a/models/repo.go +++ b/models/repo.go @@ -2377,21 +2377,3 @@ func (repo *Repository) CreateNewBranch(doer *User, oldBranchName, branchName st return nil } - -// ___________.__ ___________ __ -// \__ ___/|__| _____ ___\__ ___/___________ ____ | | __ ___________ -// | | | |/ \_/ __ \| | \_ __ \__ \ _/ ___\| |/ // __ \_ __ \ -// | | | | Y Y \ ___/| | | | \// __ \\ \___| <\ ___/| | \/ -// |____| |__|__|_| /\___ >____| |__| (____ /\___ >__|_ \\___ >__| -// \/ \/ \/ \/ \/ \/ - -// IsTimetrackerEnabled returns whether or not the timetracker is enabled. It also returns false if an error occurs. -func (repo *Repository) IsTimetrackerEnabled() bool { - var u *RepoUnit - var err error - if u, err = repo.GetUnit(UnitTypeIssues); err != nil { - return false - } - return u.IssuesConfig().EnableTimetracker - -} diff --git a/models/repo_issue.go b/models/repo_issue.go new file mode 100644 index 0000000000000..a2cd71d56a407 --- /dev/null +++ b/models/repo_issue.go @@ -0,0 +1,22 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package models + +// ___________.__ ___________ __ +// \__ ___/|__| _____ ___\__ ___/___________ ____ | | __ ___________ +// | | | |/ \_/ __ \| | \_ __ \__ \ _/ ___\| |/ // __ \_ __ \ +// | | | | Y Y \ ___/| | | | \// __ \\ \___| <\ ___/| | \/ +// |____| |__|__|_| /\___ >____| |__| (____ /\___ >__|_ \\___ >__| +// \/ \/ \/ \/ \/ \/ + +// IsTimetrackerEnabled returns whether or not the timetracker is enabled. It also returns false if an error occurs. +func (repo *Repository) IsTimetrackerEnabled() bool { + var u *RepoUnit + var err error + if u, err = repo.GetUnit(UnitTypeIssues); err != nil { + return false + } + return u.IssuesConfig().EnableTimetracker +} diff --git a/routers/api/v1/repo/issue_tracked_time.go b/routers/api/v1/repo/issue_tracked_time.go index d4a7fd57517c0..708399e796568 100644 --- a/routers/api/v1/repo/issue_tracked_time.go +++ b/routers/api/v1/repo/issue_tracked_time.go @@ -1,3 +1,7 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + package repo import ( From 4ed6614408fa2844865a785099919f0ff4b137cf Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Thu, 27 Jul 2017 01:05:09 +0200 Subject: [PATCH 48/88] Fixed GetUserByParams return 404 Signed-off-by: Jonas Franz --- routers/api/v1/repo/issue_tracked_time.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/api/v1/repo/issue_tracked_time.go b/routers/api/v1/repo/issue_tracked_time.go index 708399e796568..760081e80d9e4 100644 --- a/routers/api/v1/repo/issue_tracked_time.go +++ b/routers/api/v1/repo/issue_tracked_time.go @@ -87,7 +87,7 @@ func ListTrackedTimesByUser(ctx *context.APIContext) { // 200: TrackedTimes // 404: error // 500: error - user := GetUserByParamsName(ctx, ctx.Params(":username")) + user := GetUserByParamsName(ctx, ":username") if user == nil { return } From 078f59fbd8f84871ab77553afee78a928ac11528 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Thu, 27 Jul 2017 14:57:34 +0200 Subject: [PATCH 49/88] Moving /users/:username/times to /repos/:username/:reponame/times/:username for security reasons Adding /repos/:username/times to get all tracked times of the repo Signed-off-by: Jonas Franz --- models/issue_tracked_time.go | 8 +++++++- models/issue_tracked_time_test.go | 20 ++++++++++++++++++++ routers/api/v1/api.go | 6 +++++- routers/api/v1/repo/issue_tracked_time.go | 22 ++++++++++++++++++++-- 4 files changed, 52 insertions(+), 4 deletions(-) diff --git a/models/issue_tracked_time.go b/models/issue_tracked_time.go index dfad02e9a60e6..c4ff99c21ab0a 100644 --- a/models/issue_tracked_time.go +++ b/models/issue_tracked_time.go @@ -33,12 +33,18 @@ func GetTrackedTimesByIssue(issueID int64) (trackedTimes []*TrackedTime, err err return } -// GetTrackedTimesByUser will return all tracked times which are created by the user +// GetTrackedTimesByUser will return all tracked times which are created by the user. func GetTrackedTimesByUser(userID int64) (trackedTimes []*TrackedTime, err error) { err = x.Where("user_id = ?", userID).Find(&trackedTimes) return } +// GetTrackedTimesByRepo will return all tracked times of a repository. +func GetTrackedTimesByRepo(repositoryID int64) (trackedTimes []*TrackedTime, err error) { + err = x.Join("INNER", "issue", "issue.id = tracked_time.issue_id").Where("issue.repo_id = ?", repositoryID).Find(&trackedTimes) + return +} + // BeforeInsert will be invoked by XORM before inserting a record // representing this object. func (t *TrackedTime) BeforeInsert() { diff --git a/models/issue_tracked_time_test.go b/models/issue_tracked_time_test.go index 191939cdcfd07..fb04c22bc6dee 100644 --- a/models/issue_tracked_time_test.go +++ b/models/issue_tracked_time_test.go @@ -42,6 +42,26 @@ func TestGetTrackedTimesByUser(t *testing.T) { assert.NoError(t, err) } +func TestGetTrackedTimesByRepo(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + + times, err := GetTrackedTimesByRepo(int64(2)) + assert.Len(t, times, 1) + assert.Equal(t, times[0].Time, int64(1)) + assert.NoError(t, err) + issue, err := GetIssueByID(times[0].IssueID) + assert.NoError(t, err) + assert.Equal(t, issue.RepoID, int64(2)) + + times, err = GetTrackedTimesByRepo(int64(1)) + assert.Len(t, times, 4) + assert.NoError(t, err) + + times, err = GetTrackedTimesByRepo(int64(10)) + assert.Len(t, times, 0) + assert.NoError(t, err) +} + func TestTotalTimes(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index d494eea1a63f5..64dd1834ca880 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -288,7 +288,6 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("/starred", user.GetStarredRepos) m.Get("/subscriptions", user.GetWatchedRepos) - m.Get("/times", repo.ListTrackedTimesByUser) }) }, reqToken()) @@ -375,6 +374,11 @@ func RegisterRoutes(m *macaron.Macaron) { m.Combo("/:id").Get(repo.GetDeployKey). Delete(repo.DeleteDeploykey) }, reqToken()) + m.Group("/times", func() { + m.Combo("").Get(repo.ListTrackedTimesByRepository) + m.Combo("/:timetrackingusername").Get(repo.ListTrackedTimesByUser) + + }, mustEnableIssues) m.Group("/issues", func() { m.Combo("").Get(repo.ListIssues). Post(reqToken(), bind(api.CreateIssueOption{}), repo.CreateIssue) diff --git a/routers/api/v1/repo/issue_tracked_time.go b/routers/api/v1/repo/issue_tracked_time.go index 760081e80d9e4..624b130ec3772 100644 --- a/routers/api/v1/repo/issue_tracked_time.go +++ b/routers/api/v1/repo/issue_tracked_time.go @@ -78,7 +78,7 @@ func AddTime(ctx *context.APIContext, form api.AddTimeOption) { // ListTrackedTimesByUser lists all tracked times of the user func ListTrackedTimesByUser(ctx *context.APIContext) { - // swagger:route GET /user/{username}/times userTrackedTimes + // swagger:route GET /repos/{username}/{reponame}/times/{timetrackingusername} userTrackedTimes // // Produces: // - application/json @@ -87,7 +87,7 @@ func ListTrackedTimesByUser(ctx *context.APIContext) { // 200: TrackedTimes // 404: error // 500: error - user := GetUserByParamsName(ctx, ":username") + user := GetUserByParamsName(ctx, ":timetrackingusername") if user == nil { return } @@ -99,6 +99,24 @@ func ListTrackedTimesByUser(ctx *context.APIContext) { } } +// ListTrackedTimesByRepository lists all tracked times of the user +func ListTrackedTimesByRepository(ctx *context.APIContext) { + // swagger:route GET /repos/{username}/{reponame}/times repoTrackedTimes + // + // Produces: + // - application/json + // + // Responses: + // 200: TrackedTimes + // 404: error + // 500: error + if trackedTimes, err := models.GetTrackedTimesByRepo(ctx.Repo.Repository.ID); err != nil { + ctx.Error(500, "GetTrackedTimesByUser", err) + } else { + ctx.JSON(200, &trackedTimes) + } +} + // ListMyTrackedTimes lists all tracked times of the current user func ListMyTrackedTimes(ctx *context.APIContext) { // swagger:route GET /user/times userTrackedTimes From 3d3b94ae72dfe9fb1573d42bfcd9f1f052e5ff53 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Thu, 27 Jul 2017 15:09:39 +0200 Subject: [PATCH 50/88] Updating sdk-dependency Signed-off-by: Jonas Franz --- .../sdk/gitea/issue_tracked_time.go | 20 ++++++++++++------- vendor/vendor.json | 6 +++--- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/vendor/code.gitea.io/sdk/gitea/issue_tracked_time.go b/vendor/code.gitea.io/sdk/gitea/issue_tracked_time.go index d3b30728b236d..8b8b766c36aee 100644 --- a/vendor/code.gitea.io/sdk/gitea/issue_tracked_time.go +++ b/vendor/code.gitea.io/sdk/gitea/issue_tracked_time.go @@ -27,14 +27,20 @@ type TrackedTime struct { type TrackedTimes []*TrackedTime // GetUserTrackedTimes list tracked times of a user -func (c *Client) GetUserTrackedTimes(user string) ([]*TrackedTime, error) { - times := make([]*TrackedTime, 0, 10) - return times, c.getParsedResponse("GET", fmt.Sprintf("/users/%s/times", user), nil, nil, ×) +func (c *Client) GetUserTrackedTimes(owner, repo, user string) (TrackedTimes, error) { + times := make(TrackedTimes, 0, 10) + return times, c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/times/%s", owner, repo, user), nil, nil, ×) +} + +// GetRepoTrackedTimes list tracked times of a repository +func (c *Client) GetRepoTrackedTimes(owner, repo string) (TrackedTimes, error) { + times := make(TrackedTimes, 0, 10) + return times, c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/times", owner, repo), nil, nil, ×) } // GetMyTrackedTimes list tracked times of the current user -func (c *Client) GetMyTrackedTimes() ([]*TrackedTime, error) { - times := make([]*TrackedTime, 0, 10) +func (c *Client) GetMyTrackedTimes() (TrackedTimes, error) { + times := make(TrackedTimes, 0, 10) return times, c.getParsedResponse("GET", "/user/times", nil, nil, ×) } @@ -56,7 +62,7 @@ func (c *Client) AddTime(owner, repo string, index int64, opt AddTimeOption) (*T } // ListTrackedTimes get tracked times of one issue via issue id -func (c *Client) ListTrackedTimes(owner, repo string, index int64) ([]*TrackedTime, error) { - times := make([]*TrackedTime, 0, 5) +func (c *Client) ListTrackedTimes(owner, repo string, index int64) (TrackedTimes, error) { + times := make(TrackedTimes, 0, 5) return times, c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/times", owner, repo, index), nil, nil, ×) } diff --git a/vendor/vendor.json b/vendor/vendor.json index 24a9204f487c4..840312a8cde1d 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -16,11 +16,11 @@ "revisionTime": "2017-07-26T08:54:01Z" }, { - "checksumSHA1": "cjUHgFv9CrADhoh2YnMgONkGkzM=", + "checksumSHA1": "3uBJnZGSBlYT8RtN3lfiuqz0Bps=", "origin": "github.com/JonasFranzDEV/go-sdk/gitea", "path": "code.gitea.io/sdk/gitea", - "revision": "b1438f6e34d19a033ed50438886a614b2fcbc344", - "revisionTime": "2017-07-26T08:54:01Z" + "revision": "05511a469e69904b04678edfb7d68a549894007f", + "revisionTime": "2017-07-27T13:09:01Z" }, { "checksumSHA1": "bOODD4Gbw3GfcuQPU2dI40crxxk=", From c3b5eccef9e1ca2a520b013275d31cba4dd28757 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Thu, 27 Jul 2017 19:02:19 +0200 Subject: [PATCH 51/88] Updating swagger.v1.json Signed-off-by: Jonas Franz --- public/swagger.v1.json | 62 ++++++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/public/swagger.v1.json b/public/swagger.v1.json index 60ef4521e9d83..f64b59c9cb1e8 100644 --- a/public/swagger.v1.json +++ b/public/swagger.v1.json @@ -307,6 +307,9 @@ "200": { "$ref": "#/responses/AddTimeOption" }, + "403": { + "$ref": "#/responses/error" + }, "404": { "$ref": "#/responses/error" }, @@ -367,6 +370,44 @@ } } }, + "/repos/{username}/{reponame}/times": { + "get": { + "produces": [ + "application/json" + ], + "operationId": "repoTrackedTimes", + "responses": { + "200": { + "$ref": "#/responses/TrackedTimes" + }, + "404": { + "$ref": "#/responses/error" + }, + "500": { + "$ref": "#/responses/error" + } + } + } + }, + "/repos/{username}/{reponame}/times/{timetrackingusername}": { + "get": { + "produces": [ + "application/json" + ], + "operationId": "userTrackedTimes", + "responses": { + "200": { + "$ref": "#/responses/TrackedTimes" + }, + "404": { + "$ref": "#/responses/error" + }, + "500": { + "$ref": "#/responses/error" + } + } + } + }, "/user": { "get": { "produces": [ @@ -727,25 +768,6 @@ } } }, - "/user/{username}/times": { - "get": { - "produces": [ - "application/json" - ], - "operationId": "userTrackedTimes", - "responses": { - "200": { - "$ref": "#/responses/TrackedTimes" - }, - "404": { - "$ref": "#/responses/error" - }, - "500": { - "$ref": "#/responses/error" - } - } - } - }, "/users/:username/followers": { "get": { "produces": [ @@ -1000,7 +1022,7 @@ "x-go-package": "code.gitea.io/gitea/vendor/code.gitea.io/sdk/gitea" }, "GPGKeyEmail": { - "description": "GPGKeyEmail a email attache to a GPGKey", + "description": "GPGKeyEmail an email attached to a GPGKey", "type": "object", "properties": { "email": { From 13fb0e37ed308ec94b4e3959d6b49f46927de59c Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Fri, 28 Jul 2017 01:16:27 +0200 Subject: [PATCH 52/88] Adding warning if user has already a running stopwatch (auto-timetracker) Signed-off-by: Jonas Franz --- models/issue_stopwatch.go | 10 +++++- models/issue_stopwatch_test.go | 13 +++++++ options/locale/locale_en-US.ini | 1 + routers/repo/issue.go | 34 +++++++++++++++---- .../repo/issue/view_content/sidebar.tmpl | 5 +++ 5 files changed, 55 insertions(+), 8 deletions(-) diff --git a/models/issue_stopwatch.go b/models/issue_stopwatch.go index 6b0fe1dc350b9..2dfe5c50ca9bf 100644 --- a/models/issue_stopwatch.go +++ b/models/issue_stopwatch.go @@ -44,13 +44,21 @@ func StopwatchExists(userID int64, issueID int64) bool { return exists } +// HasUserAStopwatch returns true if the user has a stopwatch +func HasUserAStopwatch(userID int64) (exists bool, sw *Stopwatch, err error) { + sw = new(Stopwatch) + exists, err = x. + Where("user_id = ?", userID). + Get(sw) + return +} + // CreateOrStopIssueStopwatch will create or remove a stopwatch and will log it into issue's timeline. func CreateOrStopIssueStopwatch(userID int64, issueID int64) error { sw, exists, err := getStopwatch(x, userID, issueID) if err != nil { return err } - if exists { // Create tracked time out of the time difference between start date and actual date timediff := time.Now().Unix() - sw.CreatedUnix diff --git a/models/issue_stopwatch_test.go b/models/issue_stopwatch_test.go index 089d288eaee5c..bb26968f4ef3f 100644 --- a/models/issue_stopwatch_test.go +++ b/models/issue_stopwatch_test.go @@ -25,6 +25,19 @@ func TestStopwatchExists(t *testing.T) { assert.False(t, StopwatchExists(1, 2)) } +func TestHasUserAStopwatch(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + + exists, sw, err := HasUserAStopwatch(1) + assert.True(t, exists) + assert.Equal(t, sw.ID, int64(1)) + assert.NoError(t, err) + + exists, _, err = HasUserAStopwatch(3) + assert.False(t, exists) + assert.NoError(t, err) +} + func TestCreateOrStopIssueStopwatch(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index a9985642c4333..ed1f8c0715bd8 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -698,6 +698,7 @@ issues.tracker = Time tracker issues.start_tracking_short = Start issues.start_tracking = Start time tracking issues.start_tracking_history = `started working %s` +issues.tracking_already_started = `You already stared tracking at this issue!` issues.stop_tracking = Finish issues.stop_tracking_history = `finished working %s` issues.add_time = Add time manual diff --git a/routers/repo/issue.go b/routers/repo/issue.go index b342a7c7b48af..4b8ac4d823a0e 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -579,14 +579,34 @@ func ViewIssue(ctx *context.Context) { comment *models.Comment participants = make([]*models.User, 1, 10) ) - - // Deal with the stopwatch - ctx.Data["IsStopwatchRunning"] = models.StopwatchExists(ctx.User.ID, issue.ID) - if ctx.Data["WorkingUsers"], err = models.TotalTimes(issue.ID); err != nil { - ctx.Handle(500, "TotalTimes", err) - return + if ctx.Repo.Repository.IsTimetrackerEnabled() { + // Deal with the stopwatch + ctx.Data["IsStopwatchRunning"] = models.StopwatchExists(ctx.User.ID, issue.ID) + if !ctx.Data["IsStopwatchRunning"].(bool) { + var exists bool + var sw *models.Stopwatch + if exists, sw, err = models.HasUserAStopwatch(ctx.User.ID); err != nil { + ctx.Handle(500, "HasUserAStopwatch", err) + return + } + ctx.Data["HasUserAStopwatch"] = exists + if exists { + // Add warning if the user has already a stopwatch + var otherIssue *models.Issue + if otherIssue, err = models.GetIssueByID(sw.IssueID); err != nil { + ctx.Handle(500, "HasUserAStopwatch", err) + return + } + // Add link to the issue of the already running stopwatch + ctx.Data["OtherStopwatchURL"] = otherIssue.HTMLURL() + } + } + if ctx.Data["WorkingUsers"], err = models.TotalTimes(issue.ID); err != nil { + ctx.Handle(500, "TotalTimes", err) + return + } + ctx.Data["CanUseTimetracker"] = ctx.Repo.CanUseTimetracker(issue, ctx.User) } - ctx.Data["CanUseTimetracker"] = ctx.Repo.CanUseTimetracker(issue, ctx.User) // Render comments and and fetch participants. participants[0] = issue.Poster diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index e065ac6238672..7da8c9c21abc2 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -134,6 +134,11 @@ {{.i18n.Tr "repo.issues.cancel_tracking"}}
{{else}} + {{if .HasUserAStopwatch}} +
+ {{.i18n.Tr "repo.issues.tracking_already_started" .OtherStopwatchURL | Safe}} +
+ {{end}}
{{.i18n.Tr "repo.issues.start_tracking_short"}} From 497f214a1772e2341813daade0830c5aad7c47fc Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Fri, 28 Jul 2017 13:50:32 +0200 Subject: [PATCH 53/88] Replacing GetTrackedTimesBy... with GetTrackedTimes(options FindTrackedTimesOptions) Changing code.gitea.io/sdk back to code.gitea.io/sdk Signed-off-by: Jonas Franz --- models/issue_tracked_time.go | 45 +++++++++++++++-------- models/issue_tracked_time_test.go | 35 ++++++++---------- routers/api/v1/repo/issue_tracked_time.go | 8 ++-- routers/repo/issue.go | 2 +- vendor/vendor.json | 5 +-- 5 files changed, 51 insertions(+), 44 deletions(-) diff --git a/models/issue_tracked_time.go b/models/issue_tracked_time.go index c4ff99c21ab0a..74557f44e47e2 100644 --- a/models/issue_tracked_time.go +++ b/models/issue_tracked_time.go @@ -5,6 +5,7 @@ package models import ( + "github.com/go-xorm/builder" "github.com/go-xorm/xorm" "time" ) @@ -27,21 +28,35 @@ func (t *TrackedTime) AfterSet(colName string, _ xorm.Cell) { } } -// GetTrackedTimesByIssue will return all tracked times that are part of the issue -func GetTrackedTimesByIssue(issueID int64) (trackedTimes []*TrackedTime, err error) { - err = x.Where("issue_id = ?", issueID).Find(&trackedTimes) - return +// FindTrackedTimesOptions represent the filters for tracked times. If an ID is 0 it will be ignored. +type FindTrackedTimesOptions struct { + IssueID int64 + UserID int64 + RepositoryID int64 } -// GetTrackedTimesByUser will return all tracked times which are created by the user. -func GetTrackedTimesByUser(userID int64) (trackedTimes []*TrackedTime, err error) { - err = x.Where("user_id = ?", userID).Find(&trackedTimes) - return +// ToCond will convert each condition into a xorm-Cond +func (opts *FindTrackedTimesOptions) ToCond() builder.Cond { + cond := builder.NewCond() + if opts.IssueID != 0 { + cond = cond.And(builder.Eq{"issue_id": opts.IssueID}) + } + if opts.UserID != 0 { + cond = cond.And(builder.Eq{"user_id": opts.UserID}) + } + if opts.RepositoryID != 0 { + cond = cond.And(builder.Eq{"issue.repo_id": opts.RepositoryID}) + } + return cond } -// GetTrackedTimesByRepo will return all tracked times of a repository. -func GetTrackedTimesByRepo(repositoryID int64) (trackedTimes []*TrackedTime, err error) { - err = x.Join("INNER", "issue", "issue.id = tracked_time.issue_id").Where("issue.repo_id = ?", repositoryID).Find(&trackedTimes) +// GetTrackedTimes retuns all tracked times that fit to the given options. +func GetTrackedTimes(options FindTrackedTimesOptions) (trackedTimes []*TrackedTime, err error) { + if options.RepositoryID > 0 { + err = x.Join("INNER", "issue", "issue.id = tracked_time.issue_id").Where(options.ToCond()).Find(&trackedTimes) + return + } + err = x.Where(options.ToCond()).Find(&trackedTimes) return } @@ -74,11 +89,9 @@ func AddTime(userID int64, issueID int64, time int64) error { } // TotalTimes returns the spent time for each user by an issue -func TotalTimes(issueID int64) (map[*User]string, error) { - var trackedTimes []TrackedTime - if err := x. - Where("issue_id = ?", issueID). - Find(&trackedTimes); err != nil { +func TotalTimes(options FindTrackedTimesOptions) (map[*User]string, error) { + trackedTimes, err := GetTrackedTimes(options) + if err != nil { return nil, err } //Adding total time per user ID diff --git a/models/issue_tracked_time_test.go b/models/issue_tracked_time_test.go index fb04c22bc6dee..e9a38e47aeda8 100644 --- a/models/issue_tracked_time_test.go +++ b/models/issue_tracked_time_test.go @@ -16,36 +16,31 @@ func TestAddTime(t *testing.T) { assert.Equal(t, comment.Content, "1h 1min 1s") } -func TestGetTrackedTimesByIssue(t *testing.T) { +func TestGetTrackedTimes(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) - times, err := GetTrackedTimesByIssue(1) + // by Issue + times, err := GetTrackedTimes(FindTrackedTimesOptions{IssueID: 1}) assert.Len(t, times, 1) assert.Equal(t, times[0].Time, int64(400)) assert.NoError(t, err) - times, err = GetTrackedTimesByIssue(3) + times, err = GetTrackedTimes(FindTrackedTimesOptions{IssueID: -1}) assert.Len(t, times, 0) assert.NoError(t, err) -} - -func TestGetTrackedTimesByUser(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - times, err := GetTrackedTimesByUser(1) + // by User + times, err = GetTrackedTimes(FindTrackedTimesOptions{UserID: 1}) assert.Len(t, times, 1) assert.Equal(t, times[0].Time, int64(400)) assert.NoError(t, err) - times, err = GetTrackedTimesByUser(3) + times, err = GetTrackedTimes(FindTrackedTimesOptions{UserID: 3}) assert.Len(t, times, 0) assert.NoError(t, err) -} - -func TestGetTrackedTimesByRepo(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - times, err := GetTrackedTimesByRepo(int64(2)) + // by Repo + times, err = GetTrackedTimes(FindTrackedTimesOptions{RepositoryID: 2}) assert.Len(t, times, 1) assert.Equal(t, times[0].Time, int64(1)) assert.NoError(t, err) @@ -53,11 +48,11 @@ func TestGetTrackedTimesByRepo(t *testing.T) { assert.NoError(t, err) assert.Equal(t, issue.RepoID, int64(2)) - times, err = GetTrackedTimesByRepo(int64(1)) + times, err = GetTrackedTimes(FindTrackedTimesOptions{RepositoryID: 1}) assert.Len(t, times, 4) assert.NoError(t, err) - times, err = GetTrackedTimesByRepo(int64(10)) + times, err = GetTrackedTimes(FindTrackedTimesOptions{RepositoryID: 10}) assert.Len(t, times, 0) assert.NoError(t, err) } @@ -65,7 +60,7 @@ func TestGetTrackedTimesByRepo(t *testing.T) { func TestTotalTimes(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) - total, err := TotalTimes(1) + total, err := TotalTimes(FindTrackedTimesOptions{IssueID: 1}) assert.Len(t, total, 1) for user, time := range total { assert.Equal(t, int64(1), user.ID) @@ -73,7 +68,7 @@ func TestTotalTimes(t *testing.T) { } assert.NoError(t, err) - total, err = TotalTimes(2) + total, err = TotalTimes(FindTrackedTimesOptions{IssueID: 2}) assert.Len(t, total, 1) for user, time := range total { assert.Equal(t, int64(2), user.ID) @@ -81,7 +76,7 @@ func TestTotalTimes(t *testing.T) { } assert.NoError(t, err) - total, err = TotalTimes(5) + total, err = TotalTimes(FindTrackedTimesOptions{IssueID: 5}) assert.Len(t, total, 1) for user, time := range total { assert.Equal(t, int64(2), user.ID) @@ -89,7 +84,7 @@ func TestTotalTimes(t *testing.T) { } assert.NoError(t, err) - total, err = TotalTimes(4) + total, err = TotalTimes(FindTrackedTimesOptions{IssueID: 4}) assert.Len(t, total, 0) assert.NoError(t, err) } diff --git a/routers/api/v1/repo/issue_tracked_time.go b/routers/api/v1/repo/issue_tracked_time.go index 624b130ec3772..e45757e72586e 100644 --- a/routers/api/v1/repo/issue_tracked_time.go +++ b/routers/api/v1/repo/issue_tracked_time.go @@ -35,7 +35,7 @@ func ListTrackedTimes(ctx *context.APIContext) { return } - if trackedTimes, err := models.GetTrackedTimesByIssue(issue.ID); err != nil { + if trackedTimes, err := models.GetTrackedTimes(models.FindTrackedTimesOptions{IssueID: issue.ID}); err != nil { ctx.Error(500, "GetTrackedTimesByIssue", err) } else { ctx.JSON(200, &trackedTimes) @@ -92,7 +92,7 @@ func ListTrackedTimesByUser(ctx *context.APIContext) { return } - if trackedTimes, err := models.GetTrackedTimesByUser(user.ID); err != nil { + if trackedTimes, err := models.GetTrackedTimes(models.FindTrackedTimesOptions{UserID: user.ID, RepositoryID: ctx.Repo.Repository.ID}); err != nil { ctx.Error(500, "GetTrackedTimesByUser", err) } else { ctx.JSON(200, &trackedTimes) @@ -110,7 +110,7 @@ func ListTrackedTimesByRepository(ctx *context.APIContext) { // 200: TrackedTimes // 404: error // 500: error - if trackedTimes, err := models.GetTrackedTimesByRepo(ctx.Repo.Repository.ID); err != nil { + if trackedTimes, err := models.GetTrackedTimes(models.FindTrackedTimesOptions{RepositoryID: ctx.Repo.Repository.ID}); err != nil { ctx.Error(500, "GetTrackedTimesByUser", err) } else { ctx.JSON(200, &trackedTimes) @@ -127,7 +127,7 @@ func ListMyTrackedTimes(ctx *context.APIContext) { // Responses: // 200: TrackedTimes // 500: error - if trackedTimes, err := models.GetTrackedTimesByUser(ctx.User.ID); err != nil { + if trackedTimes, err := models.GetTrackedTimes(models.FindTrackedTimesOptions{UserID: ctx.User.ID}); err != nil { ctx.Error(500, "GetTrackedTimesByUser", err) } else { ctx.JSON(200, &trackedTimes) diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 4b8ac4d823a0e..1e438609f3ee2 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -601,7 +601,7 @@ func ViewIssue(ctx *context.Context) { ctx.Data["OtherStopwatchURL"] = otherIssue.HTMLURL() } } - if ctx.Data["WorkingUsers"], err = models.TotalTimes(issue.ID); err != nil { + if ctx.Data["WorkingUsers"], err = models.TotalTimes(models.FindTrackedTimesOptions{IssueID: issue.ID}); err != nil { ctx.Handle(500, "TotalTimes", err) return } diff --git a/vendor/vendor.json b/vendor/vendor.json index 840312a8cde1d..6fffb21f9f9db 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -17,10 +17,9 @@ }, { "checksumSHA1": "3uBJnZGSBlYT8RtN3lfiuqz0Bps=", - "origin": "github.com/JonasFranzDEV/go-sdk/gitea", "path": "code.gitea.io/sdk/gitea", - "revision": "05511a469e69904b04678edfb7d68a549894007f", - "revisionTime": "2017-07-27T13:09:01Z" + "revision": "c7c050c877e2f863e192c12110e73d658f7ef167", + "revisionTime": "2017-07-28T06:12:22Z" }, { "checksumSHA1": "bOODD4Gbw3GfcuQPU2dI40crxxk=", From 6563b5b977c036b72ee0feefcf7eef23d5ad797c Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Fri, 28 Jul 2017 13:57:51 +0200 Subject: [PATCH 54/88] Correcting spelling mistake Signed-off-by: Jonas Franz --- models/issue_tracked_time.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/issue_tracked_time.go b/models/issue_tracked_time.go index 74557f44e47e2..7238385de704d 100644 --- a/models/issue_tracked_time.go +++ b/models/issue_tracked_time.go @@ -50,7 +50,7 @@ func (opts *FindTrackedTimesOptions) ToCond() builder.Cond { return cond } -// GetTrackedTimes retuns all tracked times that fit to the given options. +// GetTrackedTimes returns all tracked times that fit to the given options. func GetTrackedTimes(options FindTrackedTimesOptions) (trackedTimes []*TrackedTime, err error) { if options.RepositoryID > 0 { err = x.Join("INNER", "issue", "issue.id = tracked_time.issue_id").Where(options.ToCond()).Find(&trackedTimes) From b42380cb6a3b24bc3fab18861f6f1cabf6a27cb3 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Fri, 28 Jul 2017 14:05:12 +0200 Subject: [PATCH 55/88] Updating code.gitea.io/sdk Signed-off-by: Jonas Franz --- vendor/code.gitea.io/sdk/CONTRIBUTING.md | 86 ++++++++++++++++++++++++ vendor/code.gitea.io/sdk/DCO | 36 ++++++++++ vendor/code.gitea.io/sdk/MAINTAINERS | 17 +++++ vendor/code.gitea.io/sdk/Makefile | 40 +++++++++++ vendor/code.gitea.io/sdk/README.md | 26 +++++++ 5 files changed, 205 insertions(+) create mode 100644 vendor/code.gitea.io/sdk/CONTRIBUTING.md create mode 100644 vendor/code.gitea.io/sdk/DCO create mode 100644 vendor/code.gitea.io/sdk/MAINTAINERS create mode 100644 vendor/code.gitea.io/sdk/Makefile create mode 100644 vendor/code.gitea.io/sdk/README.md diff --git a/vendor/code.gitea.io/sdk/CONTRIBUTING.md b/vendor/code.gitea.io/sdk/CONTRIBUTING.md new file mode 100644 index 0000000000000..3c076437cc8f3 --- /dev/null +++ b/vendor/code.gitea.io/sdk/CONTRIBUTING.md @@ -0,0 +1,86 @@ +# Contribution Guidelines + +## Introduction + +This document explains how to contribute changes to the Gitea project. It assumes you have followed the [installation instructions](https://github.com/go-gitea/docs/tree/master/en-US/installation). Sensitive security-related issues should be reported to [security@gitea.io](mailto:security@gitea.io). + +## Bug reports + +Please search the issues on the issue tracker with a variety of keywords to ensure your bug is not already reported. + +If unique, [open an issue](https://github.com/go-gitea/gitea/issues/new) and answer the questions so we can understand and reproduce the problematic behavior. + +To show us that the issue you are having is in Gitea itself, please write clear, concise instructions so we can reproduce the behavior (even if it seems obvious). The more detailed and specific you are, the faster we can fix the issue. Check out [How to Report Bugs Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html). + +Please be kind, remember that Gitea comes at no cost to you, and you're getting free help. + +## Discuss your design + +The project welcomes submissions but please let everyone know what you're working on if you want to change or add something to the Gitea repositories. + +Before starting to write something new for the Gitea project, please [file an issue](https://github.com/go-gitea/gitea/issues/new). Significant changes must go through the [change proposal process](https://github.com/go-gitea/proposals) before they can be accepted. + +This process gives everyone a chance to validate the design, helps prevent duplication of effort, and ensures that the idea fits inside the goals for the project and tools. It also checks that the design is sound before code is written; the code review tool is not the place for high-level discussions. + +## Testing redux + +Before sending code out for review, run all the tests for the whole tree to make sure the changes don't break other usage and keep the compatibility on upgrade. To make sure you are running the test suite exactly like we do, you should install the CLI for [Drone CI](https://github.com/drone/drone), as we are using the server for continous testing, following [these instructions](http://readme.drone.io/0.5/install/cli/). After that you can simply call `drone exec` within your working directory and it will try to run the test suite locally. + +## Code review + +Changes to Gitea must be reviewed before they are accepted, no matter who makes the change even if it is an owner or a maintainer. We use GitHub's pull request workflow to do that and we also use [LGTM](http://lgtm.co) to ensure every PR is reviewed by at least 2 maintainers. + +Please try to make your pull request easy to review for us. Please read the "[How to get faster PR reviews](https://github.com/kubernetes/community/blob/master/contributors/devel/faster_reviews.md)" guide, it has lots of useful tips for any project you may want to contribute. Some of the key points: + +* Make small pull requests. The smaller, the faster to review and the more likely it will be merged soon. +* Don't make changes unrelated to your PR. Maybe there are typos on some comments, maybe refactoring would be welcome on a function... but if that is not related to your PR, please make *another* PR for that. +* Split big pull requests into multiple small ones. An incremental change will be faster to review than a huge PR. + +## Sign your work + +The sign-off is a simple line at the end of the explanation for the patch. Your signature certifies that you wrote the patch or otherwise have the right to pass it on as an open-source patch. The rules are pretty simple: If you can certify [DCO](DCO), then you just add a line to every git commit message: + +``` +Signed-off-by: Joe Smith +``` + +Please use your real name, we really dislike pseudonyms or anonymous contributions. We are in the open-source world without secrets. If you set your `user.name` and `user.email` git configs, you can sign your commit automatically with `git commit -s`. + +## Maintainers + +To make sure every PR is checked, we have [team maintainers](https://github.com/orgs/go-gitea/teams/maintainers). Every PR **MUST** be reviewed by at least two maintainers (or owners) before it can get merged. A maintainer should be a contributor of Gitea (or Gogs) and contributed at least 4 accepted PRs. A contributor should apply as a maintainer in the [Gitter develop channel](https://gitter.im/go-gitea/develop). The owners or the team maintainers may invite the contributor. A maintainer should spend some time on code reviews. If a maintainer has no time to do that, they should apply to leave the maintainers team and we will give them the honor of being a member of the [advisors team](https://github.com/orgs/go-gitea/teams/advisors). Of course, if an advisor has time to code review, we will gladly welcome them back to the maintainers team. If a maintainer is inactive for more than 3 months and forgets to leave the maintainers team, the owners may move him or her from the maintainers team to the advisors team. + +## Owners + +Since Gitea is a pure community organization without any company support, to keep the development healthy we will elect three owners every year. All contributors may vote to elect up to three candidates, one of which will be the main owner, and the other two the assistant owners. When the new owners have been elected, the old owners will give up ownership to the newly elected owners. If an owner is unable to do so, the other owners will assist in ceding ownership to the newly elected owners. + +After the election, the new owners should proactively agree with our [CONTRIBUTING](CONTRIBUTING.md) requirements on the [Gitter main channel](https://gitter.im/go-gitea/gitea). Below are the words to speak: + +``` +I'm honored to having been elected an owner of Gitea, I agree with [CONTRIBUTING](CONTRIBUTING.md). I will spend part of my time on Gitea and lead the development of Gitea. +``` + +To honor the past owners, here's the history of the owners and the time they served: + +* 2016-11-04 ~ 2017-12-31 + * [Lunny Xiao](https://github.com/lunny) + * [Thomas Boerger](https://github.com/tboerger) + * [Kim Carlbäcker](https://github.com/bkcsoft) + +## Versions + +Gitea has the `master` branch as a tip branch and has version branches such as `v0.9`. `v0.9` is a release branch and we will tag `v0.9.0` for binary download. If `v0.9.0` has bugs, we will accept pull requests on the `v0.9` branch and publish a `v0.9.1` tag, after bringing the bug fix also to the master branch. + +Since the `master` branch is a tip version, if you wish to use Gitea in production, please download the latest release tag version. All the branches will be protected via GitHub, all the PRs to every branch must be reviewed by two maintainers and must pass the automatic tests. + +## Copyright + +Code that you contribute should use the standard copyright header: + +``` +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +``` + +Files in the repository contain copyright from the year they are added to the year they are last changed. If the copyright author is changed, just paste the header below the old one. diff --git a/vendor/code.gitea.io/sdk/DCO b/vendor/code.gitea.io/sdk/DCO new file mode 100644 index 0000000000000..716561d5d2826 --- /dev/null +++ b/vendor/code.gitea.io/sdk/DCO @@ -0,0 +1,36 @@ +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +660 York Street, Suite 102, +San Francisco, CA 94110 USA + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. diff --git a/vendor/code.gitea.io/sdk/MAINTAINERS b/vendor/code.gitea.io/sdk/MAINTAINERS new file mode 100644 index 0000000000000..5a4dfa1855020 --- /dev/null +++ b/vendor/code.gitea.io/sdk/MAINTAINERS @@ -0,0 +1,17 @@ +Alexey Makhov (@makhov) +Andrey Nering (@andreynering) +Bo-Yi Wu (@appleboy) +Ethan Koenig (@ethantkoenig) +Kees de Vries (@Bwko) +Kim Carlbäcker (@bkcsoft) +LefsFlare (@LefsFlarey) +Lunny Xiao (@lunny) +Matthias Loibl (@metalmatze) +Rachid Zarouali (@xinity) +Rémy Boulanouar (@DblK) +Sandro Santilli (@strk) +Thibault Meyer (@0xbaadf00d) +Thomas Boerger (@tboerger) +Antoine Girard (@sapk) +Lauris Bukšis-Haberkorns (@lafriks) +Jonas Östanbäck (@cez81) diff --git a/vendor/code.gitea.io/sdk/Makefile b/vendor/code.gitea.io/sdk/Makefile new file mode 100644 index 0000000000000..6c517ff18d7bc --- /dev/null +++ b/vendor/code.gitea.io/sdk/Makefile @@ -0,0 +1,40 @@ +IMPORT := code.gitea.io/sdk + +PACKAGES ?= $(shell go list ./... | grep -v /vendor/) +GENERATE ?= code.gitea.io/sdk/gitea + +.PHONY: all +all: clean test build + +.PHONY: clean +clean: + go clean -i ./... + +generate: + @which mockery > /dev/null; if [ $$? -ne 0 ]; then \ + go get -u github.com/vektra/mockery/...; \ + fi + go generate $(GENERATE) + +.PHONY: fmt +fmt: + find . -name "*.go" -type f -not -path "./vendor/*" | xargs gofmt -s -w + +.PHONY: vet +vet: + go vet $(PACKAGES) + +.PHONY: lint +lint: + @which golint > /dev/null; if [ $$? -ne 0 ]; then \ + go get -u github.com/golang/lint/golint; \ + fi + for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done; + +.PHONY: test +test: + for PKG in $(PACKAGES); do go test -cover -coverprofile $$GOPATH/src/$$PKG/coverage.out $$PKG || exit 1; done; + +.PHONY: build +build: + go build ./gitea diff --git a/vendor/code.gitea.io/sdk/README.md b/vendor/code.gitea.io/sdk/README.md new file mode 100644 index 0000000000000..225525a394ae3 --- /dev/null +++ b/vendor/code.gitea.io/sdk/README.md @@ -0,0 +1,26 @@ +# Gitea SDK for Go + +[![Build Status](http://drone.gitea.io/api/badges/go-gitea/go-sdk/status.svg)](http://drone.gitea.io/go-gitea/go-sdk) +[![Join the chat at https://img.shields.io/discord/322538954119184384.svg](https://img.shields.io/discord/322538954119184384.svg)](https://discord.gg/NsatcWJ) +[![](https://images.microbadger.com/badges/image/gitea/gitea.svg)](http://microbadger.com/images/gitea/gitea "Get your own image badge on microbadger.com") +[![Coverage Status](https://coverage.gitea.io/badges/go-gitea/go-sdk/coverage.svg)](https://coverage.gitea.io/go-gitea/go-sdk) +[![Go Report Card](https://goreportcard.com/badge/code.gitea.io/sdk)](https://goreportcard.com/report/code.gitea.io/sdk) +[![GoDoc](https://godoc.org/code.gitea.io/sdk/gitea?status.svg)](https://godoc.org/code.gitea.io/sdk/gitea) + +This project acts as a client SDK implementation written in Go to interact with +the Gitea API implementation. For further informations take a look at the +current [documentation](https://godoc.org/code.gitea.io/sdk/gitea). + +## Contributing + +Fork -> Patch -> Push -> Pull Request + +## Authors + +* [Maintainers](https://github.com/orgs/go-gitea/people) +* [Contributors](https://github.com/go-gitea/go-sdk/graphs/contributors) + +## License + +This project is under the MIT License. See the [LICENSE](LICENSE) file for the +full license text. From 4a97d69b77ef9d41b1aa3476a1ea8a6a8e60234c Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Fri, 28 Jul 2017 14:06:49 +0200 Subject: [PATCH 56/88] Updating vendor.json Signed-off-by: Jonas Franz --- vendor/vendor.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index 6fffb21f9f9db..4bd09e66bb783 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -10,10 +10,9 @@ }, { "checksumSHA1": "h9l4H9NAK15odGqlHN9p9lo1EBk=", - "origin": "github.com/JonasFranzDEV/go-sdk", "path": "code.gitea.io/sdk", - "revision": "b1438f6e34d19a033ed50438886a614b2fcbc344", - "revisionTime": "2017-07-26T08:54:01Z" + "revision": "05511a469e69904b04678edfb7d68a549894007f", + "revisionTime": "2017-07-27T13:09:01Z" }, { "checksumSHA1": "3uBJnZGSBlYT8RtN3lfiuqz0Bps=", From a69b5cd38ca7a16bf61d0c40229bb26ef1796798 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Fri, 28 Jul 2017 14:13:02 +0200 Subject: [PATCH 57/88] Updating vendor.json Signed-off-by: Jonas Franz --- vendor/vendor.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index 4bd09e66bb783..65fd09ca78a22 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -8,12 +8,6 @@ "revision": "fc639668937225e1b9cda203b3491869b439a951", "revisionTime": "2017-07-10T08:37:35Z" }, - { - "checksumSHA1": "h9l4H9NAK15odGqlHN9p9lo1EBk=", - "path": "code.gitea.io/sdk", - "revision": "05511a469e69904b04678edfb7d68a549894007f", - "revisionTime": "2017-07-27T13:09:01Z" - }, { "checksumSHA1": "3uBJnZGSBlYT8RtN3lfiuqz0Bps=", "path": "code.gitea.io/sdk/gitea", From 9023d2441434ae262b341af880fa0d4e705c433d Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Sat, 29 Jul 2017 18:13:57 +0200 Subject: [PATCH 58/88] Changing GET stopwatch/toggle to POST stopwatch/toggle Signed-off-by: Jonas Franz --- public/js/index.js | 4 ++++ routers/routes/routes.go | 2 +- templates/repo/issue/view_content/sidebar.tmpl | 9 +++++---- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/public/js/index.js b/public/js/index.js index 4c774bbae3b6a..4c04da696ae5a 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -1729,3 +1729,7 @@ function timeAddManual() { }).modal('show') ; } + +function toggleStopwatch() { + $("#toggle_stopwatch_form").submit(); +} diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 783a5673366d0..6164557bcd8fc 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -470,7 +470,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Group("/times", func() { m.Post("/add", repo.AddTimeManual) m.Group("/stopwatch", func() { - m.Get("/toggle", repo.IssueStopwatch) + m.Post("/toggle", repo.IssueStopwatch) m.Get("/cancel", repo.CancelStopwatch) }, func(ctx *context.Context) { if !ctx.Repo.CanUseTimetracker(repo.GetActionIssue(ctx), ctx.User) { diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index 7da8c9c21abc2..f7d07a21bf9f1 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -126,11 +126,12 @@
{{.i18n.Tr "repo.issues.tracker"}}
- - +
+ {{$.CsrfTokenHtml}} +
{{if $.IsStopwatchRunning}} {{else}} @@ -140,7 +141,7 @@
{{end}}
- {{.i18n.Tr "repo.issues.start_tracking_short"}} + {{end}}
- - + + {{end}} - + {{end}} {{if gt (len .WorkingUsers) 0}} -
-
- {{.i18n.Tr "repo.issues.time_spent_total"}} -
- {{range $user, $trackedtime := .WorkingUsers}} -
- - - -
- {{$user.DisplayName}} -
- {{$trackedtime}} -
+
+
+ {{.i18n.Tr "repo.issues.time_spent_total"}} +
+ {{range $user, $trackedtime := .WorkingUsers}} +
+ + + +
+ {{$user.DisplayName}} +
+ {{$trackedtime}}
- {{end}} -
+ {{end}} +
- {{end}} +
{{end}} {{end}}
From 88d1167c3255904f0aa49c34628d5a2c8f3abaf0 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Mon, 31 Jul 2017 01:21:21 +0200 Subject: [PATCH 61/88] Fixed migration by chaging x.Iterate to x.Find Signed-off-by: Jonas Franz --- models/migrations/v16.go | 8 ++++---- models/migrations/v39.go | 26 +++++++++++++++----------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/models/migrations/v16.go b/models/migrations/v16.go index 9cd4ef4da26c2..2a6d71de41cdc 100644 --- a/models/migrations/v16.go +++ b/models/migrations/v16.go @@ -19,9 +19,9 @@ type RepoUnit struct { RepoID int64 `xorm:"INDEX(s)"` Type int `xorm:"INDEX(s)"` Index int - Config map[string]string `xorm:"JSON"` - CreatedUnix int64 `xorm:"INDEX CREATED"` - Created time.Time `xorm:"-"` + Config map[string]interface{} `xorm:"JSON"` + CreatedUnix int64 `xorm:"INDEX CREATED"` + Created time.Time `xorm:"-"` } // Enumerate all the unit types @@ -95,7 +95,7 @@ func addUnitsToTables(x *xorm.Engine) error { continue } - var config = make(map[string]string) + var config = make(map[string]interface{}) switch i { case V16UnitTypeExternalTracker: config["ExternalTrackerURL"] = repo.ExternalTrackerURL diff --git a/models/migrations/v39.go b/models/migrations/v39.go index 2f81ee61905a6..4180306b5aa4b 100644 --- a/models/migrations/v39.go +++ b/models/migrations/v39.go @@ -5,11 +5,10 @@ package migrations import ( - "code.gitea.io/gitea/modules/setting" "fmt" - "github.com/go-xorm/xorm" - "strconv" "time" + "github.com/go-xorm/xorm" + "code.gitea.io/gitea/modules/setting" ) // Stopwatch see models/issue_stopwatch.go @@ -38,22 +37,27 @@ func addTimeTracking(x *xorm.Engine) error { if err := x.Sync2(new(TrackedTime)); err != nil { return fmt.Errorf("Sync2: %v", err) } - x.Where("type = ?", V16UnitTypeIssues).Iterate(new(RepoUnit), func(id int, bean interface{}) error { - unit := bean.(*RepoUnit) + //Updating existing issue units + var units []*RepoUnit + x.Where("type = ?", V16UnitTypeIssues).Find(&units) + for _, unit := range units { + if unit.Config == nil { + unit.Config = make(map[string]interface{}) + } changes := false if _, ok := unit.Config["EnableTimetracker"]; !ok { - unit.Config["EnableTimetracker"] = strconv.FormatBool(setting.Service.DefaultEnableTimetracking) + unit.Config["EnableTimetracker"] = setting.Service.DefaultEnableTimetracking changes = true } if _, ok := unit.Config["AllowOnlyContributorsToTrackTime"]; !ok { - unit.Config["AllowOnlyContributorsToTrackTime"] = strconv.FormatBool(setting.Service.DefaultAllowOnlyContributorsToTrackTime) + unit.Config["AllowOnlyContributorsToTrackTime"] = setting.Service.DefaultAllowOnlyContributorsToTrackTime changes = true } if changes { - _, err := x.Id(id).Cols("config").Update(unit) - return err + if _, err := x.Id(unit.ID).Cols("config").Update(unit); err != nil { + return err + } } - return nil - }) + } return nil } From f50e0d2c00923e43dbbb3d7d4c8355aec091439e Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Mon, 31 Jul 2017 18:11:48 +0200 Subject: [PATCH 62/88] Resorted imports Signed-off-by: Jonas Franz --- models/migrations/v39.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/migrations/v39.go b/models/migrations/v39.go index 4180306b5aa4b..4b9882859edb6 100644 --- a/models/migrations/v39.go +++ b/models/migrations/v39.go @@ -5,10 +5,10 @@ package migrations import ( + "code.gitea.io/gitea/modules/setting" "fmt" - "time" "github.com/go-xorm/xorm" - "code.gitea.io/gitea/modules/setting" + "time" ) // Stopwatch see models/issue_stopwatch.go From 55ee4e4306634a6b39ec77eaab7735ce4d1c2799 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Mon, 31 Jul 2017 22:51:25 +0200 Subject: [PATCH 63/88] Moved Add Time Manually form to repo_form.go Removed "Seconds" field from Add Time Manually Resorted imports Improved permission checking Fixed some bugs Signed-off-by: Jonas Franz --- models/issue_stopwatch.go | 4 +- models/issue_stopwatch_test.go | 5 +- models/issue_tracked_time.go | 5 +- models/issue_tracked_time_test.go | 3 +- models/migrations/v39.go | 6 ++- models/repo_issue.go | 14 ++++- modules/auth/repo_form.go | 18 +++++++ options/locale/locale_en-US.ini | 2 +- routers/repo/issue_stopwatch.go | 2 - routers/repo/issue_timetrack.go | 54 ++++++++++--------- routers/routes/routes.go | 12 ++--- .../repo/issue/view_content/sidebar.tmpl | 3 +- templates/repo/settings/options.tmpl | 2 +- 13 files changed, 82 insertions(+), 48 deletions(-) diff --git a/models/issue_stopwatch.go b/models/issue_stopwatch.go index a0b376d021d6c..50146de05b1bb 100644 --- a/models/issue_stopwatch.go +++ b/models/issue_stopwatch.go @@ -6,8 +6,9 @@ package models import ( "fmt" - "github.com/go-xorm/xorm" "time" + + "github.com/go-xorm/xorm" ) // Stopwatch represents a stopwatch for time tracking. @@ -21,7 +22,6 @@ type Stopwatch struct { // AfterSet is invoked from XORM after setting the value of a field of this object. func (s *Stopwatch) AfterSet(colName string, _ xorm.Cell) { - switch colName { case "created_unix": diff --git a/models/issue_stopwatch_test.go b/models/issue_stopwatch_test.go index 31a17b8ed41d7..e5b72fb704e0b 100644 --- a/models/issue_stopwatch_test.go +++ b/models/issue_stopwatch_test.go @@ -1,9 +1,10 @@ package models import ( - "github.com/stretchr/testify/assert" "testing" "time" + + "github.com/stretchr/testify/assert" ) func TestCancelStopwatch(t *testing.T) { @@ -25,7 +26,7 @@ func TestStopwatchExists(t *testing.T) { assert.False(t, StopwatchExists(1, 2)) } -func TestHasUserAStopwatch(t *testing.T) { +func TestHasUserStopwatch(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) exists, sw, err := HasUserStopwatch(1) diff --git a/models/issue_tracked_time.go b/models/issue_tracked_time.go index 7238385de704d..407d6116025d5 100644 --- a/models/issue_tracked_time.go +++ b/models/issue_tracked_time.go @@ -5,9 +5,10 @@ package models import ( - "github.com/go-xorm/builder" - "github.com/go-xorm/xorm" "time" + + "github.com/go-xorm/xorm" + "github.com/go-xorm/builder" ) // TrackedTime represents a time that was spent for a specific issue. diff --git a/models/issue_tracked_time_test.go b/models/issue_tracked_time_test.go index e9a38e47aeda8..0c1bda6df0186 100644 --- a/models/issue_tracked_time_test.go +++ b/models/issue_tracked_time_test.go @@ -1,8 +1,9 @@ package models import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestAddTime(t *testing.T) { diff --git a/models/migrations/v39.go b/models/migrations/v39.go index 4b9882859edb6..0fe7436717c29 100644 --- a/models/migrations/v39.go +++ b/models/migrations/v39.go @@ -5,10 +5,12 @@ package migrations import ( - "code.gitea.io/gitea/modules/setting" "fmt" - "github.com/go-xorm/xorm" "time" + + "code.gitea.io/gitea/modules/setting" + + "github.com/go-xorm/xorm" ) // Stopwatch see models/issue_stopwatch.go diff --git a/models/repo_issue.go b/models/repo_issue.go index a2cd71d56a407..7c2af9c7da4a2 100644 --- a/models/repo_issue.go +++ b/models/repo_issue.go @@ -4,6 +4,8 @@ package models +import "code.gitea.io/gitea/modules/setting" + // ___________.__ ___________ __ // \__ ___/|__| _____ ___\__ ___/___________ ____ | | __ ___________ // | | | |/ \_/ __ \| | \_ __ \__ \ _/ ___\| |/ // __ \_ __ \ @@ -16,7 +18,17 @@ func (repo *Repository) IsTimetrackerEnabled() bool { var u *RepoUnit var err error if u, err = repo.GetUnit(UnitTypeIssues); err != nil { - return false + return setting.Service.DefaultEnableTimetracking } return u.IssuesConfig().EnableTimetracker } + +// AllowOnlyContributorsToTrackTime returns value of IssuesConfig or the default value +func (repo *Repository) AllowOnlyContributorsToTrackTime() bool { + var u *RepoUnit + var err error + if u, err = repo.GetUnit(UnitTypeIssues); err != nil { + return setting.Service.DefaultAllowOnlyContributorsToTrackTime + } + return u.IssuesConfig().AllowOnlyContributorsToTrackTime +} diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go index e2dee22e06a10..3f6ede75d49f6 100644 --- a/modules/auth/repo_form.go +++ b/modules/auth/repo_form.go @@ -410,3 +410,21 @@ type DeleteRepoFileForm struct { func (f *DeleteRepoFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { return validate(errs, ctx.Data, f, ctx.Locale) } + +// ___________.__ ___________ __ +// \__ ___/|__| _____ ____ \__ ___/___________ ____ | | __ ___________ +// | | | |/ \_/ __ \ | | \_ __ \__ \ _/ ___\| |/ // __ \_ __ \ +// | | | | Y Y \ ___/ | | | | \// __ \\ \___| <\ ___/| | \/ +// |____| |__|__|_| /\___ > |____| |__| (____ /\___ >__|_ \\___ >__| +// \/ \/ \/ \/ \/ \/ + +// AddTimeManually form that adds spent time manually. +type AddTimeManuallyForm struct { + Hours int `binding:"Range(0,1000)"` + Minutes int `binding:"Range(0,1000)"` +} + +// Validate validates the fields +func (f *AddTimeManuallyForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 4e5ade70e04c8..a0ceec89f06f2 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -708,7 +708,7 @@ issues.add_time_cancel = Cancel issues.add_time_history = `added spent time %s` issues.add_time_hours = Hours issues.add_time_minutes = Minutes -issues.add_time_seconds = Seconds +issues.add_time_sum_to_small = No time was entered issues.cancel_tracking = Cancel issues.cancel_tracking_history = `cancelled time tracking %s` issues.time_spent_total = Total time spent diff --git a/routers/repo/issue_stopwatch.go b/routers/repo/issue_stopwatch.go index ffd148e8e25d9..7add8a1e1aef1 100644 --- a/routers/repo/issue_stopwatch.go +++ b/routers/repo/issue_stopwatch.go @@ -13,7 +13,6 @@ import ( // IssueStopwatch manges the stopwatch func IssueStopwatch(c *context.Context) { - issueIndex := c.ParamsInt64("index") issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, issueIndex) @@ -33,7 +32,6 @@ func IssueStopwatch(c *context.Context) { // CancelStopwatch cancel the stopwatch func CancelStopwatch(c *context.Context) { - issueIndex := c.ParamsInt64("index") issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, issueIndex) diff --git a/routers/repo/issue_timetrack.go b/routers/repo/issue_timetrack.go index 1b3bae46f5dcc..d237bbaff679d 100644 --- a/routers/repo/issue_timetrack.go +++ b/routers/repo/issue_timetrack.go @@ -5,44 +5,50 @@ package repo import ( + "time" + "strconv" + "net/http" + + "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" - "net/http" - "time" ) -// AddTimeManual tracks time manually -func AddTimeManual(c *context.Context) { - - h, err := parseTimeTrackingWithDuration(c.Req.PostForm.Get("hours"), "h") +// AddTimeManually tracks time manually +func AddTimeManually(c *context.Context, form auth.AddTimeManuallyForm) { + issueIndex := c.ParamsInt64("index") + issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, issueIndex) if err != nil { - c.Handle(http.StatusBadRequest, "hours is not numeric", err) + c.Handle(http.StatusInternalServerError, "GetIssueByIndex", err) return } + url := issue.HTMLURL() - m, err := parseTimeTrackingWithDuration(c.Req.PostForm.Get("minutes"), "m") - if err != nil { - c.Handle(http.StatusBadRequest, "minutes is not numeric", err) + if c.HasError() { + c.Flash.Error(c.GetErrMsg()) + c.Redirect(url) return } - s, err := parseTimeTrackingWithDuration(c.Req.PostForm.Get("seconds"), "s") + h, err := parseTimeTrackingWithDuration(form.Hours, "h") if err != nil { - c.Handle(http.StatusBadRequest, "seconds is not numeric", err) + c.Handle(http.StatusInternalServerError, "parseTimeTrackingWithDuration", err) return } - total := h + m + s - - if total <= 0 { - c.Handle(http.StatusBadRequest, "sum of seconds <= 0", nil) + m, err := parseTimeTrackingWithDuration(form.Minutes, "m") + if err != nil { + c.Handle(http.StatusInternalServerError, "parseTimeTrackingWithDuration", err) return } - issueIndex := c.ParamsInt64("index") - issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, issueIndex) - if err != nil { - c.Handle(http.StatusInternalServerError, "GetIssueByIndex", err) + + + total := h + m + + if total <= 0 { + c.Flash.Error(c.Tr("repo.issues.add_time_sum_to_small")) + c.Redirect(url, http.StatusSeeOther) return } @@ -51,13 +57,9 @@ func AddTimeManual(c *context.Context) { return } - url := issue.HTMLURL() c.Redirect(url, http.StatusSeeOther) } -func parseTimeTrackingWithDuration(value, space string) (time.Duration, error) { - if value == "" { - return 0, nil - } - return time.ParseDuration(value + space) +func parseTimeTrackingWithDuration(value int, space string) (time.Duration, error) { + return time.ParseDuration(strconv.Itoa(value) + space) } diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 2535eb3f0d360..c45561789e196 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -468,17 +468,17 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/watch", repo.IssueWatch) m.Combo("/comments").Post(bindIgnErr(auth.CreateCommentForm{}), repo.NewComment) m.Group("/times", func() { - m.Post("/add", repo.AddTimeManual) + m.Post("/add", bindIgnErr(auth.AddTimeManuallyForm{}), repo.AddTimeManually) m.Group("/stopwatch", func() { m.Post("/toggle", repo.IssueStopwatch) m.Post("/cancel", repo.CancelStopwatch) - }, func(ctx *context.Context) { - if !ctx.Repo.CanUseTimetracker(repo.GetActionIssue(ctx), ctx.User) { - ctx.Handle(404, ctx.Req.RequestURI, nil) - return - } }) + }, func(ctx *context.Context) { + if !ctx.Repo.CanUseTimetracker(repo.GetActionIssue(ctx), ctx.User) { + ctx.Handle(404, ctx.Req.RequestURI, nil) + return + } }) }) diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index 46cc30b2ad1ab..28a2c410748ca 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -138,7 +138,7 @@
{{else}} - {{if .HasUserAStopwatch}} + {{if .HasUserStopwatch}}
{{.i18n.Tr "repo.issues.tracking_already_started" .OtherStopwatchURL | Safe}}
@@ -153,7 +153,6 @@ {{$.CsrfTokenHtml}} -
diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index c3fdb0ceabcf2..c1f00751df924 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -148,7 +148,7 @@
- +
From 10be62fa58e9f615829b025d78e58da21fe60370 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Tue, 1 Aug 2017 12:01:50 +0200 Subject: [PATCH 64/88] Added integration test Signed-off-by: Jonas Franz --- integrations/timetracking_test.go | 93 +++++++++++++++++++ models/fixtures/repo_unit.yml | 4 +- models/issue_tracked_time.go | 2 +- modules/auth/repo_form.go | 4 +- routers/repo/issue_timetrack.go | 8 +- .../repo/issue/view_content/sidebar.tmpl | 12 +-- 6 files changed, 107 insertions(+), 16 deletions(-) create mode 100644 integrations/timetracking_test.go diff --git a/integrations/timetracking_test.go b/integrations/timetracking_test.go new file mode 100644 index 0000000000000..0745adfbb68f5 --- /dev/null +++ b/integrations/timetracking_test.go @@ -0,0 +1,93 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integrations + +import ( + "github.com/stretchr/testify/assert" + "net/http" + "path" + "testing" +) + +func TestViewTimetrackingControls(t *testing.T) { + prepareTestEnv(t) + session := loginUser(t, "user2") + testViewTimetrackingControls(t, session, "user2", "repo1", "1", true) + //user2/repo1 +} + +func TestNotViewTimetrackingControls(t *testing.T) { + prepareTestEnv(t) + session := loginUser(t, "user3") + testViewTimetrackingControls(t, session, "user2", "repo1", "1", false) + //user2/repo1 +} + +func testViewTimetrackingControls(t *testing.T, session *TestSession, user, repo, issue string, canView bool) { + req := NewRequest(t, "GET", path.Join(user, repo, "issues", issue)) + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + + if start, exists := htmlDoc.doc.Find(".timetrack.start-add-tracking.start-tracking").Attr("onclick"); exists { + assert.Equal(t, "this.disabled=true;toggleStopwatch()", start) + } else { + assert.True(t, (canView && exists) || (!canView || !exists)) + } + if addTime, exists := htmlDoc.doc.Find(".timetrack.start-add-tracking.add-time").Attr("onclick"); exists { + assert.Equal(t, "timeAddManual()", addTime) + } else { + assert.True(t, (canView && exists) || (!canView || !exists)) + } + + req = NewRequestWithValues(t, "POST", path.Join(user, repo, "issues", issue, "times", "stopwatch", "toggle"), map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + }) + if canView { + session.MakeRequest(t, req, http.StatusSeeOther) + } else { + session.MakeRequest(t, req, http.StatusNotFound) + } + + req = NewRequest(t, "GET", path.Join(user, repo, "issues", issue)) + resp = session.MakeRequest(t, req, http.StatusOK) + + htmlDoc = NewHTMLParser(t, resp.Body) + if stop, exists := htmlDoc.doc.Find(".timetrack.stop-cancel-tracking.stop").Attr("onclick"); exists { + assert.Equal(t, "this.disabled=true;toggleStopwatch()", stop) + } else { + assert.True(t, (canView && exists) || (!canView || !exists)) + } + if cancel, exists := htmlDoc.doc.Find(".timetrack.stop-cancel-tracking.cancel").Attr("onclick"); exists { + assert.Equal(t, "this.disabled=true;cancelStopwatch()", cancel) + } else { + assert.True(t, (canView && exists) || (!canView || !exists)) + } + + req = NewRequestWithValues(t, "POST", path.Join(user, repo, "issues", issue, "times", "stopwatch", "cancel"), map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + }) + if canView { + session.MakeRequest(t, req, http.StatusSeeOther) + } else { + session.MakeRequest(t, req, http.StatusNotFound) + } + + req = NewRequest(t, "GET", path.Join(user, repo, "issues", issue)) + resp = session.MakeRequest(t, req, http.StatusOK) + + htmlDoc = NewHTMLParser(t, resp.Body) + + if start, exists := htmlDoc.doc.Find(".timetrack.start-add-tracking.start-tracking").Attr("onclick"); exists { + assert.Equal(t, "this.disabled=true;toggleStopwatch()", start) + } else { + assert.True(t, (canView && exists) || (!canView || !exists)) + } + if addTime, exists := htmlDoc.doc.Find(".timetrack.start-add-tracking.add-time").Attr("onclick"); exists { + assert.Equal(t, "timeAddManual()", addTime) + } else { + assert.True(t, (canView && exists) || (!canView || !exists)) + } +} diff --git a/models/fixtures/repo_unit.yml b/models/fixtures/repo_unit.yml index 02daa48277b6f..57cf35e198894 100644 --- a/models/fixtures/repo_unit.yml +++ b/models/fixtures/repo_unit.yml @@ -11,7 +11,7 @@ repo_id: 1 type: 2 index: 1 - config: "{}" + config: "{\"EnableTimetracker\":true,\"AllowOnlyContributorsToTrackTime\":true}" created_unix: 946684810 - @@ -51,7 +51,7 @@ repo_id: 3 type: 2 index: 1 - config: "{}" + config: "{\"EnableTimetracker\":false,\"AllowOnlyContributorsToTrackTime\":false}" created_unix: 946684810 - diff --git a/models/issue_tracked_time.go b/models/issue_tracked_time.go index 407d6116025d5..0be75ff061567 100644 --- a/models/issue_tracked_time.go +++ b/models/issue_tracked_time.go @@ -7,8 +7,8 @@ package models import ( "time" - "github.com/go-xorm/xorm" "github.com/go-xorm/builder" + "github.com/go-xorm/xorm" ) // TrackedTime represents a time that was spent for a specific issue. diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go index 3f6ede75d49f6..967a5a2cd4c1c 100644 --- a/modules/auth/repo_form.go +++ b/modules/auth/repo_form.go @@ -418,9 +418,9 @@ func (f *DeleteRepoFileForm) Validate(ctx *macaron.Context, errs binding.Errors) // |____| |__|__|_| /\___ > |____| |__| (____ /\___ >__|_ \\___ >__| // \/ \/ \/ \/ \/ \/ -// AddTimeManually form that adds spent time manually. +// AddTimeManuallyForm form that adds spent time manually. type AddTimeManuallyForm struct { - Hours int `binding:"Range(0,1000)"` + Hours int `binding:"Range(0,1000)"` Minutes int `binding:"Range(0,1000)"` } diff --git a/routers/repo/issue_timetrack.go b/routers/repo/issue_timetrack.go index d237bbaff679d..98b7fd47731fd 100644 --- a/routers/repo/issue_timetrack.go +++ b/routers/repo/issue_timetrack.go @@ -5,12 +5,12 @@ package repo import ( - "time" - "strconv" "net/http" + "strconv" + "time" - "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/context" ) @@ -42,8 +42,6 @@ func AddTimeManually(c *context.Context, form auth.AddTimeManuallyForm) { return } - - total := h + m if total <= 0 { diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index 28a2c410748ca..817e6c4bc4231 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -133,9 +133,9 @@ {{$.CsrfTokenHtml}} {{if $.IsStopwatchRunning}} -
- - +
+ +
{{else}} {{if .HasUserStopwatch}} @@ -143,9 +143,9 @@ {{.i18n.Tr "repo.issues.tracking_already_started" .OtherStopwatchURL | Safe}}
{{end}} -
- - +
+ + {{if $.IssueWatch}} -
+
-
- {{.i18n.Tr "notification.notifications"}} -
-
- - {{$.CsrfTokenHtml}} - -
+
+ {{.i18n.Tr "notification.notifications"}} +
+
+ + {{$.CsrfTokenHtml}} + +
+
-
+ {{end}} {{if .Repository.IsTimetrackerEnabled }} - {{if .CanUseTimetracker }} -
-
- {{.i18n.Tr "repo.issues.tracker"}} -
+ {{if .CanUseTimetracker }} +
+
+ {{.i18n.Tr "repo.issues.tracker"}} +
{{$.CsrfTokenHtml}}
@@ -153,7 +154,6 @@ {{$.CsrfTokenHtml}} -
@@ -163,33 +163,30 @@
{{end}} - -
-
- {{end}} - {{if gt (len .WorkingUsers) 0}} -
-
- {{.i18n.Tr "repo.issues.time_spent_total"}} -
- {{range $user, $trackedtime := .WorkingUsers}} -
- - - -
- {{$user.DisplayName}} -
- {{$trackedtime}} -
- {{end}} - -
-
- {{end}} - {{end}} + {{end}} + {{if gt (len .WorkingUsers) 0}} +
+
+ {{.i18n.Tr "repo.issues.time_spent_total"}} +
+ {{range $user, $trackedtime := .WorkingUsers}} +
+ + + +
+ {{$user.DisplayName}} +
+ {{$trackedtime}} +
+
+
+ {{end}} +
+
+ {{end}} {{end}}
diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index 29bdaf343ebd1..b2b7fc9c3a955 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -134,11 +134,11 @@
- +
-
+
@@ -155,7 +155,7 @@
- +
From 528dd2d98e43878a0c159ed7088c3ba2ae92c008 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Mon, 28 Aug 2017 21:23:23 +0200 Subject: [PATCH 80/88] Updating swagger spec Signed-off-by: Jonas Franz --- public/swagger.v1.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/public/swagger.v1.json b/public/swagger.v1.json index f5eb2f9a9668c..e640b4e83160d 100644 --- a/public/swagger.v1.json +++ b/public/swagger.v1.json @@ -1416,6 +1416,9 @@ "200": { "$ref": "#/responses/TrackedTime" }, + "400": { + "$ref": "#/responses/error" + }, "403": { "$ref": "#/responses/error" }, @@ -1504,7 +1507,7 @@ "200": { "$ref": "#/responses/TrackedTimes" }, - "404": { + "400": { "$ref": "#/responses/error" }, "500": { @@ -1526,6 +1529,9 @@ "200": { "$ref": "#/responses/TrackedTimes" }, + "400": { + "$ref": "#/responses/error" + }, "404": { "$ref": "#/responses/error" }, From 16de1d2ecc466d8ab2f67360d56fcd11541cc276 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Tue, 29 Aug 2017 11:41:00 +0200 Subject: [PATCH 81/88] Fixing integration test caused be translation-changes Signed-off-by: Jonas Franz --- integrations/timetracking_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/timetracking_test.go b/integrations/timetracking_test.go index 4d8b9d0e76e80..2eb4c2a1e42b4 100644 --- a/integrations/timetracking_test.go +++ b/integrations/timetracking_test.go @@ -66,7 +66,7 @@ func testViewTimetrackingControls(t *testing.T, session *TestSession, user, repo htmlDoc = NewHTMLParser(t, resp.Body) events = htmlDoc.doc.Find(".event > span.text") - assert.Contains(t, events.Last().Text(), "finished working") + assert.Contains(t, events.Last().Text(), "stopped working") htmlDoc.AssertElement(t, ".event .detail .octicon-clock", true) } else { session.MakeRequest(t, req, http.StatusNotFound) From 83a73d4b33b6931117cfd056df04c86ea3e3a3a1 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Thu, 31 Aug 2017 21:12:47 +0200 Subject: [PATCH 82/88] Removed encoding issues in local_en-US.ini. "Added" copyright line Signed-off-by: Jonas Franz --- modules/context/repo.go | 1 + options/locale/locale_en-US.ini | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/context/repo.go b/modules/context/repo.go index 784472dd7d544..e335eafde3816 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -1,3 +1,4 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. // Copyright 2017 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 5d90a6b5a5a85..1c6f27615bdbc 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -500,14 +500,14 @@ migrate.clone_local_path = or local server path migrate.permission_denied = You are not allowed to import local repositories. migrate.invalid_local_path = Invalid local path, it does not exist or not a directory. migrate.failed = Migration failed: %v -migrate.lfs_mirror_unsupported = Mirroring LFS objects is not supported - use 'git?lfs?fetch?--all' and 'git?lfs?push?--all' instead. +migrate.lfs_mirror_unsupported = Mirroring LFS objects is not supported - use 'git lfs fetch?--all' and 'git lfs push --all' instead. mirror_from = mirror of forked_from = forked from fork_from_self = You cannot fork a repository you already own! copy_link = Copy copy_link_success = Copied! -copy_link_error = Press ?-C or Ctrl-C to copy +copy_link_error = Press ⌘-C or Ctrl-C to copy copied = Copied OK unwatch = Unwatch watch = Watch @@ -1260,7 +1260,7 @@ auths.tip.github = Register a new OAuth application on https://github.com/settin auths.tip.gitlab = Register a new application on https://gitlab.com/profile/applications auths.tip.google_plus = Obtain OAuth2 client credentials from the Google API console (https://console.developers.google.com/) auths.tip.openid_connect = Use the OpenID Connect Discovery URL (/.well-known/openid-configuration) to specify the endpoints -auths.tip.twitter = Go to https://dev.twitter.com/apps , create an application and ensure that the ?Allow this application to be used to Sign in with Twitter? option is enabled. +auths.tip.twitter = Go to https://dev.twitter.com/apps , create an application and ensure that the ”Allow this application to be used to Sign in with Twitter” option is enabled. auths.edit = Edit Authentication Settings auths.activated = This authentication is activated auths.new_success = The authentication '%s' has been added. From 3d117fce1a91ae510a746d8aef866d93cb32a221 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Sat, 2 Sep 2017 12:12:12 +0200 Subject: [PATCH 83/88] Moved unit.IssuesConfig().EnableTimetracker into a != nil check Removed some other encoding issues in local_en-US.ini Signed-off-by: Jonas Franz --- options/locale/locale_en-US.ini | 4 ++-- routers/repo/repo.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 1c6f27615bdbc..1fbfaa3ad1204 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -500,7 +500,7 @@ migrate.clone_local_path = or local server path migrate.permission_denied = You are not allowed to import local repositories. migrate.invalid_local_path = Invalid local path, it does not exist or not a directory. migrate.failed = Migration failed: %v -migrate.lfs_mirror_unsupported = Mirroring LFS objects is not supported - use 'git lfs fetch?--all' and 'git lfs push --all' instead. +migrate.lfs_mirror_unsupported = Mirroring LFS objects is not supported - use 'git lfs fetch --all' and 'git lfs push --all' instead. mirror_from = mirror of forked_from = forked from @@ -1260,7 +1260,7 @@ auths.tip.github = Register a new OAuth application on https://github.com/settin auths.tip.gitlab = Register a new application on https://gitlab.com/profile/applications auths.tip.google_plus = Obtain OAuth2 client credentials from the Google API console (https://console.developers.google.com/) auths.tip.openid_connect = Use the OpenID Connect Discovery URL (/.well-known/openid-configuration) to specify the endpoints -auths.tip.twitter = Go to https://dev.twitter.com/apps , create an application and ensure that the ”Allow this application to be used to Sign in with Twitter” option is enabled. +auths.tip.twitter = Go to https://dev.twitter.com/apps , create an application and ensure that the “Allow this application to be used to Sign in with Twitter” option is enabled. auths.edit = Edit Authentication Settings auths.activated = This authentication is activated auths.new_success = The authentication '%s' has been added. diff --git a/routers/repo/repo.go b/routers/repo/repo.go index 297a131c997b3..6f8fc3a3d5411 100644 --- a/routers/repo/repo.go +++ b/routers/repo/repo.go @@ -146,9 +146,9 @@ func CreatePost(ctx *context.Context, form auth.CreateRepoForm) { if errDelete := models.DeleteRepository(ctxUser.ID, repo.ID); errDelete != nil { log.Error(4, "DeleteRepository: %v", errDelete) } - } - if unit := repo.Units[models.UnitTypeIssues]; unit != nil { - unit.IssuesConfig().EnableTimetracker = setting.Service.DefaultEnableTimetracking + if unit := repo.Units[models.UnitTypeIssues]; unit != nil { + unit.IssuesConfig().EnableTimetracker = setting.Service.DefaultEnableTimetracking + } } handleCreateError(ctx, ctxUser, err, "CreatePost", tplCreate, &form) From e2244a8ba3d33413f53aace3d567c029fd900e1b Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Sun, 3 Sep 2017 01:44:15 +0200 Subject: [PATCH 84/88] Improved javascript by checking if data-context exists Signed-off-by: Jonas Franz --- public/js/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/public/js/index.js b/public/js/index.js index ee605cff69cb4..732beaca0cb64 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -404,19 +404,19 @@ function initRepository() { $('.enable-system').change(function () { if (this.checked) { $($(this).data('target')).removeClass('disabled'); - if ($(this).data('context') != undefined) $($(this).data('context')).addClass('disabled'); + if (!$(this).data('context')) $($(this).data('context')).addClass('disabled'); } else { $($(this).data('target')).addClass('disabled'); - if ($(this).data('context') != undefined) $($(this).data('context')).removeClass('disabled'); + if (!$(this).data('context')) $($(this).data('context')).removeClass('disabled'); } }); $('.enable-system-radio').change(function () { if (this.value == 'false') { $($(this).data('target')).addClass('disabled'); - if ($(this).data('context') != undefined) $($(this).data('context')).removeClass('disabled'); + if (typeof $(this).data('context') !== 'undefined') $($(this).data('context')).removeClass('disabled'); } else if (this.value == 'true') { $($(this).data('target')).removeClass('disabled'); - if ($(this).data('context') != undefined) $($(this).data('context')).addClass('disabled'); + if (typeof $(this).data('context') !== 'undefined') $($(this).data('context')).addClass('disabled'); } }); } From 52be3f59e0bd513879b794dccecb6395d5d1c9a1 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Sun, 3 Sep 2017 23:16:47 +0200 Subject: [PATCH 85/88] Replaced manual comment creation with CreateComment Removed unnecessary code Improved error checking Small cosmetic changes Signed-off-by: Jonas Franz --- integrations/html_helper.go | 10 ++++ integrations/timetracking_test.go | 10 ---- models/issue_stopwatch.go | 70 +++++++++++------------ models/issue_stopwatch_test.go | 36 +++++++++--- models/issue_tracked_time.go | 45 +++++++-------- models/issue_tracked_time_test.go | 31 ++++++---- routers/api/v1/repo/issue_tracked_time.go | 2 +- routers/repo/issue.go | 4 +- routers/repo/issue_stopwatch.go | 6 +- routers/repo/issue_timetrack.go | 6 +- routers/repo/repo.go | 3 - 11 files changed, 122 insertions(+), 101 deletions(-) diff --git a/integrations/html_helper.go b/integrations/html_helper.go index 43e75db30d18f..c181a25b5769d 100644 --- a/integrations/html_helper.go +++ b/integrations/html_helper.go @@ -40,3 +40,13 @@ func (doc *HTMLDoc) GetInputValueByName(name string) string { func (doc *HTMLDoc) GetCSRF() string { return doc.GetInputValueByName("_csrf") } + +// AssertElement check if element by selector exists or does not exist depending on checkExists +func (doc *HTMLDoc) AssertElement(t testing.TB, selector string, checkExists bool) { + sel := doc.doc.Find(selector) + if checkExists { + assert.Equal(t, 1, sel.Length()) + } else { + assert.Equal(t, 0, sel.Length()) + } +} diff --git a/integrations/timetracking_test.go b/integrations/timetracking_test.go index 2eb4c2a1e42b4..e92cb95a8969c 100644 --- a/integrations/timetracking_test.go +++ b/integrations/timetracking_test.go @@ -72,13 +72,3 @@ func testViewTimetrackingControls(t *testing.T, session *TestSession, user, repo session.MakeRequest(t, req, http.StatusNotFound) } } - -// AssertElement check if element by selector exists or does not exist depending on checkExists -func (doc *HTMLDoc) AssertElement(t testing.TB, selector string, checkExists bool) { - sel := doc.doc.Find(selector) - if checkExists { - assert.Equal(t, 1, sel.Length()) - } else { - assert.Equal(t, 0, sel.Length()) - } -} diff --git a/models/issue_stopwatch.go b/models/issue_stopwatch.go index 50146de05b1bb..3b5b4d57f37e1 100644 --- a/models/issue_stopwatch.go +++ b/models/issue_stopwatch.go @@ -20,6 +20,12 @@ type Stopwatch struct { CreatedUnix int64 } +// BeforeInsert will be invoked by XORM before inserting a record +// representing this object. +func (s *Stopwatch) BeforeInsert() { + s.CreatedUnix = time.Now().Unix() +} + // AfterSet is invoked from XORM after setting the value of a field of this object. func (s *Stopwatch) AfterSet(colName string, _ xorm.Cell) { switch colName { @@ -54,8 +60,8 @@ func HasUserStopwatch(userID int64) (exists bool, sw *Stopwatch, err error) { } // CreateOrStopIssueStopwatch will create or remove a stopwatch and will log it into issue's timeline. -func CreateOrStopIssueStopwatch(userID int64, issueID int64) error { - sw, exists, err := getStopwatch(x, userID, issueID) +func CreateOrStopIssueStopwatch(user *User, issue *Issue) error { + sw, exists, err := getStopwatch(x, user.ID, issue.ID) if err != nil { return err } @@ -66,34 +72,32 @@ func CreateOrStopIssueStopwatch(userID int64, issueID int64) error { // Create TrackedTime tt := &TrackedTime{ Created: time.Now(), - IssueID: issueID, - UserID: userID, + IssueID: issue.ID, + UserID: user.ID, Time: timediff, } if _, err := x.Insert(tt); err != nil { return err } - // Add comment referencing to the tracked time - comment := &Comment{ - IssueID: issueID, - PosterID: userID, - Type: CommentTypeStopTracking, - Content: secToTime(timediff), - } - if _, err := x.Insert(comment); err != nil { + if _, err := CreateComment(&CreateCommentOptions{ + Doer: user, + Issue: issue, + Repo: issue.Repo, + Content: secToTime(timediff), + Type: CommentTypeStopTracking, + }); err != nil { return err } - if _, err := x.Delete(sw); err != nil { return err } } else { // Create stopwatch sw = &Stopwatch{ - UserID: userID, - IssueID: issueID, + UserID: user.ID, + IssueID: issue.ID, Created: time.Now(), } @@ -101,14 +105,12 @@ func CreateOrStopIssueStopwatch(userID int64, issueID int64) error { return err } - // Add comment referencing to the stopwatch - comment := &Comment{ - IssueID: issueID, - PosterID: userID, - Type: CommentTypeStartTracking, - } - - if _, err := x.Insert(comment); err != nil { + if _, err := CreateComment(&CreateCommentOptions{ + Doer: user, + Issue: issue, + Repo: issue.Repo, + Type: CommentTypeStartTracking, + }); err != nil { return err } } @@ -116,8 +118,8 @@ func CreateOrStopIssueStopwatch(userID int64, issueID int64) error { } // CancelStopwatch removes the given stopwatch and logs it into issue's timeline. -func CancelStopwatch(userID int64, issueID int64) error { - sw, exists, err := getStopwatch(x, userID, issueID) +func CancelStopwatch(user *User, issue *Issue) error { + sw, exists, err := getStopwatch(x, user.ID, issue.ID) if err != nil { return err } @@ -126,13 +128,13 @@ func CancelStopwatch(userID int64, issueID int64) error { if _, err := x.Delete(sw); err != nil { return err } - comment := &Comment{ - PosterID: userID, - IssueID: issueID, - Type: CommentTypeCancelTracking, - } - if _, err := x.Insert(comment); err != nil { + if _, err := CreateComment(&CreateCommentOptions{ + Doer: user, + Issue: issue, + Repo: issue.Repo, + Type: CommentTypeCancelTracking, + }); err != nil { return err } } @@ -166,9 +168,3 @@ func secToTime(duration int64) string { return hrs } - -// BeforeInsert will be invoked by XORM before inserting a record -// representing this object. -func (s *Stopwatch) BeforeInsert() { - s.CreatedUnix = time.Now().Unix() -} diff --git a/models/issue_stopwatch_test.go b/models/issue_stopwatch_test.go index e5b72fb704e0b..9875066e537c7 100644 --- a/models/issue_stopwatch_test.go +++ b/models/issue_stopwatch_test.go @@ -10,13 +10,21 @@ import ( func TestCancelStopwatch(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) - err := CancelStopwatch(1, 1) + user1, err := GetUserByID(1) assert.NoError(t, err) - AssertNotExistsBean(t, &Stopwatch{UserID: 1, IssueID: 1}) - _ = AssertExistsAndLoadBean(t, &Comment{Type: CommentTypeCancelTracking, PosterID: 1, IssueID: 1}) + issue1, err := GetIssueByID(1) + assert.NoError(t, err) + issue2, err := GetIssueByID(2) + assert.NoError(t, err) + + err = CancelStopwatch(user1, issue1) + assert.NoError(t, err) + AssertNotExistsBean(t, &Stopwatch{UserID: user1.ID, IssueID: issue1.ID}) - assert.Nil(t, CancelStopwatch(1, 2)) + _ = AssertExistsAndLoadBean(t, &Comment{Type: CommentTypeCancelTracking, PosterID: user1.ID, IssueID: issue1.ID}) + + assert.Nil(t, CancelStopwatch(user1, issue2)) } func TestStopwatchExists(t *testing.T) { @@ -30,23 +38,33 @@ func TestHasUserStopwatch(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) exists, sw, err := HasUserStopwatch(1) - assert.True(t, exists) - assert.Equal(t, sw.ID, int64(1)) assert.NoError(t, err) + assert.True(t, exists) + assert.Equal(t, int64(1), sw.ID) exists, _, err = HasUserStopwatch(3) - assert.False(t, exists) assert.NoError(t, err) + assert.False(t, exists) } func TestCreateOrStopIssueStopwatch(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) - assert.NoError(t, CreateOrStopIssueStopwatch(3, 1)) + user2, err := GetUserByID(2) + assert.NoError(t, err) + user3, err := GetUserByID(3) + assert.NoError(t, err) + + issue1, err := GetIssueByID(1) + assert.NoError(t, err) + issue2, err := GetIssueByID(2) + assert.NoError(t, err) + + assert.NoError(t, CreateOrStopIssueStopwatch(user3, issue1)) sw := AssertExistsAndLoadBean(t, &Stopwatch{UserID: 3, IssueID: 1}).(*Stopwatch) assert.Equal(t, true, sw.Created.Before(time.Now())) - assert.NoError(t, CreateOrStopIssueStopwatch(2, 2)) + assert.NoError(t, CreateOrStopIssueStopwatch(user2, issue2)) AssertNotExistsBean(t, &Stopwatch{UserID: 2, IssueID: 2}) AssertExistsAndLoadBean(t, &TrackedTime{UserID: 2, IssueID: 2}) } diff --git a/models/issue_tracked_time.go b/models/issue_tracked_time.go index 8f58814a94975..33914dbb1faee 100644 --- a/models/issue_tracked_time.go +++ b/models/issue_tracked_time.go @@ -21,6 +21,12 @@ type TrackedTime struct { Time int64 `json:"time"` } +// BeforeInsert will be invoked by XORM before inserting a record +// representing this object. +func (t *TrackedTime) BeforeInsert() { + t.CreatedUnix = time.Now().Unix() +} + // AfterSet is invoked from XORM after setting the value of a field of this object. func (t *TrackedTime) AfterSet(colName string, _ xorm.Cell) { switch colName { @@ -61,29 +67,23 @@ func GetTrackedTimes(options FindTrackedTimesOptions) (trackedTimes []*TrackedTi return } -// BeforeInsert will be invoked by XORM before inserting a record -// representing this object. -func (t *TrackedTime) BeforeInsert() { - t.CreatedUnix = time.Now().Unix() -} - // AddTime will add the given time (in seconds) to the issue -func AddTime(userID int64, issueID int64, time int64) (*TrackedTime, error) { +func AddTime(user *User, issue *Issue, time int64) (*TrackedTime, error) { tt := &TrackedTime{ - IssueID: issueID, - UserID: userID, + IssueID: issue.ID, + UserID: user.ID, Time: time, } if _, err := x.Insert(tt); err != nil { return nil, err } - comment := &Comment{ - IssueID: issueID, - PosterID: userID, - Type: CommentTypeAddTimeManual, - Content: secToTime(time), - } - if _, err := x.Insert(comment); err != nil { + if _, err := CreateComment(&CreateCommentOptions{ + Issue: issue, + Repo: issue.Repo, + Doer: user, + Content: secToTime(time), + Type: CommentTypeAddTimeManual, + }); err != nil { return nil, err } return tt, nil @@ -98,19 +98,18 @@ func TotalTimes(options FindTrackedTimesOptions) (map[*User]string, error) { //Adding total time per user ID totalTimesByUser := make(map[int64]int64) for _, t := range trackedTimes { - if total, ok := totalTimesByUser[t.UserID]; !ok { - totalTimesByUser[t.UserID] = t.Time - } else { - totalTimesByUser[t.UserID] = total + t.Time - } + totalTimesByUser[t.UserID] += t.Time } totalTimes := make(map[*User]string) //Fetching User and making time human readable for userID, total := range totalTimesByUser { user, err := GetUserByID(userID) - if err != nil || user == nil { - continue + if err != nil { + if IsErrUserNotExist(err) { + continue + } + return nil, err } totalTimes[user] = secToTime(total) } diff --git a/models/issue_tracked_time_test.go b/models/issue_tracked_time_test.go index 02d8be341517e..130e8f33e244b 100644 --- a/models/issue_tracked_time_test.go +++ b/models/issue_tracked_time_test.go @@ -8,8 +8,15 @@ import ( func TestAddTime(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) + + user3, err := GetUserByID(3) + assert.NoError(t, err) + + issue1, err := GetIssueByID(1) + assert.NoError(t, err) + //3661 = 1h 1min 1s - trackedTime, err := AddTime(3, 1, 3661) + trackedTime, err := AddTime(user3, issue1, 3661) assert.NoError(t, err) assert.Equal(t, int64(3), trackedTime.UserID) assert.Equal(t, int64(1), trackedTime.IssueID) @@ -27,70 +34,70 @@ func TestGetTrackedTimes(t *testing.T) { // by Issue times, err := GetTrackedTimes(FindTrackedTimesOptions{IssueID: 1}) + assert.NoError(t, err) assert.Len(t, times, 1) assert.Equal(t, times[0].Time, int64(400)) - assert.NoError(t, err) times, err = GetTrackedTimes(FindTrackedTimesOptions{IssueID: -1}) - assert.Len(t, times, 0) assert.NoError(t, err) + assert.Len(t, times, 0) // by User times, err = GetTrackedTimes(FindTrackedTimesOptions{UserID: 1}) + assert.NoError(t, err) assert.Len(t, times, 1) assert.Equal(t, times[0].Time, int64(400)) - assert.NoError(t, err) times, err = GetTrackedTimes(FindTrackedTimesOptions{UserID: 3}) - assert.Len(t, times, 0) assert.NoError(t, err) + assert.Len(t, times, 0) // by Repo times, err = GetTrackedTimes(FindTrackedTimesOptions{RepositoryID: 2}) + assert.NoError(t, err) assert.Len(t, times, 1) assert.Equal(t, times[0].Time, int64(1)) - assert.NoError(t, err) issue, err := GetIssueByID(times[0].IssueID) assert.NoError(t, err) assert.Equal(t, issue.RepoID, int64(2)) times, err = GetTrackedTimes(FindTrackedTimesOptions{RepositoryID: 1}) - assert.Len(t, times, 4) assert.NoError(t, err) + assert.Len(t, times, 4) times, err = GetTrackedTimes(FindTrackedTimesOptions{RepositoryID: 10}) - assert.Len(t, times, 0) assert.NoError(t, err) + assert.Len(t, times, 0) } func TestTotalTimes(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) total, err := TotalTimes(FindTrackedTimesOptions{IssueID: 1}) + assert.NoError(t, err) assert.Len(t, total, 1) for user, time := range total { assert.Equal(t, int64(1), user.ID) assert.Equal(t, "6min 40s", time) } - assert.NoError(t, err) total, err = TotalTimes(FindTrackedTimesOptions{IssueID: 2}) + assert.NoError(t, err) assert.Len(t, total, 1) for user, time := range total { assert.Equal(t, int64(2), user.ID) assert.Equal(t, "1h 1min 2s", time) } - assert.NoError(t, err) total, err = TotalTimes(FindTrackedTimesOptions{IssueID: 5}) + assert.NoError(t, err) assert.Len(t, total, 1) for user, time := range total { assert.Equal(t, int64(2), user.ID) assert.Equal(t, "1s", time) } - assert.NoError(t, err) total, err = TotalTimes(FindTrackedTimesOptions{IssueID: 4}) - assert.Len(t, total, 0) assert.NoError(t, err) + assert.Len(t, total, 0) } diff --git a/routers/api/v1/repo/issue_tracked_time.go b/routers/api/v1/repo/issue_tracked_time.go index 6382e6b554511..964fc11ddb678 100644 --- a/routers/api/v1/repo/issue_tracked_time.go +++ b/routers/api/v1/repo/issue_tracked_time.go @@ -74,7 +74,7 @@ func AddTime(ctx *context.APIContext, form api.AddTimeOption) { return } var tt *models.TrackedTime - if tt, err = models.AddTime(ctx.User.ID, issue.ID, form.Time); err != nil { + if tt, err = models.AddTime(ctx.User, issue, form.Time); err != nil { ctx.Error(500, "AddTime", err) return } diff --git a/routers/repo/issue.go b/routers/repo/issue.go index f30527d7bd060..0cd4edabb6ab9 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -597,7 +597,7 @@ func ViewIssue(ctx *context.Context) { var exists bool var sw *models.Stopwatch if exists, sw, err = models.HasUserStopwatch(ctx.User.ID); err != nil { - ctx.Handle(500, "HasUserAStopwatch", err) + ctx.Handle(500, "HasUserStopwatch", err) return } ctx.Data["HasUserStopwatch"] = exists @@ -605,7 +605,7 @@ func ViewIssue(ctx *context.Context) { // Add warning if the user has already a stopwatch var otherIssue *models.Issue if otherIssue, err = models.GetIssueByID(sw.IssueID); err != nil { - ctx.Handle(500, "HasUserAStopwatch", err) + ctx.Handle(500, "GetIssueByID", err) return } // Add link to the issue of the already running stopwatch diff --git a/routers/repo/issue_stopwatch.go b/routers/repo/issue_stopwatch.go index 7add8a1e1aef1..7e3121da9f7e5 100644 --- a/routers/repo/issue_stopwatch.go +++ b/routers/repo/issue_stopwatch.go @@ -11,7 +11,7 @@ import ( "code.gitea.io/gitea/modules/context" ) -// IssueStopwatch manges the stopwatch +// IssueStopwatch creates or stops a stopwatch for the given issue. func IssueStopwatch(c *context.Context) { issueIndex := c.ParamsInt64("index") issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, issueIndex) @@ -21,7 +21,7 @@ func IssueStopwatch(c *context.Context) { return } - if err := models.CreateOrStopIssueStopwatch(c.User.ID, issue.ID); err != nil { + if err := models.CreateOrStopIssueStopwatch(c.User, issue); err != nil { c.Handle(http.StatusInternalServerError, "CreateOrStopIssueStopwatch", err) return } @@ -40,7 +40,7 @@ func CancelStopwatch(c *context.Context) { return } - if err := models.CancelStopwatch(c.User.ID, issue.ID); err != nil { + if err := models.CancelStopwatch(c.User, issue); err != nil { c.Handle(http.StatusInternalServerError, "CancelStopwatch", err) return } diff --git a/routers/repo/issue_timetrack.go b/routers/repo/issue_timetrack.go index f28e374c5dd33..ed7d3d1697ee1 100644 --- a/routers/repo/issue_timetrack.go +++ b/routers/repo/issue_timetrack.go @@ -19,6 +19,10 @@ func AddTimeManually(c *context.Context, form auth.AddTimeManuallyForm) { issueIndex := c.ParamsInt64("index") issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, issueIndex) if err != nil { + if models.IsErrIssueNotExist(err) { + c.Handle(http.StatusNotFound, "GetIssueByIndex", err) + return + } c.Handle(http.StatusInternalServerError, "GetIssueByIndex", err) return } @@ -50,7 +54,7 @@ func AddTimeManually(c *context.Context, form auth.AddTimeManuallyForm) { return } - if _, err := models.AddTime(c.User.ID, issue.ID, int64(total.Seconds())); err != nil { + if _, err := models.AddTime(c.User, issue, int64(total.Seconds())); err != nil { c.Handle(http.StatusInternalServerError, "AddTime", err) return } diff --git a/routers/repo/repo.go b/routers/repo/repo.go index 6f8fc3a3d5411..5fcbb84b9a608 100644 --- a/routers/repo/repo.go +++ b/routers/repo/repo.go @@ -146,9 +146,6 @@ func CreatePost(ctx *context.Context, form auth.CreateRepoForm) { if errDelete := models.DeleteRepository(ctxUser.ID, repo.ID); errDelete != nil { log.Error(4, "DeleteRepository: %v", errDelete) } - if unit := repo.Units[models.UnitTypeIssues]; unit != nil { - unit.IssuesConfig().EnableTimetracker = setting.Service.DefaultEnableTimetracking - } } handleCreateError(ctx, ctxUser, err, "CreatePost", tplCreate, &form) From 17684f58c950d30fc072fac550de3791a1061cb4 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Mon, 4 Sep 2017 06:51:58 +0200 Subject: [PATCH 86/88] Replaced int>string>duration parsing with int>duration parsing Signed-off-by: Jonas Franz --- routers/repo/issue_timetrack.go | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/routers/repo/issue_timetrack.go b/routers/repo/issue_timetrack.go index ed7d3d1697ee1..9770c7e8e8f4b 100644 --- a/routers/repo/issue_timetrack.go +++ b/routers/repo/issue_timetrack.go @@ -34,19 +34,7 @@ func AddTimeManually(c *context.Context, form auth.AddTimeManuallyForm) { return } - h, err := parseTimeTrackingWithDuration(form.Hours, "h") - if err != nil { - c.Handle(http.StatusInternalServerError, "parseTimeTrackingWithDuration", err) - return - } - - m, err := parseTimeTrackingWithDuration(form.Minutes, "m") - if err != nil { - c.Handle(http.StatusInternalServerError, "parseTimeTrackingWithDuration", err) - return - } - - total := h + m + total := time.Duration(form.Hours) * time.Hour + time.Duration(form.Minutes) + time.Minute if total <= 0 { c.Flash.Error(c.Tr("repo.issues.add_time_sum_to_small")) @@ -54,14 +42,10 @@ func AddTimeManually(c *context.Context, form auth.AddTimeManuallyForm) { return } - if _, err := models.AddTime(c.User, issue, int64(total.Seconds())); err != nil { + if _, err := models.AddTime(c.User, issue, int64(total)); err != nil { c.Handle(http.StatusInternalServerError, "AddTime", err) return } c.Redirect(url, http.StatusSeeOther) } - -func parseTimeTrackingWithDuration(value int, space string) (time.Duration, error) { - return time.ParseDuration(strconv.Itoa(value) + space) -} From 3142af370288594ba9b2907bfee58f694eb202a8 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Mon, 4 Sep 2017 14:58:54 +0200 Subject: [PATCH 87/88] Fixed encoding issues Signed-off-by: Jonas Franz --- options/locale/locale_en-US.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 1fbfaa3ad1204..12ee268dc32f1 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -500,7 +500,7 @@ migrate.clone_local_path = or local server path migrate.permission_denied = You are not allowed to import local repositories. migrate.invalid_local_path = Invalid local path, it does not exist or not a directory. migrate.failed = Migration failed: %v -migrate.lfs_mirror_unsupported = Mirroring LFS objects is not supported - use 'git lfs fetch --all' and 'git lfs push --all' instead. +migrate.lfs_mirror_unsupported = Mirroring LFS objects is not supported - use 'git lfs fetch --all' and 'git lfs push --all' instead. mirror_from = mirror of forked_from = forked from From 3e08048441b3f37d2b2da0bb9f9a1c51b0652499 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Mon, 4 Sep 2017 15:01:37 +0200 Subject: [PATCH 88/88] Removed unused imports +gofmt Signed-off-by: Jonas Franz --- routers/repo/issue_timetrack.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/routers/repo/issue_timetrack.go b/routers/repo/issue_timetrack.go index 9770c7e8e8f4b..e01cd48a66a7f 100644 --- a/routers/repo/issue_timetrack.go +++ b/routers/repo/issue_timetrack.go @@ -6,7 +6,6 @@ package repo import ( "net/http" - "strconv" "time" "code.gitea.io/gitea/models" @@ -34,7 +33,7 @@ func AddTimeManually(c *context.Context, form auth.AddTimeManuallyForm) { return } - total := time.Duration(form.Hours) * time.Hour + time.Duration(form.Minutes) + time.Minute + total := time.Duration(form.Hours)*time.Hour + time.Duration(form.Minutes)*time.Minute if total <= 0 { c.Flash.Error(c.Tr("repo.issues.add_time_sum_to_small"))