Files
wakapi/models/duration.go

137 lines
3.7 KiB
Go

package models
import (
"fmt"
"github.com/cespare/xxhash/v2"
"github.com/gohugoio/hashstructure"
"github.com/muety/wakapi/models/lib"
"log/slog"
"strings"
"time"
"unicode"
)
// TODO: support multiple durations per time per user for different heartbeat timeouts
// see discussion at https://github.com/muety/wakapi/issues/675
type Duration struct {
ID int64 `json:"-" gorm:"primaryKey; autoIncrement"` // https://github.com/muety/wakapi/issues/777
UserID string `json:"user_id" gorm:"not null; index:idx_time_duration_user"`
Time CustomTime `json:"time" hash:"ignore" gorm:"not null; index:idx_time_duration_user"` // time of first heartbeat of this duration
Duration time.Duration `json:"duration" hash:"ignore" gorm:"not null"`
Project string `json:"project"`
Language string `json:"language"`
Editor string `json:"editor"`
OperatingSystem string `json:"operating_system"`
Machine string `json:"machine"`
Category string `json:"category"`
Branch string `json:"branch"`
Entity string `json:"Entity"`
NumHeartbeats int `json:"-" hash:"ignore"`
GroupHash string `json:"-" hash:"ignore" gorm:"type:varchar(17)"`
Timeout time.Duration `json:"-" gorm:"not null; default:600000000000"` // heartbeat timeout preference, see DefaultHeartbeatsTimeout
excludeEntity bool `json:"-" hash:"ignore"`
}
func (d *Duration) TimeEnd() time.Time {
return d.Time.T().Add(d.Duration)
}
func (d *Duration) HashInclude(field string, v interface{}) (bool, error) {
if field == "Entity" {
return !d.excludeEntity, nil
}
if field == "Time" ||
field == "Duration" ||
field == "NumHeartbeats" ||
field == "GroupHash" ||
field == "ID" ||
field == "Timeout" ||
unicode.IsLower(rune(field[0])) {
return false, nil
}
return true, nil
}
func NewDurationFromHeartbeat(h *Heartbeat) *Duration {
var interval = DefaultHeartbeatsTimeout
if h.User != nil && h.User.HeartbeatsTimeout() > 0 {
interval = h.User.HeartbeatsTimeout()
}
d := &Duration{
UserID: h.UserID,
Time: h.Time,
Duration: 0,
Project: h.Project,
Language: h.Language,
Editor: h.Editor,
OperatingSystem: h.OperatingSystem,
Machine: h.Machine,
Category: h.Category,
Branch: h.Branch,
Entity: h.Entity,
NumHeartbeats: 1,
Timeout: interval,
}
return d
}
func (d *Duration) WithEntityIgnored() *Duration {
d.excludeEntity = true
return d
}
func (d *Duration) WithTimeout(interval time.Duration) *Duration {
d.Timeout = interval
return d
}
func (d *Duration) Hashed() *Duration {
hash, err := hashstructure.Hash(d, &hashstructure.HashOptions{Hasher: xxhash.New()})
if err != nil {
slog.Error("CRITICAL ERROR: failed to hash struct", "error", err)
}
d.GroupHash = fmt.Sprintf("%x", hash)
return d
}
func (d *Duration) Augmented(languageMappings map[string]string) *Duration {
for ext, targetLang := range languageMappings {
langs, ok := lib.LanguagesByExtension["."+ext]
if !ok {
continue
}
if lang := langs[0]; strings.ToLower(d.Language) == strings.ToLower(lang) {
d.Language = targetLang
}
}
return d
}
func (d *Duration) GetKey(t uint8) (key string) {
switch t {
case SummaryProject:
key = d.Project
case SummaryEditor:
key = d.Editor
case SummaryLanguage:
key = d.Language
case SummaryOS:
key = d.OperatingSystem
case SummaryMachine:
key = d.Machine
case SummaryBranch:
key = d.Branch
case SummaryEntity:
key = d.Entity
case SummaryCategory:
key = d.Category
}
if key == "" {
key = UnknownSummaryKey
}
return key
}