From 30510591eb1c67b0601da97a6abf6f6018ebfa82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?= Date: Sat, 13 Feb 2021 12:59:59 +0100 Subject: [PATCH] feat: custom time intervals (resolve #115) --- config.default.yml | 2 +- models/summary.go | 1 + models/user.go | 1 + repositories/user.go | 1 + routes/api/heartbeat.go | 15 ++++++-- routes/api/metrics.go | 2 +- routes/routes.go | 20 ++++++----- routes/summary.go | 1 + static/assets/app.css | 5 +++ static/assets/app.js | 19 ++++++++-- utils/common.go | 8 +++++ utils/summary.go | 14 +++++--- views/settings.tpl.html | 4 +-- views/summary.tpl.html | 79 ++++++++++++++++++++++++----------------- 14 files changed, 118 insertions(+), 54 deletions(-) diff --git a/config.default.yml b/config.default.yml index 07a9112..6fbec91 100644 --- a/config.default.yml +++ b/config.default.yml @@ -27,7 +27,7 @@ db: security: password_salt: # CHANGE ! - insecure_cookies: false + insecure_cookies: false # You need to set this to 'true' when on localhost cookie_max_age: 172800 allow_signup: true expose_metrics: false \ No newline at end of file diff --git a/models/summary.go b/models/summary.go index df34256..0c62fce 100644 --- a/models/summary.go +++ b/models/summary.go @@ -47,6 +47,7 @@ type SummaryItemContainer struct { type SummaryViewModel struct { *Summary + User *User LanguageColors map[string]string EditorColors map[string]string OSColors map[string]string diff --git a/models/user.go b/models/user.go index 79b6b83..f6a72f7 100644 --- a/models/user.go +++ b/models/user.go @@ -13,6 +13,7 @@ type User struct { ShareOSs bool `json:"-" gorm:"default:false; type:bool; column:share_oss"` ShareMachines bool `json:"-" gorm:"default:false; type:bool"` IsAdmin bool `json:"-" gorm:"default:false; type:bool"` + HasData bool `json:"-" gorm:"default:false; type:bool"` WakatimeApiKey string `json:"-"` } diff --git a/repositories/user.go b/repositories/user.go index 0fcb048..bf196fb 100644 --- a/repositories/user.go +++ b/repositories/user.go @@ -117,6 +117,7 @@ func (r *UserRepository) Update(user *models.User) (*models.User, error) { "share_projects": user.ShareProjects, "share_machines": user.ShareMachines, "wakatime_api_key": user.WakatimeApiKey, + "has_data": user.HasData, } result := r.db.Model(user).Updates(updateMap) diff --git a/routes/api/heartbeat.go b/routes/api/heartbeat.go index f5da92e..8ebe1c3 100644 --- a/routes/api/heartbeat.go +++ b/routes/api/heartbeat.go @@ -73,7 +73,7 @@ func (h *HeartbeatApiHandler) Post(w http.ResponseWriter, r *http.Request) { if !hb.Valid() { w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("Invalid heartbeat object.")) + w.Write([]byte("invalid heartbeat object")) return } @@ -82,10 +82,21 @@ func (h *HeartbeatApiHandler) Post(w http.ResponseWriter, r *http.Request) { if err := h.heartbeatSrvc.InsertBatch(heartbeats); err != nil { w.WriteHeader(http.StatusInternalServerError) - logbuch.Error(err.Error()) + w.Write([]byte(conf.ErrInternalServerError)) + logbuch.Error("failed to batch-insert heartbeats – %v", err) return } + if !user.HasData { + user.HasData = true + if _, err := h.userSrvc.Update(user); err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(conf.ErrInternalServerError)) + logbuch.Error("failed to update user – %v", err) + return + } + } + utils.RespondJSON(w, http.StatusCreated, constructSuccessResponse(len(heartbeats))) } diff --git a/routes/api/metrics.go b/routes/api/metrics.go index c9e64e1..59a44a1 100644 --- a/routes/api/metrics.go +++ b/routes/api/metrics.go @@ -218,7 +218,7 @@ func (h *MetricsHandler) getAdminMetrics(user *models.User) (*mm.Metrics, error) activeUsers, err := h.userSrvc.GetActive() if err != nil { - logbuch.Error("failed to retrieve active users for metric", err) + logbuch.Error("failed to retrieve active users for metric – %v", err) return nil, err } diff --git a/routes/routes.go b/routes/routes.go index 05615a5..65e54c9 100644 --- a/routes/routes.go +++ b/routes/routes.go @@ -24,15 +24,17 @@ var templates map[string]*template.Template func loadTemplates() { const tplPath = "/views" tpls := template.New("").Funcs(template.FuncMap{ - "json": utils.Json, - "date": utils.FormatDateHuman, - "title": strings.Title, - "join": strings.Join, - "add": utils.Add, - "capitalize": utils.Capitalize, - "toRunes": utils.ToRunes, - "entityTypes": models.SummaryTypes, - "typeName": typeName, + "json": utils.Json, + "date": utils.FormatDateHuman, + "simpledate": utils.FormatDate, + "simpledatetime": utils.FormatDateTime, + "title": strings.Title, + "join": strings.Join, + "add": utils.Add, + "capitalize": utils.Capitalize, + "toRunes": utils.ToRunes, + "entityTypes": models.SummaryTypes, + "typeName": typeName, "getBasePath": func() string { return config.Get().Server.BasePath }, diff --git a/routes/summary.go b/routes/summary.go index 1c6f488..c1979d2 100644 --- a/routes/summary.go +++ b/routes/summary.go @@ -62,6 +62,7 @@ func (h *SummaryHandler) GetIndex(w http.ResponseWriter, r *http.Request) { vm := models.SummaryViewModel{ Summary: summary, + User: user, LanguageColors: utils.FilterColors(h.config.App.GetLanguageColors(), summary.Languages), EditorColors: utils.FilterColors(h.config.App.GetEditorColors(), summary.Editors), OSColors: utils.FilterColors(h.config.App.GetOSColors(), summary.OperatingSystems), diff --git a/static/assets/app.css b/static/assets/app.css index a04403a..b42a0af 100644 --- a/static/assets/app.css +++ b/static/assets/app.css @@ -4,4 +4,9 @@ body { .bg-gray-850 { background-color: #242b3a; +} + +::-webkit-calendar-picker-indicator { + filter: invert(1); + cursor: pointer; } \ No newline at end of file diff --git a/static/assets/app.js b/static/assets/app.js index 950bff0..e62e147 100644 --- a/static/assets/app.js +++ b/static/assets/app.js @@ -49,6 +49,20 @@ String.prototype.toHHMMSS = function () { return hours + ':' + minutes + ':' + seconds } +String.prototype.toHHMM = function () { + var sec_num = parseInt(this, 10) + var hours = Math.floor(sec_num / 3600) + var minutes = Math.floor((sec_num - (hours * 3600)) / 60) + + if (hours < 10 && hours > 0) { + hours = '0' + hours + } + if (minutes < 10 && minutes > 0) { + minutes = '0' + minutes + } + return hours + ':' + minutes +} + function draw(subselection) { function getTooltipOptions(key) { return { @@ -319,8 +333,9 @@ function equalizeHeights() { function getTotal(items) { const el = document.getElementById('total-span') if (!el) return - let total = items.reduce((acc, d) => acc + d.total, 0) - el.innerText = total.toString().toHHMMSS() + const total = items.reduce((acc, d) => acc + d.total, 0) + const formatted = total.toString().toHHMM() + el.innerText = `${formatted.split(':')[0]} hours, ${formatted.split(':')[1]} minutes` } function getRandomColor(seed) { diff --git a/utils/common.go b/utils/common.go index a314be1..8d0978b 100644 --- a/utils/common.go +++ b/utils/common.go @@ -8,10 +8,18 @@ import ( ) func ParseDate(date string) (time.Time, error) { + return time.Parse(config.SimpleDateFormat, date) +} + +func ParseDateTime(date string) (time.Time, error) { return time.Parse(config.SimpleDateTimeFormat, date) } func FormatDate(date time.Time) string { + return date.Format(config.SimpleDateFormat) +} + +func FormatDateTime(date time.Time) string { return date.Format(config.SimpleDateTimeFormat) } diff --git a/utils/summary.go b/utils/summary.go index 4ab8716..0796933 100644 --- a/utils/summary.go +++ b/utils/summary.go @@ -82,14 +82,20 @@ func ParseSummaryParams(r *http.Request) (*models.SummaryParams, error) { } else if start := params.Get("start"); start != "" { err, from, to = ResolveIntervalRaw(start) } else { - from, err = ParseDate(params.Get("from")) + from, err = ParseDateTime(params.Get("from")) if err != nil { - return nil, errors.New("missing 'from' parameter") + from, err = ParseDate(params.Get("from")) + if err != nil { + return nil, errors.New("missing 'from' parameter") + } } - to, err = ParseDate(params.Get("to")) + to, err = ParseDateTime(params.Get("to")) if err != nil { - return nil, errors.New("missing 'to' parameter") + to, err = ParseDate(params.Get("to")) + if err != nil { + return nil, errors.New("missing 'to' parameter") + } } } diff --git a/views/settings.tpl.html b/views/settings.tpl.html index f2e8c27..6681c7b 100644 --- a/views/settings.tpl.html +++ b/views/settings.tpl.html @@ -22,7 +22,7 @@ {{ template "header.tpl.html" . }}
-
+
← Go back

Settings

         
@@ -32,7 +32,7 @@ {{ template "alerts.tpl.html" . }}
-
+
diff --git a/views/summary.tpl.html b/views/summary.tpl.html index 5735da6..41124a3 100644 --- a/views/summary.tpl.html +++ b/views/summary.tpl.html @@ -36,43 +36,56 @@
-

Your Coding Statistics 🤓

+

Summary

-
- Today - Yesterday - This Week - This Month - This Year - Past 7 Days - Past 30 Days - Past 12 Months - All Time +{{ if .User.HasData }} + +
+
+
+ + +
+
+ + +
+
+ +
+
+ +
+{{ end }} + {{ template "alerts.tpl.html" . }}
-
-
-
-

▶️ {{ .FromTime.T | date }}

-

⏹️ {{ .ToTime.T | date }}

-

- ⏱️ - {{ if gt .Summary.TotalTime 0 }} - - {{ else }} - No Data - {{ end }} -

-
-
-
+ {{ if .User.HasData }} - {{ if or (gt .Summary.TotalTime 0) (ne .RawQuery "") }} + + ⏱️  + Showing a total of + + (from {{ .FromTime.T | date }} to {{ .ToTime.T | date }}) + +
@@ -87,7 +100,7 @@
@@ -103,7 +116,7 @@
@@ -119,7 +132,7 @@
@@ -135,7 +148,7 @@ @@ -151,7 +164,7 @@