From c771b8ddf47c273d2eae9178c9a19256e7b7389b Mon Sep 17 00:00:00 2001 From: Gusted Date: Sun, 27 Feb 2022 15:05:07 +0100 Subject: [PATCH 1/7] Improve Stopwatch behavior - Don't send empty stopwatch over and over again, only send once. - Stop interval to update stopwatch's timer when there is no more stopwatch. --- routers/web/events/events.go | 9 +++++++++ web_src/js/features/stopwatch.js | 1 + 2 files changed, 10 insertions(+) diff --git a/routers/web/events/events.go b/routers/web/events/events.go index 41f52375c3f55..19ab9dcd7dd8b 100644 --- a/routers/web/events/events.go +++ b/routers/web/events/events.go @@ -72,6 +72,7 @@ func Events(ctx *context.Context) { timer := time.NewTicker(30 * time.Second) stopwatchTimer := time.NewTicker(setting.UI.Notification.MinTimeout) + prevStopwatchEmpty := false loop: for { @@ -99,6 +100,14 @@ loop: log.Error("Unable to GetUserStopwatches: %v", err) continue } + if len(sws) == 0 { + if prevStopwatchEmpty { + continue + } + prevStopwatchEmpty = true + } else { + prevStopwatchEmpty = false + } apiSWs, err := convert.ToStopWatches(sws) if err != nil { log.Error("Unable to APIFormat stopwatches: %v", err) diff --git a/web_src/js/features/stopwatch.js b/web_src/js/features/stopwatch.js index 1748c5119c2ca..b66757c5ec914 100644 --- a/web_src/js/features/stopwatch.js +++ b/web_src/js/features/stopwatch.js @@ -127,6 +127,7 @@ function updateStopwatchData(data) { const watch = data[0]; const btnEl = $('.active-stopwatch-trigger'); if (!watch) { + clearInterval(updateTimeInterval); btnEl.addClass('hidden'); } else { const {repo_owner_name, repo_name, issue_index, seconds} = watch; From 0514e7971b7a5579993a6f3d6a945dfe22c4bc29 Mon Sep 17 00:00:00 2001 From: Gusted Date: Sun, 27 Feb 2022 15:17:03 +0000 Subject: [PATCH 2/7] Apply code suggestion Co-authored-by: wxiaoguang --- web_src/js/features/stopwatch.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/web_src/js/features/stopwatch.js b/web_src/js/features/stopwatch.js index b66757c5ec914..e6ce79e89a16f 100644 --- a/web_src/js/features/stopwatch.js +++ b/web_src/js/features/stopwatch.js @@ -127,7 +127,10 @@ function updateStopwatchData(data) { const watch = data[0]; const btnEl = $('.active-stopwatch-trigger'); if (!watch) { - clearInterval(updateTimeInterval); + if (updateTimeInterval) { + clearInterval(updateTimeInterval); + updateTimeInterval = null; + } btnEl.addClass('hidden'); } else { const {repo_owner_name, repo_name, issue_index, seconds} = watch; From 90a60cb6c36fb611cf7d1ef4119d0f789149e137 Mon Sep 17 00:00:00 2001 From: Gusted Date: Sun, 27 Feb 2022 16:33:20 +0100 Subject: [PATCH 3/7] Fix linter --- web_src/js/features/stopwatch.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web_src/js/features/stopwatch.js b/web_src/js/features/stopwatch.js index e6ce79e89a16f..89241b4ae5646 100644 --- a/web_src/js/features/stopwatch.js +++ b/web_src/js/features/stopwatch.js @@ -127,10 +127,10 @@ function updateStopwatchData(data) { const watch = data[0]; const btnEl = $('.active-stopwatch-trigger'); if (!watch) { - if (updateTimeInterval) { - clearInterval(updateTimeInterval); - updateTimeInterval = null; - } + if (updateTimeInterval) { + clearInterval(updateTimeInterval); + updateTimeInterval = null; + } btnEl.addClass('hidden'); } else { const {repo_owner_name, repo_name, issue_index, seconds} = watch; From e769c241ed1c0e67439d13f87d2c6d4fdbaf9317 Mon Sep 17 00:00:00 2001 From: Gusted Date: Mon, 28 Feb 2022 21:17:37 +0100 Subject: [PATCH 4/7] Improve loop behavior --- models/issue_stopwatch.go | 32 ++++++++++++++++++++++ modules/eventsource/manager_run.go | 25 +++++++++++++++++ routers/web/events/events.go | 42 ----------------------------- routers/web/repo/issue_stopwatch.go | 14 ++++++++++ 4 files changed, 71 insertions(+), 42 deletions(-) diff --git a/models/issue_stopwatch.go b/models/issue_stopwatch.go index 3be9ad4e3f415..0e0cb87d506b2 100644 --- a/models/issue_stopwatch.go +++ b/models/issue_stopwatch.go @@ -66,6 +66,38 @@ func getStopwatch(ctx context.Context, userID, issueID int64) (sw *Stopwatch, ex return } +// UserIDCount is a simple coalition of UserID and Count +type UserStopwatch struct { + UserID int64 + StopWatches []*Stopwatch +} + +// GetUIDsAndNotificationCounts between the two provided times +func GetUIDsAndStopwatch() ([]*UserStopwatch, error) { + sws := []*Stopwatch{} + if err := db.GetEngine(db.DefaultContext).Find(&sws); err != nil { + return nil, err + } + if len(sws) == 0 { + return []*UserStopwatch{}, nil + } + + lastUserID := int64(-1) + res := []*UserStopwatch{} + for _, sw := range sws { + if lastUserID == sw.UserID { + lastUserStopwatch := res[len(res)-1] + lastUserStopwatch.StopWatches = append(lastUserStopwatch.StopWatches, sw) + } else { + res = append(res, &UserStopwatch{ + UserID: sw.UserID, + StopWatches: []*Stopwatch{sw}, + }) + } + } + return res, nil +} + // GetUserStopwatches return list of all stopwatches of a user func GetUserStopwatches(userID int64, listOptions db.ListOptions) ([]*Stopwatch, error) { sws := make([]*Stopwatch, 0, 8) diff --git a/modules/eventsource/manager_run.go b/modules/eventsource/manager_run.go index 60598ecb495f5..3b5cf984ae7b8 100644 --- a/modules/eventsource/manager_run.go +++ b/modules/eventsource/manager_run.go @@ -9,7 +9,9 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/graceful" + "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" @@ -63,6 +65,29 @@ loop: } } + usersStopwatches, err := models.GetUIDsAndStopwatch() + if err != nil { + log.Error("Unable to get GetUIDsAndStopwatch: %v", err) + return + } + + for _, userStopwatches := range usersStopwatches { + apiSWs, err := convert.ToStopWatches(userStopwatches.StopWatches) + if err != nil { + log.Error("Unable to APIFormat stopwatches: %v", err) + continue + } + dataBs, err := json.Marshal(apiSWs) + if err != nil { + log.Error("Unable to marshal stopwatches: %v", err) + continue + } + m.SendMessage(userStopwatches.UserID, &Event{ + Name: "stopwatches", + Data: string(dataBs), + }) + } + now := timeutil.TimeStampNow().Add(-2) uidCounts, err := models.GetUIDsAndNotificationCounts(then, now) diff --git a/routers/web/events/events.go b/routers/web/events/events.go index 19ab9dcd7dd8b..38d25ac9ffdf1 100644 --- a/routers/web/events/events.go +++ b/routers/web/events/events.go @@ -8,15 +8,10 @@ import ( "net/http" "time" - "code.gitea.io/gitea/models" - "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/context" - "code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/eventsource" "code.gitea.io/gitea/modules/graceful" - "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/routers/web/auth" ) @@ -71,9 +66,6 @@ func Events(ctx *context.Context) { timer := time.NewTicker(30 * time.Second) - stopwatchTimer := time.NewTicker(setting.UI.Notification.MinTimeout) - prevStopwatchEmpty := false - loop: for { select { @@ -94,40 +86,6 @@ loop: case <-shutdownCtx.Done(): go unregister() break loop - case <-stopwatchTimer.C: - sws, err := models.GetUserStopwatches(ctx.User.ID, db.ListOptions{}) - if err != nil { - log.Error("Unable to GetUserStopwatches: %v", err) - continue - } - if len(sws) == 0 { - if prevStopwatchEmpty { - continue - } - prevStopwatchEmpty = true - } else { - prevStopwatchEmpty = false - } - apiSWs, err := convert.ToStopWatches(sws) - if err != nil { - log.Error("Unable to APIFormat stopwatches: %v", err) - continue - } - dataBs, err := json.Marshal(apiSWs) - if err != nil { - log.Error("Unable to marshal stopwatches: %v", err) - continue - } - _, err = (&eventsource.Event{ - Name: "stopwatches", - Data: string(dataBs), - }).WriteTo(ctx.Resp) - if err != nil { - log.Error("Unable to write to EventStream for user %s: %v", ctx.User.Name, err) - go unregister() - break loop - } - ctx.Resp.Flush() case event, ok := <-messageChan: if !ok { break loop diff --git a/routers/web/repo/issue_stopwatch.go b/routers/web/repo/issue_stopwatch.go index 0e9405fde4dbd..4cba5c8bdd59f 100644 --- a/routers/web/repo/issue_stopwatch.go +++ b/routers/web/repo/issue_stopwatch.go @@ -9,7 +9,9 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/eventsource" ) // IssueStopwatch creates or stops a stopwatch for the given issue. @@ -59,6 +61,18 @@ func CancelStopwatch(c *context.Context) { return } + stopwatches, err := models.GetUserStopwatches(c.User.ID, db.ListOptions{}) + if err != nil { + c.ServerError("GetUserStopwatches", err) + return + } + if len(stopwatches) == 0 { + eventsource.GetManager().SendMessage(c.User.ID, &eventsource.Event{ + Name: "stopwatches", + Data: "{}", + }) + } + url := issue.HTMLURL() c.Redirect(url, http.StatusSeeOther) } From 1f300b49bf0a748804a038a588762c213751454c Mon Sep 17 00:00:00 2001 From: Gusted Date: Tue, 15 Mar 2022 17:22:44 +0100 Subject: [PATCH 5/7] Only do Stopwatch stuff when enabled --- modules/eventsource/manager_run.go | 38 ++++++++++++++++-------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/modules/eventsource/manager_run.go b/modules/eventsource/manager_run.go index 3b5cf984ae7b8..e8dcd99feb72d 100644 --- a/modules/eventsource/manager_run.go +++ b/modules/eventsource/manager_run.go @@ -65,27 +65,29 @@ loop: } } - usersStopwatches, err := models.GetUIDsAndStopwatch() - if err != nil { - log.Error("Unable to get GetUIDsAndStopwatch: %v", err) - return - } - - for _, userStopwatches := range usersStopwatches { - apiSWs, err := convert.ToStopWatches(userStopwatches.StopWatches) + if setting.Service.EnableTimetracking { + usersStopwatches, err := models.GetUIDsAndStopwatch() if err != nil { - log.Error("Unable to APIFormat stopwatches: %v", err) - continue + log.Error("Unable to get GetUIDsAndStopwatch: %v", err) + return } - dataBs, err := json.Marshal(apiSWs) - if err != nil { - log.Error("Unable to marshal stopwatches: %v", err) - continue + + for _, userStopwatches := range usersStopwatches { + apiSWs, err := convert.ToStopWatches(userStopwatches.StopWatches) + if err != nil { + log.Error("Unable to APIFormat stopwatches: %v", err) + continue + } + dataBs, err := json.Marshal(apiSWs) + if err != nil { + log.Error("Unable to marshal stopwatches: %v", err) + continue + } + m.SendMessage(userStopwatches.UserID, &Event{ + Name: "stopwatches", + Data: string(dataBs), + }) } - m.SendMessage(userStopwatches.UserID, &Event{ - Name: "stopwatches", - Data: string(dataBs), - }) } now := timeutil.TimeStampNow().Add(-2) From cc12262c4ca2a54a50c1ad15f4aaea8e41f55612 Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Sun, 27 Mar 2022 03:58:51 +0100 Subject: [PATCH 6/7] Fix broken merge Signed-off-by: Andrew Thornton --- routers/web/repo/issue_stopwatch.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routers/web/repo/issue_stopwatch.go b/routers/web/repo/issue_stopwatch.go index 55f252686616d..2cc808f5d804f 100644 --- a/routers/web/repo/issue_stopwatch.go +++ b/routers/web/repo/issue_stopwatch.go @@ -61,13 +61,13 @@ func CancelStopwatch(c *context.Context) { return } - stopwatches, err := models.GetUserStopwatches(c.User.ID, db.ListOptions{}) + stopwatches, err := models.GetUserStopwatches(c.Doer.ID, db.ListOptions{}) if err != nil { c.ServerError("GetUserStopwatches", err) return } if len(stopwatches) == 0 { - eventsource.GetManager().SendMessage(c.User.ID, &eventsource.Event{ + eventsource.GetManager().SendMessage(c.Doer.ID, &eventsource.Event{ Name: "stopwatches", Data: "{}", }) From 951793c88414cabfa124c967637cc60b3ec33e86 Mon Sep 17 00:00:00 2001 From: Gusted Date: Mon, 28 Mar 2022 02:08:07 +0200 Subject: [PATCH 7/7] Fix test - The current test isn't expecting the noise from stopwatch, so simply but hacky move up the code for notications. No behavior changes or anything. --- modules/eventsource/manager_run.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/modules/eventsource/manager_run.go b/modules/eventsource/manager_run.go index e8dcd99feb72d..ff805f0eb0369 100644 --- a/modules/eventsource/manager_run.go +++ b/modules/eventsource/manager_run.go @@ -65,6 +65,20 @@ loop: } } + now := timeutil.TimeStampNow().Add(-2) + + uidCounts, err := models.GetUIDsAndNotificationCounts(then, now) + if err != nil { + log.Error("Unable to get UIDcounts: %v", err) + } + for _, uidCount := range uidCounts { + m.SendMessage(uidCount.UserID, &Event{ + Name: "notification-count", + Data: uidCount, + }) + } + then = now + if setting.Service.EnableTimetracking { usersStopwatches, err := models.GetUIDsAndStopwatch() if err != nil { @@ -89,20 +103,6 @@ loop: }) } } - - now := timeutil.TimeStampNow().Add(-2) - - uidCounts, err := models.GetUIDsAndNotificationCounts(then, now) - if err != nil { - log.Error("Unable to get UIDcounts: %v", err) - } - for _, uidCount := range uidCounts { - m.SendMessage(uidCount.UserID, &Event{ - Name: "notification-count", - Data: uidCount, - }) - } - then = now } } m.UnregisterAll()