mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
fix: critical fixes related to alias resolution
This commit is contained in:
parent
861c81e414
commit
c2d30826f6
@ -22,8 +22,8 @@ type BadgeData struct {
|
||||
|
||||
func NewBadgeDataFrom(summary *models.Summary, filters *models.Filters) *BadgeData {
|
||||
var total time.Duration
|
||||
if hasFilter, filterType, filterKey := filters.First(); hasFilter {
|
||||
total = summary.TotalTimeByKey(filterType, filterKey)
|
||||
if hasFilter, _, _ := filters.First(); hasFilter {
|
||||
total = summary.TotalTimeByFilters(filters)
|
||||
} else {
|
||||
total = summary.TotalTime()
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ type allTimeData struct {
|
||||
func NewAllTimeFrom(summary *models.Summary, filters *models.Filters) *AllTimeViewModel {
|
||||
var total time.Duration
|
||||
if key := filters.Project; key != "" {
|
||||
total = summary.TotalTimeByKey(models.SummaryProject, key)
|
||||
total = summary.TotalTimeByFilters(filters)
|
||||
} else {
|
||||
total = summary.TotalTime()
|
||||
}
|
||||
|
67
models/filters.go
Normal file
67
models/filters.go
Normal file
@ -0,0 +1,67 @@
|
||||
package models
|
||||
|
||||
type Filters struct {
|
||||
Project string
|
||||
OS string
|
||||
Language string
|
||||
Editor string
|
||||
Machine string
|
||||
}
|
||||
|
||||
type FilterElement struct {
|
||||
Type uint8
|
||||
Key string
|
||||
}
|
||||
|
||||
func NewFiltersWith(entity uint8, key string) *Filters {
|
||||
switch entity {
|
||||
case SummaryProject:
|
||||
return &Filters{Project: key}
|
||||
case SummaryOS:
|
||||
return &Filters{Project: key}
|
||||
case SummaryLanguage:
|
||||
return &Filters{Project: key}
|
||||
case SummaryEditor:
|
||||
return &Filters{Project: key}
|
||||
case SummaryMachine:
|
||||
return &Filters{Project: key}
|
||||
}
|
||||
return &Filters{}
|
||||
}
|
||||
|
||||
func (f *Filters) First() (bool, uint8, string) {
|
||||
if f.Project != "" {
|
||||
return true, SummaryProject, f.Project
|
||||
} else if f.OS != "" {
|
||||
return true, SummaryOS, f.OS
|
||||
} else if f.Language != "" {
|
||||
return true, SummaryLanguage, f.Language
|
||||
} else if f.Editor != "" {
|
||||
return true, SummaryEditor, f.Editor
|
||||
} else if f.Machine != "" {
|
||||
return true, SummaryMachine, f.Machine
|
||||
}
|
||||
return false, 0, ""
|
||||
}
|
||||
|
||||
func (f *Filters) All() []*FilterElement {
|
||||
all := make([]*FilterElement, 0)
|
||||
|
||||
if f.Project != "" {
|
||||
all = append(all, &FilterElement{Type: SummaryProject, Key: f.Project})
|
||||
}
|
||||
if f.Editor != "" {
|
||||
all = append(all, &FilterElement{Type: SummaryEditor, Key: f.Editor})
|
||||
}
|
||||
if f.Language != "" {
|
||||
all = append(all, &FilterElement{Type: SummaryLanguage, Key: f.Language})
|
||||
}
|
||||
if f.Machine != "" {
|
||||
all = append(all, &FilterElement{Type: SummaryMachine, Key: f.Machine})
|
||||
}
|
||||
if f.OS != "" {
|
||||
all = append(all, &FilterElement{Type: SummaryOS, Key: f.OS})
|
||||
}
|
||||
|
||||
return all
|
||||
}
|
@ -24,45 +24,6 @@ type KeyStringValue struct {
|
||||
Value string `gorm:"type:text"`
|
||||
}
|
||||
|
||||
type Filters struct {
|
||||
Project string
|
||||
OS string
|
||||
Language string
|
||||
Editor string
|
||||
Machine string
|
||||
}
|
||||
|
||||
func NewFiltersWith(entity uint8, key string) *Filters {
|
||||
switch entity {
|
||||
case SummaryProject:
|
||||
return &Filters{Project: key}
|
||||
case SummaryOS:
|
||||
return &Filters{Project: key}
|
||||
case SummaryLanguage:
|
||||
return &Filters{Project: key}
|
||||
case SummaryEditor:
|
||||
return &Filters{Project: key}
|
||||
case SummaryMachine:
|
||||
return &Filters{Project: key}
|
||||
}
|
||||
return &Filters{}
|
||||
}
|
||||
|
||||
func (f *Filters) First() (bool, uint8, string) {
|
||||
if f.Project != "" {
|
||||
return true, SummaryProject, f.Project
|
||||
} else if f.OS != "" {
|
||||
return true, SummaryOS, f.OS
|
||||
} else if f.Language != "" {
|
||||
return true, SummaryLanguage, f.Language
|
||||
} else if f.Editor != "" {
|
||||
return true, SummaryEditor, f.Editor
|
||||
} else if f.Machine != "" {
|
||||
return true, SummaryMachine, f.Machine
|
||||
}
|
||||
return false, 0, ""
|
||||
}
|
||||
|
||||
type CustomTime time.Time
|
||||
|
||||
func (j *CustomTime) UnmarshalJSON(b []byte) error {
|
||||
|
@ -147,31 +147,32 @@ func (s *Summary) TotalTime() time.Duration {
|
||||
return timeSum * time.Second
|
||||
}
|
||||
|
||||
func (s *Summary) TotalTimeBy(entityType uint8) time.Duration {
|
||||
var timeSum time.Duration
|
||||
|
||||
func (s *Summary) TotalTimeBy(entityType uint8) (timeSum time.Duration) {
|
||||
mappedItems := s.MappedItems()
|
||||
if items := mappedItems[entityType]; len(*items) > 0 {
|
||||
for _, item := range *items {
|
||||
timeSum += item.Total
|
||||
timeSum = timeSum + item.Total*time.Second
|
||||
}
|
||||
}
|
||||
|
||||
return timeSum * time.Second
|
||||
return timeSum
|
||||
}
|
||||
|
||||
func (s *Summary) TotalTimeByKey(entityType uint8, key string) time.Duration {
|
||||
var timeSum time.Duration
|
||||
|
||||
func (s *Summary) TotalTimeByKey(entityType uint8, key string) (timeSum time.Duration) {
|
||||
mappedItems := s.MappedItems()
|
||||
if items := mappedItems[entityType]; len(*items) > 0 {
|
||||
for _, item := range *items {
|
||||
if item.Key != key {
|
||||
continue
|
||||
}
|
||||
timeSum += item.Total
|
||||
timeSum = timeSum + item.Total*time.Second
|
||||
}
|
||||
}
|
||||
|
||||
return timeSum * time.Second
|
||||
return timeSum
|
||||
}
|
||||
|
||||
func (s *Summary) TotalTimeByFilters(filter *Filters) (timeSum time.Duration) {
|
||||
for _, f := range filter.All() {
|
||||
timeSum += s.TotalTimeByKey(f.Type, f.Key)
|
||||
}
|
||||
return timeSum
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ const (
|
||||
type BadgeHandler struct {
|
||||
userSrvc *services.UserService
|
||||
summarySrvc *services.SummaryService
|
||||
aliasSrvc *services.AliasService
|
||||
config *config2.Config
|
||||
}
|
||||
|
||||
@ -57,18 +58,18 @@ func (h *BadgeHandler) ApiGet(w http.ResponseWriter, r *http.Request) {
|
||||
interval = groups[1]
|
||||
}
|
||||
|
||||
filters := &models.Filters{}
|
||||
var filters *models.Filters
|
||||
switch filterEntity {
|
||||
case "project":
|
||||
filters.Project = filterKey
|
||||
filters = models.NewFiltersWith(models.SummaryProject, filterKey)
|
||||
case "os":
|
||||
filters.OS = filterKey
|
||||
filters = models.NewFiltersWith(models.SummaryOS, filterKey)
|
||||
case "editor":
|
||||
filters.Editor = filterKey
|
||||
filters = models.NewFiltersWith(models.SummaryEditor, filterKey)
|
||||
case "language":
|
||||
filters.Language = filterKey
|
||||
filters = models.NewFiltersWith(models.SummaryLanguage, filterKey)
|
||||
case "machine":
|
||||
filters.Machine = filterKey
|
||||
filters = models.NewFiltersWith(models.SummaryMachine, filterKey)
|
||||
}
|
||||
|
||||
summary, err, status := h.loadUserSummary(user, interval)
|
||||
@ -94,7 +95,9 @@ func (h *BadgeHandler) loadUserSummary(user *models.User, interval string) (*mod
|
||||
User: user,
|
||||
}
|
||||
|
||||
summary, err := h.summarySrvc.Construct(summaryParams.From, summaryParams.To, summaryParams.User, summaryParams.Recompute)
|
||||
summary, err := h.summarySrvc.PostProcessWrapped(
|
||||
h.summarySrvc.Construct(summaryParams.From, summaryParams.To, summaryParams.User, summaryParams.Recompute),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err, http.StatusInternalServerError
|
||||
}
|
||||
|
@ -55,7 +55,9 @@ func (h *AllTimeHandler) loadUserSummary(user *models.User) (*models.Summary, er
|
||||
Recompute: false,
|
||||
}
|
||||
|
||||
summary, err := h.summarySrvc.Construct(summaryParams.From, summaryParams.To, summaryParams.User, summaryParams.Recompute) // 'to' is always constant
|
||||
summary, err := h.summarySrvc.PostProcessWrapped(
|
||||
h.summarySrvc.Construct(summaryParams.From, summaryParams.To, summaryParams.User, summaryParams.Recompute), // 'to' is always constant
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err, http.StatusInternalServerError
|
||||
}
|
||||
|
@ -86,7 +86,9 @@ func (h *SummariesHandler) loadUserSummaries(r *http.Request) ([]*models.Summary
|
||||
summaries := make([]*models.Summary, len(intervals))
|
||||
|
||||
for i, interval := range intervals {
|
||||
summary, err := h.summarySrvc.Construct(interval[0], interval[1], user, false) // 'to' is always constant
|
||||
summary, err := h.summarySrvc.PostProcessWrapped(
|
||||
h.summarySrvc.Construct(interval[0], interval[1], user, false), // 'to' is always constant
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err, http.StatusInternalServerError
|
||||
}
|
||||
|
@ -69,7 +69,9 @@ func (h *SummaryHandler) loadUserSummary(r *http.Request) (*models.Summary, erro
|
||||
return nil, err, http.StatusBadRequest
|
||||
}
|
||||
|
||||
summary, err := h.summarySrvc.Construct(summaryParams.From, summaryParams.To, summaryParams.User, summaryParams.Recompute) // 'to' is always constant
|
||||
summary, err := h.summarySrvc.PostProcessWrapped(
|
||||
h.summarySrvc.Construct(summaryParams.From, summaryParams.To, summaryParams.User, summaryParams.Recompute), // 'to' is always constant
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err, http.StatusInternalServerError
|
||||
}
|
||||
|
@ -150,6 +150,62 @@ func (srv *SummaryService) Construct(from, to time.Time, user *models.User, reco
|
||||
return summary, nil
|
||||
}
|
||||
|
||||
func (srv *SummaryService) PostProcessWrapped(summary *models.Summary, err error) (*models.Summary, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return srv.PostProcess(summary), nil
|
||||
}
|
||||
|
||||
func (srv *SummaryService) PostProcess(summary *models.Summary) *models.Summary {
|
||||
updatedSummary := &models.Summary{
|
||||
ID: summary.ID,
|
||||
UserID: summary.UserID,
|
||||
FromTime: summary.FromTime,
|
||||
ToTime: summary.ToTime,
|
||||
}
|
||||
|
||||
processAliases := func(origin []*models.SummaryItem) []*models.SummaryItem {
|
||||
target := make([]*models.SummaryItem, 0)
|
||||
|
||||
findItem := func(key string) *models.SummaryItem {
|
||||
for _, item := range target {
|
||||
if item.Key == key {
|
||||
return item
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, item := range origin {
|
||||
// Add all "top-level" items, i.e. such without aliases
|
||||
if key, _ := srv.AliasService.GetAliasOrDefault(summary.UserID, item.Type, item.Key); key == item.Key {
|
||||
target = append(target, item)
|
||||
}
|
||||
}
|
||||
|
||||
for _, item := range origin {
|
||||
// Add all remaining projects and merge with their alias
|
||||
if key, _ := srv.AliasService.GetAliasOrDefault(summary.UserID, item.Type, item.Key); key != item.Key {
|
||||
if targetItem := findItem(key); targetItem != nil {
|
||||
targetItem.Total += item.Total
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return target
|
||||
}
|
||||
|
||||
// Resolve aliases
|
||||
updatedSummary.Projects = processAliases(summary.Projects)
|
||||
updatedSummary.Editors = processAliases(summary.Editors)
|
||||
updatedSummary.Languages = processAliases(summary.Languages)
|
||||
updatedSummary.OperatingSystems = processAliases(summary.OperatingSystems)
|
||||
updatedSummary.Machines = processAliases(summary.Machines)
|
||||
|
||||
return updatedSummary
|
||||
}
|
||||
|
||||
func (srv *SummaryService) Insert(summary *models.Summary) error {
|
||||
if err := srv.Db.Create(summary).Error; err != nil {
|
||||
return err
|
||||
@ -210,10 +266,6 @@ func (srv *SummaryService) aggregateBy(heartbeats []*models.Heartbeat, summaryTy
|
||||
key = models.UnknownSummaryKey
|
||||
}
|
||||
|
||||
if aliasedKey, err := srv.AliasService.GetAliasOrDefault(user.ID, summaryType, key); err == nil {
|
||||
key = aliasedKey
|
||||
}
|
||||
|
||||
if _, ok := durations[key]; !ok {
|
||||
durations[key] = time.Duration(0)
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
1.13.0
|
||||
1.13.1
|
||||
|
Loading…
Reference in New Issue
Block a user