mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
feat: implement file statistics (resolve #80)
This commit is contained in:
@@ -77,6 +77,8 @@ func NewStatsFrom(summary *models.Summary, filters *models.Filters) *StatsViewMo
|
||||
branches[i] = convertEntry(e, summary.TotalTimeBy(models.SummaryBranch))
|
||||
}
|
||||
|
||||
// entities omitted intentionally
|
||||
|
||||
data.Editors = editors
|
||||
data.Languages = languages
|
||||
data.Machines = machines
|
||||
|
||||
@@ -35,6 +35,7 @@ type SummariesData struct {
|
||||
OperatingSystems []*SummariesEntry `json:"operating_systems"`
|
||||
Projects []*SummariesEntry `json:"projects"`
|
||||
Branches []*SummariesEntry `json:"branches,omitempty"`
|
||||
Entities []*SummariesEntry `json:"entities,omitempty"`
|
||||
GrandTotal *SummariesGrandTotal `json:"grand_total"`
|
||||
Range *SummariesRange `json:"range"`
|
||||
}
|
||||
@@ -132,8 +133,8 @@ func newDataFrom(s *models.Summary) *SummariesData {
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(6)
|
||||
|
||||
wg.Add(1)
|
||||
go func(data *SummariesData) {
|
||||
defer wg.Done()
|
||||
for i, e := range s.Projects {
|
||||
@@ -141,6 +142,7 @@ func newDataFrom(s *models.Summary) *SummariesData {
|
||||
}
|
||||
}(data)
|
||||
|
||||
wg.Add(1)
|
||||
go func(data *SummariesData) {
|
||||
defer wg.Done()
|
||||
for i, e := range s.Editors {
|
||||
@@ -148,6 +150,7 @@ func newDataFrom(s *models.Summary) *SummariesData {
|
||||
}
|
||||
}(data)
|
||||
|
||||
wg.Add(1)
|
||||
go func(data *SummariesData) {
|
||||
defer wg.Done()
|
||||
for i, e := range s.Languages {
|
||||
@@ -155,6 +158,7 @@ func newDataFrom(s *models.Summary) *SummariesData {
|
||||
}
|
||||
}(data)
|
||||
|
||||
wg.Add(1)
|
||||
go func(data *SummariesData) {
|
||||
defer wg.Done()
|
||||
for i, e := range s.OperatingSystems {
|
||||
@@ -162,6 +166,7 @@ func newDataFrom(s *models.Summary) *SummariesData {
|
||||
}
|
||||
}(data)
|
||||
|
||||
wg.Add(1)
|
||||
go func(data *SummariesData) {
|
||||
defer wg.Done()
|
||||
for i, e := range s.Machines {
|
||||
@@ -169,6 +174,7 @@ func newDataFrom(s *models.Summary) *SummariesData {
|
||||
}
|
||||
}(data)
|
||||
|
||||
wg.Add(1)
|
||||
go func(data *SummariesData) {
|
||||
defer wg.Done()
|
||||
for i, e := range s.Branches {
|
||||
@@ -176,9 +182,20 @@ func newDataFrom(s *models.Summary) *SummariesData {
|
||||
}
|
||||
}(data)
|
||||
|
||||
wg.Add(1)
|
||||
go func(data *SummariesData) {
|
||||
defer wg.Done()
|
||||
for i, e := range s.Entities {
|
||||
data.Entities[i] = convertEntry(e, s.TotalTimeBy(models.SummaryEntity))
|
||||
}
|
||||
}(data)
|
||||
|
||||
if s.Branches == nil {
|
||||
data.Branches = nil
|
||||
}
|
||||
if s.Entities == nil {
|
||||
data.Entities = nil
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return data
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"github.com/emvi/logbuch"
|
||||
"github.com/mitchellh/hashstructure/v2"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type Duration struct {
|
||||
@@ -17,8 +18,24 @@ type Duration struct {
|
||||
OperatingSystem string `json:"operating_system"`
|
||||
Machine string `json:"machine"`
|
||||
Branch string `json:"branch"`
|
||||
Entity string `json:"entity"`
|
||||
NumHeartbeats int `json:"-" hash:"ignore"`
|
||||
GroupHash string `json:"-" hash:"ignore"`
|
||||
excludeEntity bool `json:"-" hash:"ignore"`
|
||||
}
|
||||
|
||||
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" ||
|
||||
unicode.IsLower(rune(field[0])) {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func NewDurationFromHeartbeat(h *Heartbeat) *Duration {
|
||||
@@ -32,11 +49,17 @@ func NewDurationFromHeartbeat(h *Heartbeat) *Duration {
|
||||
OperatingSystem: h.OperatingSystem,
|
||||
Machine: h.Machine,
|
||||
Branch: h.Branch,
|
||||
Entity: h.Entity,
|
||||
NumHeartbeats: 1,
|
||||
}
|
||||
return d.Hashed()
|
||||
}
|
||||
|
||||
func (d *Duration) WithEntityIgnored() *Duration {
|
||||
d.excludeEntity = true
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *Duration) Hashed() *Duration {
|
||||
hash, err := hashstructure.Hash(d, hashstructure.FormatV2, nil)
|
||||
if err != nil {
|
||||
@@ -60,6 +83,8 @@ func (d *Duration) GetKey(t uint8) (key string) {
|
||||
key = d.Machine
|
||||
case SummaryBranch:
|
||||
key = d.Branch
|
||||
case SummaryEntity:
|
||||
key = d.Entity
|
||||
}
|
||||
|
||||
if key == "" {
|
||||
|
||||
@@ -14,6 +14,7 @@ type Filters struct {
|
||||
Machine OrFilter
|
||||
Label OrFilter
|
||||
Branch OrFilter
|
||||
Entity OrFilter
|
||||
}
|
||||
|
||||
type OrFilter []string
|
||||
@@ -65,6 +66,8 @@ func (f *Filters) WithMultiple(entity uint8, keys []string) *Filters {
|
||||
f.Label = append(f.Label, keys...)
|
||||
case SummaryBranch:
|
||||
f.Branch = append(f.Branch, keys...)
|
||||
case SummaryEntity:
|
||||
f.Entity = append(f.Entity, keys...)
|
||||
}
|
||||
return f
|
||||
}
|
||||
@@ -84,6 +87,8 @@ func (f *Filters) One() (bool, uint8, OrFilter) {
|
||||
return true, SummaryLabel, f.Label
|
||||
} else if f.Branch != nil && f.Branch.Exists() {
|
||||
return true, SummaryBranch, f.Branch
|
||||
} else if f.Entity != nil && f.Entity.Exists() {
|
||||
return true, SummaryEntity, f.Entity
|
||||
}
|
||||
return false, 0, OrFilter{}
|
||||
}
|
||||
@@ -102,7 +107,7 @@ func (f *Filters) IsEmpty() bool {
|
||||
|
||||
func (f *Filters) Count() int {
|
||||
var count int
|
||||
for i := SummaryProject; i <= SummaryBranch; i++ {
|
||||
for i := SummaryProject; i <= SummaryEntity; i++ {
|
||||
count += f.CountByEntity(i)
|
||||
}
|
||||
return count
|
||||
@@ -114,7 +119,7 @@ func (f *Filters) CountByEntity(entity uint8) int {
|
||||
|
||||
func (f *Filters) EntityCount() int {
|
||||
var count int
|
||||
for i := SummaryProject; i <= SummaryBranch; i++ {
|
||||
for i := SummaryProject; i <= SummaryEntity; i++ {
|
||||
if c := f.CountByEntity(i); c > 0 {
|
||||
count++
|
||||
}
|
||||
@@ -138,6 +143,8 @@ func (f *Filters) ResolveEntity(entityId uint8) *OrFilter {
|
||||
return &f.Label
|
||||
case SummaryBranch:
|
||||
return &f.Branch
|
||||
case SummaryEntity:
|
||||
return &f.Entity
|
||||
default:
|
||||
return &OrFilter{}
|
||||
}
|
||||
@@ -209,6 +216,7 @@ func (f *Filters) WithAliases(resolve AliasReverseResolver) *Filters {
|
||||
}
|
||||
f.Branch = updated
|
||||
}
|
||||
// no aliases for entites / files
|
||||
return f
|
||||
}
|
||||
|
||||
@@ -221,3 +229,7 @@ func (f *Filters) WithProjectLabels(resolve ProjectLabelReverseResolver) *Filter
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *Filters) IsProjectDetails() bool {
|
||||
return f != nil && f.Project != nil && f.Project.Exists()
|
||||
}
|
||||
|
||||
@@ -63,6 +63,8 @@ func (h *Heartbeat) GetKey(t uint8) (key string) {
|
||||
key = h.Machine
|
||||
case SummaryBranch:
|
||||
key = h.Branch
|
||||
case SummaryEntity:
|
||||
key = h.Entity
|
||||
}
|
||||
|
||||
if key == "" {
|
||||
|
||||
@@ -16,6 +16,7 @@ const (
|
||||
SummaryMachine uint8 = 4
|
||||
SummaryLabel uint8 = 5
|
||||
SummaryBranch uint8 = 6
|
||||
SummaryEntity uint8 = 7
|
||||
)
|
||||
|
||||
const UnknownSummaryKey = "unknown"
|
||||
@@ -34,6 +35,7 @@ type Summary struct {
|
||||
Machines SummaryItems `json:"machines" gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
|
||||
Labels SummaryItems `json:"labels" gorm:"-"` // labels are not persisted, but calculated at runtime, i.e. when summary is retrieved
|
||||
Branches SummaryItems `json:"branches" gorm:"-"` // branches are not persisted, but calculated at runtime in case a project filter is applied
|
||||
Entities SummaryItems `json:"entities" gorm:"-"` // entities are not persisted, but calculated at runtime in case a project filter is applied
|
||||
NumHeartbeats int `json:"-"`
|
||||
}
|
||||
|
||||
@@ -62,11 +64,11 @@ type SummaryParams struct {
|
||||
}
|
||||
|
||||
func SummaryTypes() []uint8 {
|
||||
return []uint8{SummaryProject, SummaryLanguage, SummaryEditor, SummaryOS, SummaryMachine, SummaryLabel, SummaryBranch}
|
||||
return []uint8{SummaryProject, SummaryLanguage, SummaryEditor, SummaryOS, SummaryMachine, SummaryLabel, SummaryBranch, SummaryEntity}
|
||||
}
|
||||
|
||||
func NativeSummaryTypes() []uint8 {
|
||||
return []uint8{SummaryProject, SummaryLanguage, SummaryEditor, SummaryOS, SummaryMachine, SummaryBranch}
|
||||
return []uint8{SummaryProject, SummaryLanguage, SummaryEditor, SummaryOS, SummaryMachine, SummaryBranch, SummaryEntity}
|
||||
}
|
||||
|
||||
func PersistedSummaryTypes() []uint8 {
|
||||
@@ -81,6 +83,7 @@ func (s *Summary) Sorted() *Summary {
|
||||
sort.Sort(sort.Reverse(s.Editors))
|
||||
sort.Sort(sort.Reverse(s.Labels))
|
||||
sort.Sort(sort.Reverse(s.Branches))
|
||||
sort.Sort(sort.Reverse(s.Entities))
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -97,6 +100,7 @@ func (s *Summary) MappedItems() map[uint8]*SummaryItems {
|
||||
SummaryMachine: &s.Machines,
|
||||
SummaryLabel: &s.Labels,
|
||||
SummaryBranch: &s.Branches,
|
||||
SummaryEntity: &s.Entities,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +120,8 @@ func (s *Summary) ItemsByType(summaryType uint8) *SummaryItems {
|
||||
return &s.Labels
|
||||
case SummaryBranch:
|
||||
return &s.Branches
|
||||
case SummaryEntity:
|
||||
return &s.Entities
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -325,6 +331,7 @@ func (s *Summary) WithResolvedAliases(resolve AliasResolver) *Summary {
|
||||
s.Machines = processAliases(s.Machines)
|
||||
s.Labels = processAliases(s.Labels)
|
||||
s.Branches = processAliases(s.Branches)
|
||||
// no aliases for entities / files
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user