mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
feat: comprehensive summary-level filtering (resolve #262)
This commit is contained in:
parent
8a3e6f0179
commit
a279548c89
@ -10,7 +10,7 @@ type DurationServiceMock struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *DurationServiceMock) Get(time time.Time, time2 time.Time, user *models.User) (models.Durations, error) {
|
||||
args := m.Called(time, time2, user)
|
||||
func (m *DurationServiceMock) Get(time time.Time, time2 time.Time, user *models.User, f *models.Filters) (models.Durations, error) {
|
||||
args := m.Called(time, time2, user, f)
|
||||
return args.Get(0).(models.Durations), args.Error(1)
|
||||
}
|
||||
|
@ -1,5 +1,11 @@
|
||||
package models
|
||||
|
||||
// AliasResolver returns the alias of an entity, given its original name. I.e., it returns Alias.Key, given an Alias.Value
|
||||
type AliasResolver func(t uint8, k string) string
|
||||
|
||||
// AliasReverseResolver returns all original names, which have the given alias as mapping target. I.e., it returns a list of Alias.Value, given an Alias.Key
|
||||
type AliasReverseResolver func(t uint8, k string) []string
|
||||
|
||||
type Alias struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
Type uint8 `gorm:"not null; index:idx_alias_type_key"`
|
||||
|
@ -3,7 +3,6 @@ package v1
|
||||
import (
|
||||
"github.com/muety/wakapi/models"
|
||||
"github.com/muety/wakapi/utils"
|
||||
"time"
|
||||
)
|
||||
|
||||
// https://shields.io/endpoint
|
||||
@ -20,18 +19,11 @@ type BadgeData struct {
|
||||
Color string `json:"color"`
|
||||
}
|
||||
|
||||
func NewBadgeDataFrom(summary *models.Summary, filters *models.Filters) *BadgeData {
|
||||
var total time.Duration
|
||||
if hasFilter, _, _ := filters.One(); hasFilter {
|
||||
total = summary.TotalTimeByFilters(filters)
|
||||
} else {
|
||||
total = summary.TotalTime()
|
||||
}
|
||||
|
||||
func NewBadgeDataFrom(summary *models.Summary) *BadgeData {
|
||||
return &BadgeData{
|
||||
SchemaVersion: 1,
|
||||
Label: defaultLabel,
|
||||
Message: utils.FmtWakatimeDuration(total),
|
||||
Message: utils.FmtWakatimeDuration(summary.TotalTime()),
|
||||
Color: defaultColor,
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package v1
|
||||
import (
|
||||
"github.com/muety/wakapi/models"
|
||||
"github.com/muety/wakapi/utils"
|
||||
"time"
|
||||
)
|
||||
|
||||
// https://wakatime.com/developers#all_time_since_today
|
||||
@ -27,14 +26,8 @@ type AllTimeRange struct {
|
||||
Timezone string `json:"timezone"`
|
||||
}
|
||||
|
||||
func NewAllTimeFrom(summary *models.Summary, filters *models.Filters) *AllTimeViewModel {
|
||||
var total time.Duration
|
||||
if key := filters.Project; key != "" {
|
||||
total = summary.TotalTimeByFilters(filters)
|
||||
} else {
|
||||
total = summary.TotalTime()
|
||||
}
|
||||
|
||||
func NewAllTimeFrom(summary *models.Summary) *AllTimeViewModel {
|
||||
total := summary.TotalTime()
|
||||
return &AllTimeViewModel{
|
||||
Data: &AllTimeData{
|
||||
TotalSeconds: float32(total.Seconds()),
|
||||
|
@ -57,8 +57,7 @@ type SummariesRange struct {
|
||||
Timezone string `json:"timezone"`
|
||||
}
|
||||
|
||||
func NewSummariesFrom(summaries []*models.Summary, filters *models.Filters) *SummariesViewModel {
|
||||
// TODO: implement filtering (https://github.com/muety/wakapi/issues/58)
|
||||
func NewSummariesFrom(summaries []*models.Summary) *SummariesViewModel {
|
||||
data := make([]*SummariesData, len(summaries))
|
||||
minDate, maxDate := time.Now().Add(1*time.Second), time.Time{}
|
||||
|
||||
|
@ -1,50 +1,158 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/emvi/logbuch"
|
||||
"github.com/mitchellh/hashstructure/v2"
|
||||
)
|
||||
|
||||
type Filters struct {
|
||||
Project string
|
||||
OS string
|
||||
Language string
|
||||
Editor string
|
||||
Machine string
|
||||
Label string
|
||||
Project OrFilter
|
||||
OS OrFilter
|
||||
Language OrFilter
|
||||
Editor OrFilter
|
||||
Machine OrFilter
|
||||
Label OrFilter
|
||||
}
|
||||
|
||||
type OrFilter []string
|
||||
|
||||
func (f OrFilter) Exists() bool {
|
||||
return len(f) > 0 && f[0] != ""
|
||||
}
|
||||
|
||||
func (f OrFilter) MatchAny(search string) bool {
|
||||
for _, s := range f {
|
||||
if s == search {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type FilterElement struct {
|
||||
Type uint8
|
||||
Key string
|
||||
entity uint8
|
||||
filter OrFilter
|
||||
}
|
||||
|
||||
func NewFiltersWith(entity uint8, key string) *Filters {
|
||||
switch entity {
|
||||
case SummaryProject:
|
||||
return &Filters{Project: key}
|
||||
case SummaryOS:
|
||||
return &Filters{OS: key}
|
||||
case SummaryLanguage:
|
||||
return &Filters{Language: key}
|
||||
case SummaryEditor:
|
||||
return &Filters{Editor: key}
|
||||
case SummaryMachine:
|
||||
return &Filters{Machine: key}
|
||||
case SummaryLabel:
|
||||
return &Filters{Label: key}
|
||||
}
|
||||
return &Filters{}
|
||||
return NewFilterWithMultiple(entity, []string{key})
|
||||
}
|
||||
|
||||
func (f *Filters) One() (bool, uint8, string) {
|
||||
if f.Project != "" {
|
||||
func NewFilterWithMultiple(entity uint8, keys []string) *Filters {
|
||||
filters := &Filters{}
|
||||
return filters.WithMultiple(entity, keys)
|
||||
}
|
||||
|
||||
func (f *Filters) With(entity uint8, key string) *Filters {
|
||||
return f.WithMultiple(entity, []string{key})
|
||||
}
|
||||
|
||||
func (f *Filters) WithMultiple(entity uint8, keys []string) *Filters {
|
||||
switch entity {
|
||||
case SummaryProject:
|
||||
f.Project = append(f.Project, keys...)
|
||||
case SummaryOS:
|
||||
f.OS = append(f.OS, keys...)
|
||||
case SummaryLanguage:
|
||||
f.Language = append(f.Language, keys...)
|
||||
case SummaryEditor:
|
||||
f.Editor = append(f.Editor, keys...)
|
||||
case SummaryMachine:
|
||||
f.Machine = append(f.Machine, keys...)
|
||||
case SummaryLabel:
|
||||
f.Label = append(f.Label, keys...)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *Filters) One() (bool, uint8, OrFilter) {
|
||||
if f.Project != nil && f.Project.Exists() {
|
||||
return true, SummaryProject, f.Project
|
||||
} else if f.OS != "" {
|
||||
} else if f.OS != nil && f.OS.Exists() {
|
||||
return true, SummaryOS, f.OS
|
||||
} else if f.Language != "" {
|
||||
} else if f.Language != nil && f.Language.Exists() {
|
||||
return true, SummaryLanguage, f.Language
|
||||
} else if f.Editor != "" {
|
||||
} else if f.Editor != nil && f.Editor.Exists() {
|
||||
return true, SummaryEditor, f.Editor
|
||||
} else if f.Machine != "" {
|
||||
} else if f.Machine != nil && f.Machine.Exists() {
|
||||
return true, SummaryMachine, f.Machine
|
||||
} else if f.Label != "" {
|
||||
} else if f.Label != nil && f.Label.Exists() {
|
||||
return true, SummaryLabel, f.Label
|
||||
}
|
||||
return false, 0, ""
|
||||
return false, 0, OrFilter{}
|
||||
}
|
||||
|
||||
func (f *Filters) OneOrEmpty() FilterElement {
|
||||
if ok, t, of := f.One(); ok {
|
||||
return FilterElement{entity: t, filter: of}
|
||||
}
|
||||
return FilterElement{}
|
||||
}
|
||||
|
||||
func (f *Filters) IsEmpty() bool {
|
||||
nonEmpty, _, _ := f.One()
|
||||
return !nonEmpty
|
||||
}
|
||||
|
||||
func (f *Filters) Hash() string {
|
||||
hash, err := hashstructure.Hash(f, hashstructure.FormatV2, nil)
|
||||
if err != nil {
|
||||
logbuch.Error("CRITICAL ERROR: failed to hash struct – %v", err)
|
||||
}
|
||||
return fmt.Sprintf("%x", hash) // "uint64 values with high bit set are not supported"
|
||||
}
|
||||
|
||||
func (f *Filters) Match(h *Heartbeat) bool {
|
||||
// TODO: labels?
|
||||
return (f.Project == nil || f.Project.MatchAny(h.Project)) &&
|
||||
(f.OS == nil || f.OS.MatchAny(h.OperatingSystem)) &&
|
||||
(f.Language == nil || f.Language.MatchAny(h.Language)) &&
|
||||
(f.Editor == nil || f.Editor.MatchAny(h.Editor)) &&
|
||||
(f.Machine == nil || f.Machine.MatchAny(h.Machine))
|
||||
}
|
||||
|
||||
// WithAliases adds OR-conditions for every alias of a filter key as additional filter keys
|
||||
func (f *Filters) WithAliases(resolver AliasReverseResolver) *Filters {
|
||||
if f.Project != nil {
|
||||
updated := OrFilter(make([]string, 0, len(f.Project)))
|
||||
for _, e := range f.Project {
|
||||
updated = append(updated, e)
|
||||
updated = append(updated, resolver(SummaryProject, e)...)
|
||||
}
|
||||
f.Project = updated
|
||||
}
|
||||
if f.OS != nil {
|
||||
updated := OrFilter(make([]string, 0, len(f.OS)))
|
||||
for _, e := range f.OS {
|
||||
updated = append(updated, e)
|
||||
updated = append(updated, resolver(SummaryOS, e)...)
|
||||
}
|
||||
f.OS = updated
|
||||
}
|
||||
if f.Language != nil {
|
||||
updated := OrFilter(make([]string, 0, len(f.Language)))
|
||||
for _, e := range f.Language {
|
||||
updated = append(updated, e)
|
||||
updated = append(updated, resolver(SummaryLanguage, e)...)
|
||||
}
|
||||
f.Language = updated
|
||||
}
|
||||
if f.Editor != nil {
|
||||
updated := OrFilter(make([]string, 0, len(f.Editor)))
|
||||
for _, e := range f.Editor {
|
||||
updated = append(updated, e)
|
||||
updated = append(updated, resolver(SummaryEditor, e)...)
|
||||
}
|
||||
f.Editor = updated
|
||||
}
|
||||
if f.Machine != nil {
|
||||
updated := OrFilter(make([]string, 0, len(f.Machine)))
|
||||
for _, e := range f.Machine {
|
||||
updated = append(updated, e)
|
||||
updated = append(updated, resolver(SummaryMachine, e)...)
|
||||
}
|
||||
f.Machine = updated
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
@ -57,8 +57,6 @@ type SummaryParams struct {
|
||||
Recompute bool
|
||||
}
|
||||
|
||||
type AliasResolver func(t uint8, k string) string
|
||||
|
||||
func SummaryTypes() []uint8 {
|
||||
return []uint8{SummaryProject, SummaryLanguage, SummaryEditor, SummaryOS, SummaryMachine, SummaryLabel}
|
||||
}
|
||||
@ -204,12 +202,12 @@ func (s *Summary) TotalTimeByKey(entityType uint8, key string) (timeSum time.Dur
|
||||
return timeSum
|
||||
}
|
||||
|
||||
func (s *Summary) TotalTimeByFilters(filters *Filters) time.Duration {
|
||||
do, typeId, key := filters.One()
|
||||
if do {
|
||||
return s.TotalTimeByKey(typeId, key)
|
||||
func (s *Summary) TotalTimeByFilter(filter FilterElement) time.Duration {
|
||||
var total time.Duration
|
||||
for _, f := range filter.filter {
|
||||
total += s.TotalTimeByKey(filter.entity, f)
|
||||
}
|
||||
return 0
|
||||
return total
|
||||
}
|
||||
|
||||
func (s *Summary) MaxBy(entityType uint8) *SummaryItem {
|
||||
|
@ -98,20 +98,13 @@ func TestSummary_TotalTimeByFilters(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
// Specifying filters about multiple entites is not supported at the moment
|
||||
// as the current, very rudimentary, time calculation logic wouldn't make sense then.
|
||||
// Evaluating a filter like (project="wakapi", language="go") can only be realized
|
||||
// before computing the summary in the first place, because afterwards we can't know
|
||||
// what time coded in "Go" was in the "Wakapi" project
|
||||
// See https://github.com/muety/wakapi/issues/108
|
||||
filters1 := NewFiltersWith(SummaryProject, "wakapi").OneOrEmpty()
|
||||
filters2 := NewFiltersWith(SummaryLanguage, "Go").OneOrEmpty()
|
||||
filters3 := FilterElement{}
|
||||
|
||||
filters1 := &Filters{Project: "wakapi"}
|
||||
filters2 := &Filters{Language: "Go"}
|
||||
filters3 := &Filters{}
|
||||
|
||||
assert.Equal(t, testDuration1, sut.TotalTimeByFilters(filters1))
|
||||
assert.Equal(t, testDuration3, sut.TotalTimeByFilters(filters2))
|
||||
assert.Zero(t, sut.TotalTimeByFilters(filters3))
|
||||
assert.Equal(t, testDuration1, sut.TotalTimeByFilter(filters1))
|
||||
assert.Equal(t, testDuration3, sut.TotalTimeByFilter(filters2))
|
||||
assert.Zero(t, sut.TotalTimeByFilter(filters3))
|
||||
}
|
||||
|
||||
func TestSummary_WithResolvedAliases(t *testing.T) {
|
||||
|
@ -116,7 +116,7 @@ func (h *MetricsHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *MetricsHandler) getUserMetrics(user *models.User) (*mm.Metrics, error) {
|
||||
var metrics mm.Metrics
|
||||
|
||||
summaryAllTime, err := h.summarySrvc.Aliased(time.Time{}, time.Now(), user, h.summarySrvc.Retrieve, false)
|
||||
summaryAllTime, err := h.summarySrvc.Aliased(time.Time{}, time.Now(), user, h.summarySrvc.Retrieve, nil, false)
|
||||
if err != nil {
|
||||
logbuch.Error("failed to retrieve all time summary for user '%s' for metric", user.ID)
|
||||
return nil, err
|
||||
@ -124,7 +124,7 @@ func (h *MetricsHandler) getUserMetrics(user *models.User) (*mm.Metrics, error)
|
||||
|
||||
from, to := utils.MustResolveIntervalRawTZ("today", user.TZ())
|
||||
|
||||
summaryToday, err := h.summarySrvc.Aliased(from, to, user, h.summarySrvc.Retrieve, false)
|
||||
summaryToday, err := h.summarySrvc.Aliased(from, to, user, h.summarySrvc.Retrieve, nil, false)
|
||||
if err != nil {
|
||||
logbuch.Error("failed to retrieve today's summary for user '%s' for metric", user.ID)
|
||||
return nil, err
|
||||
@ -141,7 +141,7 @@ func (h *MetricsHandler) getUserMetrics(user *models.User) (*mm.Metrics, error)
|
||||
metrics = append(metrics, &mm.CounterMetric{
|
||||
Name: MetricsPrefix + "_cumulative_seconds_total",
|
||||
Desc: DescAllTime,
|
||||
Value: int(v1.NewAllTimeFrom(summaryAllTime, &models.Filters{}).Data.TotalSeconds),
|
||||
Value: int(v1.NewAllTimeFrom(summaryAllTime).Data.TotalSeconds),
|
||||
Labels: []mm.Label{},
|
||||
})
|
||||
|
||||
|
@ -122,19 +122,19 @@ func (h *BadgeHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
summary, err, status := h.loadUserSummary(user, interval)
|
||||
summary, err, status := h.loadUserSummary(user, interval, filters)
|
||||
if err != nil {
|
||||
w.WriteHeader(status)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
vm := v1.NewBadgeDataFrom(summary, filters)
|
||||
vm := v1.NewBadgeDataFrom(summary)
|
||||
h.cache.SetDefault(cacheKey, vm)
|
||||
utils.RespondJSON(w, r, http.StatusOK, vm)
|
||||
}
|
||||
|
||||
func (h *BadgeHandler) loadUserSummary(user *models.User, interval *models.IntervalKey) (*models.Summary, error, int) {
|
||||
func (h *BadgeHandler) loadUserSummary(user *models.User, interval *models.IntervalKey, filters *models.Filters) (*models.Summary, error, int) {
|
||||
err, from, to := utils.ResolveIntervalTZ(interval, user.TZ())
|
||||
if err != nil {
|
||||
return nil, err, http.StatusBadRequest
|
||||
@ -151,7 +151,14 @@ func (h *BadgeHandler) loadUserSummary(user *models.User, interval *models.Inter
|
||||
retrieveSummary = h.summarySrvc.Summarize
|
||||
}
|
||||
|
||||
summary, err := h.summarySrvc.Aliased(summaryParams.From, summaryParams.To, summaryParams.User, retrieveSummary, summaryParams.Recompute)
|
||||
summary, err := h.summarySrvc.Aliased(
|
||||
summaryParams.From,
|
||||
summaryParams.To,
|
||||
summaryParams.User,
|
||||
retrieveSummary,
|
||||
filters,
|
||||
summaryParams.Recompute,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err, http.StatusInternalServerError
|
||||
}
|
||||
|
@ -1,10 +1,6 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
conf "github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/middlewares"
|
||||
@ -13,6 +9,8 @@ import (
|
||||
routeutils "github.com/muety/wakapi/routes/utils"
|
||||
"github.com/muety/wakapi/services"
|
||||
"github.com/muety/wakapi/utils"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AllTimeHandler struct {
|
||||
@ -47,25 +45,23 @@ func (h *AllTimeHandler) RegisterRoutes(router *mux.Router) {
|
||||
// @Success 200 {object} v1.AllTimeViewModel
|
||||
// @Router /compat/wakatime/v1/users/{user}/all_time_since_today [get]
|
||||
func (h *AllTimeHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
values, _ := url.ParseQuery(r.URL.RawQuery)
|
||||
|
||||
user, err := routeutils.CheckEffectiveUser(w, r, h.userSrvc, "current")
|
||||
if err != nil {
|
||||
return // response was already sent by util function
|
||||
}
|
||||
|
||||
summary, err, status := h.loadUserSummary(user)
|
||||
summary, err, status := h.loadUserSummary(user, routeutils.ParseFilters(r))
|
||||
if err != nil {
|
||||
w.WriteHeader(status)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
vm := v1.NewAllTimeFrom(summary, models.NewFiltersWith(models.SummaryProject, values.Get("project")))
|
||||
vm := v1.NewAllTimeFrom(summary)
|
||||
utils.RespondJSON(w, r, http.StatusOK, vm)
|
||||
}
|
||||
|
||||
func (h *AllTimeHandler) loadUserSummary(user *models.User) (*models.Summary, error, int) {
|
||||
func (h *AllTimeHandler) loadUserSummary(user *models.User, filters *models.Filters) (*models.Summary, error, int) {
|
||||
summaryParams := &models.SummaryParams{
|
||||
From: time.Time{},
|
||||
To: time.Now(),
|
||||
@ -78,7 +74,14 @@ func (h *AllTimeHandler) loadUserSummary(user *models.User) (*models.Summary, er
|
||||
retrieveSummary = h.summarySrvc.Summarize
|
||||
}
|
||||
|
||||
summary, err := h.summarySrvc.Aliased(summaryParams.From, summaryParams.To, summaryParams.User, retrieveSummary, summaryParams.Recompute)
|
||||
summary, err := h.summarySrvc.Aliased(
|
||||
summaryParams.From,
|
||||
summaryParams.To,
|
||||
summaryParams.User,
|
||||
retrieveSummary,
|
||||
filters,
|
||||
summaryParams.Recompute,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err, http.StatusInternalServerError
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"github.com/muety/wakapi/middlewares"
|
||||
"github.com/muety/wakapi/models"
|
||||
v1 "github.com/muety/wakapi/models/compat/wakatime/v1"
|
||||
routeutils "github.com/muety/wakapi/routes/utils"
|
||||
"github.com/muety/wakapi/services"
|
||||
"github.com/muety/wakapi/utils"
|
||||
)
|
||||
@ -88,7 +89,7 @@ func (h *StatsHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
summary, err, status := h.loadUserSummary(requestedUser, rangeFrom, rangeTo)
|
||||
summary, err, status := h.loadUserSummary(requestedUser, rangeFrom, rangeTo, routeutils.ParseFilters(r))
|
||||
if err != nil {
|
||||
w.WriteHeader(status)
|
||||
w.Write([]byte(err.Error()))
|
||||
@ -117,7 +118,7 @@ func (h *StatsHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
utils.RespondJSON(w, r, http.StatusOK, stats)
|
||||
}
|
||||
|
||||
func (h *StatsHandler) loadUserSummary(user *models.User, start, end time.Time) (*models.Summary, error, int) {
|
||||
func (h *StatsHandler) loadUserSummary(user *models.User, start, end time.Time, filters *models.Filters) (*models.Summary, error, int) {
|
||||
overallParams := &models.SummaryParams{
|
||||
From: start,
|
||||
To: end,
|
||||
@ -125,7 +126,7 @@ func (h *StatsHandler) loadUserSummary(user *models.User, start, end time.Time)
|
||||
Recompute: false,
|
||||
}
|
||||
|
||||
summary, err := h.summarySrvc.Aliased(overallParams.From, overallParams.To, user, h.summarySrvc.Retrieve, false)
|
||||
summary, err := h.summarySrvc.Aliased(overallParams.From, overallParams.To, user, h.summarySrvc.Retrieve, filters, false)
|
||||
if err != nil {
|
||||
return nil, err, http.StatusInternalServerError
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ func (h *StatusBarHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
summariesView := v1.NewSummariesFrom([]*models.Summary{summary}, &models.Filters{})
|
||||
summariesView := v1.NewSummariesFrom([]*models.Summary{summary})
|
||||
utils.RespondJSON(w, r, http.StatusOK, StatusBarViewModel{
|
||||
CachedAt: time.Now(),
|
||||
Data: *summariesView.Data[0],
|
||||
@ -98,7 +98,7 @@ func (h *StatusBarHandler) loadUserSummary(user *models.User, start, end time.Ti
|
||||
retrieveSummary = h.summarySrvc.Summarize
|
||||
}
|
||||
|
||||
summary, err := h.summarySrvc.Aliased(summaryParams.From, summaryParams.To, summaryParams.User, retrieveSummary, summaryParams.Recompute)
|
||||
summary, err := h.summarySrvc.Aliased(summaryParams.From, summaryParams.To, summaryParams.User, retrieveSummary, nil, summaryParams.Recompute)
|
||||
if err != nil {
|
||||
return nil, http.StatusInternalServerError, err
|
||||
}
|
||||
|
@ -68,12 +68,7 @@ func (h *SummariesHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
filters := &models.Filters{}
|
||||
if projectQuery := r.URL.Query().Get("project"); projectQuery != "" {
|
||||
filters.Project = projectQuery
|
||||
}
|
||||
|
||||
vm := v1.NewSummariesFrom(summaries, filters)
|
||||
vm := v1.NewSummariesFrom(summaries)
|
||||
utils.RespondJSON(w, r, http.StatusOK, vm)
|
||||
}
|
||||
|
||||
@ -130,8 +125,11 @@ func (h *SummariesHandler) loadUserSummaries(r *http.Request) ([]*models.Summary
|
||||
intervals := utils.SplitRangeByDays(overallParams.From, overallParams.To)
|
||||
summaries := make([]*models.Summary, len(intervals))
|
||||
|
||||
// filtering
|
||||
filters := routeutils.ParseFilters(r)
|
||||
|
||||
for i, interval := range intervals {
|
||||
summary, err := h.summarySrvc.Aliased(interval[0], interval[1], user, h.summarySrvc.Retrieve, end.After(time.Now()))
|
||||
summary, err := h.summarySrvc.Aliased(interval[0], interval[1], user, h.summarySrvc.Retrieve, filters, end.After(time.Now()))
|
||||
if err != nil {
|
||||
return nil, err, http.StatusInternalServerError
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ func LoadUserSummary(ss services.ISummaryService, r *http.Request) (*models.Summ
|
||||
retrieveSummary = ss.Summarize
|
||||
}
|
||||
|
||||
summary, err := ss.Aliased(summaryParams.From, summaryParams.To, summaryParams.User, retrieveSummary, summaryParams.Recompute)
|
||||
summary, err := ss.Aliased(summaryParams.From, summaryParams.To, summaryParams.User, retrieveSummary, ParseFilters(r), summaryParams.Recompute)
|
||||
if err != nil {
|
||||
return nil, err, http.StatusInternalServerError
|
||||
}
|
||||
@ -30,3 +30,26 @@ func LoadUserSummary(ss services.ISummaryService, r *http.Request) (*models.Summ
|
||||
|
||||
return summary, nil, http.StatusOK
|
||||
}
|
||||
|
||||
func ParseFilters(r *http.Request) *models.Filters {
|
||||
filters := &models.Filters{}
|
||||
if q := r.URL.Query().Get("project"); q != "" {
|
||||
filters.With(models.SummaryProject, q)
|
||||
}
|
||||
if q := r.URL.Query().Get("language"); q != "" {
|
||||
filters.With(models.SummaryLanguage, q)
|
||||
}
|
||||
if q := r.URL.Query().Get("editor"); q != "" {
|
||||
filters.With(models.SummaryEditor, q)
|
||||
}
|
||||
if q := r.URL.Query().Get("machine"); q != "" {
|
||||
filters.With(models.SummaryMachine, q)
|
||||
}
|
||||
if q := r.URL.Query().Get("operating_system"); q != "" {
|
||||
filters.With(models.SummaryOS, q)
|
||||
}
|
||||
if q := r.URL.Query().Get("label"); q != "" {
|
||||
filters.With(models.SummaryLabel, q)
|
||||
}
|
||||
return filters
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ func (srv *AggregationService) Run(userIds map[string]bool) error {
|
||||
|
||||
func (srv *AggregationService) summaryWorker(jobs <-chan *AggregationJob, summaries chan<- *models.Summary) {
|
||||
for job := range jobs {
|
||||
if summary, err := srv.summaryService.Summarize(job.From, job.To, &models.User{ID: job.UserID}); err != nil {
|
||||
if summary, err := srv.summaryService.Summarize(job.From, job.To, &models.User{ID: job.UserID}, nil); err != nil {
|
||||
config.Log().Error("failed to generate summary (%v, %v, %s) – %v", job.From, job.To, job.UserID, err)
|
||||
} else {
|
||||
logbuch.Info("successfully generated summary (%v, %v, %s)", job.From, job.To, job.UserID)
|
||||
|
@ -2,6 +2,7 @@ package services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/emvi/logbuch"
|
||||
"github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/models"
|
||||
@ -38,35 +39,53 @@ func (srv *AliasService) InitializeUser(userId string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (srv *AliasService) GetByUser(userId string) ([]*models.Alias, error) {
|
||||
aliases, err := srv.repository.GetByUser(userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (srv *AliasService) MayInitializeUser(userId string) {
|
||||
if err := srv.InitializeUser(userId); err != nil {
|
||||
logbuch.Error("failed to initialize user alias map for user %s", userId)
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *AliasService) GetByUser(userId string) ([]*models.Alias, error) {
|
||||
if !srv.IsInitialized(userId) {
|
||||
srv.MayInitializeUser(userId)
|
||||
}
|
||||
if aliases, ok := userAliases.Load(userId); ok {
|
||||
return aliases.([]*models.Alias), nil
|
||||
} else {
|
||||
return nil, errors.New(fmt.Sprintf("no user aliases loaded for user %s", userId))
|
||||
}
|
||||
return aliases, nil
|
||||
}
|
||||
|
||||
func (srv *AliasService) GetByUserAndKeyAndType(userId, key string, summaryType uint8) ([]*models.Alias, error) {
|
||||
aliases, err := srv.repository.GetByUserAndKeyAndType(userId, key, summaryType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if !srv.IsInitialized(userId) {
|
||||
srv.MayInitializeUser(userId)
|
||||
}
|
||||
if aliases, ok := userAliases.Load(userId); ok {
|
||||
filteredAliases := make([]*models.Alias, 0, len(aliases.([]*models.Alias)))
|
||||
for _, a := range aliases.([]*models.Alias) {
|
||||
if a.Key == key && a.Type == summaryType {
|
||||
filteredAliases = append(filteredAliases, a)
|
||||
}
|
||||
}
|
||||
return filteredAliases, nil
|
||||
} else {
|
||||
return nil, errors.New(fmt.Sprintf("no user aliases loaded for user %s", userId))
|
||||
}
|
||||
return aliases, nil
|
||||
}
|
||||
|
||||
func (srv *AliasService) GetAliasOrDefault(userId string, summaryType uint8, value string) (string, error) {
|
||||
if !srv.IsInitialized(userId) {
|
||||
if err := srv.InitializeUser(userId); err != nil {
|
||||
return "", err
|
||||
srv.MayInitializeUser(userId)
|
||||
}
|
||||
|
||||
if aliases, ok := userAliases.Load(userId); ok {
|
||||
for _, a := range aliases.([]*models.Alias) {
|
||||
if a.Type == summaryType && a.Value == value {
|
||||
return a.Key, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
aliases, _ := userAliases.Load(userId)
|
||||
for _, a := range aliases.([]*models.Alias) {
|
||||
if a.Type == summaryType && a.Value == value {
|
||||
return a.Key, nil
|
||||
}
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
@ -75,7 +94,7 @@ func (srv *AliasService) Create(alias *models.Alias) (*models.Alias, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go srv.reinitUser(alias.UserID)
|
||||
go srv.MayInitializeUser(alias.UserID)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@ -84,7 +103,7 @@ func (srv *AliasService) Delete(alias *models.Alias) error {
|
||||
return errors.New("no user id specified")
|
||||
}
|
||||
err := srv.repository.Delete(alias.ID)
|
||||
go srv.reinitUser(alias.UserID)
|
||||
go srv.MayInitializeUser(alias.UserID)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -102,14 +121,8 @@ func (srv *AliasService) DeleteMulti(aliases []*models.Alias) error {
|
||||
err := srv.repository.DeleteBatch(ids)
|
||||
|
||||
for k := range affectedUsers {
|
||||
go srv.reinitUser(k)
|
||||
go srv.MayInitializeUser(k)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (srv *AliasService) reinitUser(userId string) {
|
||||
if err := srv.InitializeUser(userId); err != nil {
|
||||
logbuch.Error("error initializing user aliases – %v", err)
|
||||
}
|
||||
}
|
||||
|
@ -52,12 +52,3 @@ func (suite *AliasServiceTestSuite) TestAliasService_GetAliasOrDefault() {
|
||||
assert.Equal(suite.T(), "anchr", result3)
|
||||
assert.Nil(suite.T(), err3)
|
||||
}
|
||||
|
||||
func (suite *AliasServiceTestSuite) TestAliasService_GetAliasOrDefault_ErrorOnNonExistingUser() {
|
||||
sut := NewAliasService(suite.AliasRepository)
|
||||
|
||||
result, err := sut.GetAliasOrDefault("nonexisting", models.SummaryProject, "wakapi-mobile")
|
||||
|
||||
assert.Empty(suite.T(), result)
|
||||
assert.Error(suite.T(), err)
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ func NewDurationService(heartbeatService IHeartbeatService) *DurationService {
|
||||
return srv
|
||||
}
|
||||
|
||||
func (srv *DurationService) Get(from, to time.Time, user *models.User) (models.Durations, error) {
|
||||
func (srv *DurationService) Get(from, to time.Time, user *models.User, filters *models.Filters) (models.Durations, error) {
|
||||
heartbeats, err := srv.heartbeatService.GetAllWithin(from, to, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -34,6 +34,10 @@ func (srv *DurationService) Get(from, to time.Time, user *models.User) (models.D
|
||||
mapping := make(map[string][]*models.Duration)
|
||||
|
||||
for _, h := range heartbeats {
|
||||
if filters != nil && !filters.Match(h) {
|
||||
continue
|
||||
}
|
||||
|
||||
d1 := models.NewDurationFromHeartbeat(h)
|
||||
|
||||
if list, ok := mapping[d1.GroupHash]; !ok || len(list) < 1 {
|
||||
|
@ -126,7 +126,7 @@ func (suite *DurationServiceTestSuite) TestDurationService_Get() {
|
||||
from, to = suite.TestStartTime.Add(-1*time.Hour), suite.TestStartTime.Add(-1*time.Minute)
|
||||
suite.HeartbeatService.On("GetAllWithin", from, to, suite.TestUser).Return(filterHeartbeats(from, to, suite.TestHeartbeats), nil)
|
||||
|
||||
durations, err = sut.Get(from, to, suite.TestUser)
|
||||
durations, err = sut.Get(from, to, suite.TestUser, nil)
|
||||
|
||||
assert.Nil(suite.T(), err)
|
||||
assert.Empty(suite.T(), durations)
|
||||
@ -135,7 +135,7 @@ func (suite *DurationServiceTestSuite) TestDurationService_Get() {
|
||||
from, to = suite.TestStartTime.Add(-1*time.Hour), suite.TestStartTime.Add(1*time.Second)
|
||||
suite.HeartbeatService.On("GetAllWithin", from, to, suite.TestUser).Return(filterHeartbeats(from, to, suite.TestHeartbeats), nil)
|
||||
|
||||
durations, err = sut.Get(from, to, suite.TestUser)
|
||||
durations, err = sut.Get(from, to, suite.TestUser, nil)
|
||||
|
||||
assert.Nil(suite.T(), err)
|
||||
assert.Len(suite.T(), durations, 1)
|
||||
@ -146,7 +146,7 @@ func (suite *DurationServiceTestSuite) TestDurationService_Get() {
|
||||
from, to = suite.TestStartTime, suite.TestStartTime.Add(1*time.Hour)
|
||||
suite.HeartbeatService.On("GetAllWithin", from, to, suite.TestUser).Return(filterHeartbeats(from, to, suite.TestHeartbeats), nil)
|
||||
|
||||
durations, err = sut.Get(from, to, suite.TestUser)
|
||||
durations, err = sut.Get(from, to, suite.TestUser, nil)
|
||||
|
||||
assert.Nil(suite.T(), err)
|
||||
assert.Len(suite.T(), durations, 3)
|
||||
|
@ -97,7 +97,7 @@ func (srv *MiscService) runCountTotalTime() error {
|
||||
|
||||
func (srv *MiscService) countTotalTimeWorker(jobs <-chan *CountTotalTimeJob, results chan<- *CountTotalTimeResult) {
|
||||
for job := range jobs {
|
||||
if result, err := srv.summaryService.Aliased(time.Time{}, time.Now(), &models.User{ID: job.UserID}, srv.summaryService.Retrieve, false); err != nil {
|
||||
if result, err := srv.summaryService.Aliased(time.Time{}, time.Now(), &models.User{ID: job.UserID}, srv.summaryService.Retrieve, nil, false); err != nil {
|
||||
config.Log().Error("failed to count total for user %s: %v", job.UserID, err)
|
||||
} else {
|
||||
results <- &CountTotalTimeResult{
|
||||
|
@ -112,7 +112,7 @@ func (srv *ReportService) Run(user *models.User, duration time.Duration) error {
|
||||
end := time.Now().In(user.TZ())
|
||||
start := time.Now().Add(-1 * duration)
|
||||
|
||||
summary, err := srv.summaryService.Aliased(start, end, user, srv.summaryService.Retrieve, false)
|
||||
summary, err := srv.summaryService.Aliased(start, end, user, srv.summaryService.Retrieve, nil, false)
|
||||
if err != nil {
|
||||
config.Log().Error("failed to generate report for '%s' – %v", user.ID, err)
|
||||
return err
|
||||
|
@ -75,13 +75,13 @@ type IMailService interface {
|
||||
}
|
||||
|
||||
type IDurationService interface {
|
||||
Get(time.Time, time.Time, *models.User) (models.Durations, error)
|
||||
Get(time.Time, time.Time, *models.User, *models.Filters) (models.Durations, error)
|
||||
}
|
||||
|
||||
type ISummaryService interface {
|
||||
Aliased(time.Time, time.Time, *models.User, SummaryRetriever, bool) (*models.Summary, error)
|
||||
Retrieve(time.Time, time.Time, *models.User) (*models.Summary, error)
|
||||
Summarize(time.Time, time.Time, *models.User) (*models.Summary, error)
|
||||
Aliased(time.Time, time.Time, *models.User, SummaryRetriever, *models.Filters, bool) (*models.Summary, error)
|
||||
Retrieve(time.Time, time.Time, *models.User, *models.Filters) (*models.Summary, error)
|
||||
Summarize(time.Time, time.Time, *models.User, *models.Filters) (*models.Summary, error)
|
||||
GetLatestByUser() ([]*models.TimeByUser, error)
|
||||
DeleteByUser(string) error
|
||||
Insert(*models.Summary) error
|
||||
|
@ -24,7 +24,7 @@ type SummaryService struct {
|
||||
projectLabelService IProjectLabelService
|
||||
}
|
||||
|
||||
type SummaryRetriever func(f, t time.Time, u *models.User) (*models.Summary, error)
|
||||
type SummaryRetriever func(f, t time.Time, u *models.User, filters *models.Filters) (*models.Summary, error)
|
||||
|
||||
func NewSummaryService(summaryRepo repositories.ISummaryRepository, durationService IDurationService, aliasService IAliasService, projectLabelService IProjectLabelService) *SummaryService {
|
||||
srv := &SummaryService{
|
||||
@ -55,10 +55,10 @@ func NewSummaryService(summaryRepo repositories.ISummaryRepository, durationServ
|
||||
// Public summary generation methods
|
||||
|
||||
// Aliased retrieves or computes a new summary based on the given SummaryRetriever and augments it with entity aliases and project labels
|
||||
func (srv *SummaryService) Aliased(from, to time.Time, user *models.User, f SummaryRetriever, skipCache bool) (*models.Summary, error) {
|
||||
func (srv *SummaryService) Aliased(from, to time.Time, user *models.User, f SummaryRetriever, filters *models.Filters, skipCache bool) (*models.Summary, error) {
|
||||
// Check cache
|
||||
cacheKey := srv.getHash(from.String(), to.String(), user.ID, "--aliased")
|
||||
if cacheResult, ok := srv.cache.Get(cacheKey); ok && !skipCache && false {
|
||||
cacheKey := srv.getHash(from.String(), to.String(), user.ID, filters.Hash(), "--aliased")
|
||||
if cacheResult, ok := srv.cache.Get(cacheKey); ok && !skipCache {
|
||||
return cacheResult.(*models.Summary), nil
|
||||
}
|
||||
|
||||
@ -67,6 +67,19 @@ func (srv *SummaryService) Aliased(from, to time.Time, user *models.User, f Summ
|
||||
s, _ := srv.aliasService.GetAliasOrDefault(user.ID, t, k)
|
||||
return s
|
||||
}
|
||||
resolveReverse := func(t uint8, k string) []string {
|
||||
aliases, _ := srv.aliasService.GetByUserAndKeyAndType(user.ID, k, t)
|
||||
aliasStrings := make([]string, 0, len(aliases))
|
||||
for _, a := range aliases {
|
||||
aliasStrings = append(aliasStrings, a.Value)
|
||||
}
|
||||
return aliasStrings
|
||||
}
|
||||
|
||||
// Post-process filters
|
||||
if filters != nil {
|
||||
filters = filters.WithAliases(resolveReverse)
|
||||
}
|
||||
|
||||
// Initialize alias resolver service
|
||||
if err := srv.aliasService.InitializeUser(user.ID); err != nil {
|
||||
@ -74,7 +87,7 @@ func (srv *SummaryService) Aliased(from, to time.Time, user *models.User, f Summ
|
||||
}
|
||||
|
||||
// Get actual summary
|
||||
s, err := f(from, to, user)
|
||||
s, err := f(from, to, user, filters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -89,17 +102,24 @@ func (srv *SummaryService) Aliased(from, to time.Time, user *models.User, f Summ
|
||||
return summary.Sorted(), nil
|
||||
}
|
||||
|
||||
func (srv *SummaryService) Retrieve(from, to time.Time, user *models.User) (*models.Summary, error) {
|
||||
// Get all already existing, pre-generated summaries that fall into the requested interval
|
||||
summaries, err := srv.repository.GetByUserWithin(user, from, to)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (srv *SummaryService) Retrieve(from, to time.Time, user *models.User, filters *models.Filters) (*models.Summary, error) {
|
||||
summaries := make([]*models.Summary, 0)
|
||||
|
||||
// Filtered summaries are not persisted currently
|
||||
if filters == nil || filters.IsEmpty() {
|
||||
// Get all already existing, pre-generated summaries that fall into the requested interval
|
||||
result, err := srv.repository.GetByUserWithin(user, from, to)
|
||||
if err == nil {
|
||||
summaries = result
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Generate missing slots (especially before and after existing summaries) from durations (formerly raw heartbeats)
|
||||
missingIntervals := srv.getMissingIntervals(from, to, summaries)
|
||||
for _, interval := range missingIntervals {
|
||||
if s, err := srv.Summarize(interval.Start, interval.End, user); err == nil {
|
||||
if s, err := srv.Summarize(interval.Start, interval.End, user, filters); err == nil {
|
||||
summaries = append(summaries, s)
|
||||
} else {
|
||||
return nil, err
|
||||
@ -115,9 +135,9 @@ func (srv *SummaryService) Retrieve(from, to time.Time, user *models.User) (*mod
|
||||
return summary.Sorted(), nil
|
||||
}
|
||||
|
||||
func (srv *SummaryService) Summarize(from, to time.Time, user *models.User) (*models.Summary, error) {
|
||||
func (srv *SummaryService) Summarize(from, to time.Time, user *models.User, filters *models.Filters) (*models.Summary, error) {
|
||||
// Initialize and fetch data
|
||||
durations, err := srv.durationService.Get(from, to, user)
|
||||
durations, err := srv.durationService.Get(from, to, user, filters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -108,9 +108,9 @@ func (suite *SummaryServiceTestSuite) TestSummaryService_Summarize() {
|
||||
|
||||
/* TEST 1 */
|
||||
from, to = suite.TestStartTime.Add(-1*time.Hour), suite.TestStartTime.Add(-1*time.Minute)
|
||||
suite.DurationService.On("Get", from, to, suite.TestUser).Return(filterDurations(from, to, suite.TestDurations), nil)
|
||||
suite.DurationService.On("Get", from, to, suite.TestUser, mock.Anything).Return(filterDurations(from, to, suite.TestDurations), nil)
|
||||
|
||||
result, err = sut.Summarize(from, to, suite.TestUser)
|
||||
result, err = sut.Summarize(from, to, suite.TestUser, nil)
|
||||
|
||||
assert.Nil(suite.T(), err)
|
||||
assert.NotNil(suite.T(), result)
|
||||
@ -122,9 +122,9 @@ func (suite *SummaryServiceTestSuite) TestSummaryService_Summarize() {
|
||||
|
||||
/* TEST 2 */
|
||||
from, to = suite.TestStartTime.Add(-1*time.Hour), suite.TestStartTime.Add(1*time.Second)
|
||||
suite.DurationService.On("Get", from, to, suite.TestUser).Return(filterDurations(from, to, suite.TestDurations), nil)
|
||||
suite.DurationService.On("Get", from, to, suite.TestUser, mock.Anything).Return(filterDurations(from, to, suite.TestDurations), nil)
|
||||
|
||||
result, err = sut.Summarize(from, to, suite.TestUser)
|
||||
result, err = sut.Summarize(from, to, suite.TestUser, nil)
|
||||
|
||||
assert.Nil(suite.T(), err)
|
||||
assert.NotNil(suite.T(), result)
|
||||
@ -136,9 +136,9 @@ func (suite *SummaryServiceTestSuite) TestSummaryService_Summarize() {
|
||||
|
||||
/* TEST 3 */
|
||||
from, to = suite.TestStartTime, suite.TestStartTime.Add(1*time.Hour)
|
||||
suite.DurationService.On("Get", from, to, suite.TestUser).Return(filterDurations(from, to, suite.TestDurations), nil)
|
||||
suite.DurationService.On("Get", from, to, suite.TestUser, mock.Anything).Return(filterDurations(from, to, suite.TestDurations), nil)
|
||||
|
||||
result, err = sut.Summarize(from, to, suite.TestUser)
|
||||
result, err = sut.Summarize(from, to, suite.TestUser, nil)
|
||||
|
||||
assert.Nil(suite.T(), err)
|
||||
assert.NotNil(suite.T(), result)
|
||||
@ -187,10 +187,10 @@ func (suite *SummaryServiceTestSuite) TestSummaryService_Retrieve() {
|
||||
}
|
||||
|
||||
suite.SummaryRepository.On("GetByUserWithin", suite.TestUser, from, to).Return(summaries, nil)
|
||||
suite.DurationService.On("Get", from, summaries[0].FromTime.T(), suite.TestUser).Return(models.Durations{}, nil)
|
||||
suite.DurationService.On("Get", summaries[0].ToTime.T(), to, suite.TestUser).Return(models.Durations{}, nil)
|
||||
suite.DurationService.On("Get", from, summaries[0].FromTime.T(), suite.TestUser, mock.Anything).Return(models.Durations{}, nil)
|
||||
suite.DurationService.On("Get", summaries[0].ToTime.T(), to, suite.TestUser, mock.Anything).Return(models.Durations{}, nil)
|
||||
|
||||
result, err = sut.Retrieve(from, to, suite.TestUser)
|
||||
result, err = sut.Retrieve(from, to, suite.TestUser, nil)
|
||||
|
||||
assert.Nil(suite.T(), err)
|
||||
assert.NotNil(suite.T(), result)
|
||||
@ -241,9 +241,9 @@ func (suite *SummaryServiceTestSuite) TestSummaryService_Retrieve() {
|
||||
}
|
||||
|
||||
suite.SummaryRepository.On("GetByUserWithin", suite.TestUser, from, to).Return(summaries, nil)
|
||||
suite.DurationService.On("Get", from, summaries[0].FromTime.T(), suite.TestUser).Return(filterDurations(from, summaries[0].FromTime.T(), suite.TestDurations), nil)
|
||||
suite.DurationService.On("Get", from, summaries[0].FromTime.T(), suite.TestUser, mock.Anything).Return(filterDurations(from, summaries[0].FromTime.T(), suite.TestDurations), nil)
|
||||
|
||||
result, err = sut.Retrieve(from, to, suite.TestUser)
|
||||
result, err = sut.Retrieve(from, to, suite.TestUser, nil)
|
||||
|
||||
assert.Nil(suite.T(), err)
|
||||
assert.NotNil(suite.T(), result)
|
||||
@ -297,9 +297,9 @@ func (suite *SummaryServiceTestSuite) TestSummaryService_Retrieve() {
|
||||
}
|
||||
|
||||
suite.SummaryRepository.On("GetByUserWithin", suite.TestUser, from, to).Return(summaries, nil)
|
||||
suite.DurationService.On("Get", summaries[0].ToTime.T(), summaries[1].FromTime.T(), suite.TestUser).Return(filterDurations(summaries[0].ToTime.T(), summaries[1].FromTime.T(), suite.TestDurations), nil)
|
||||
suite.DurationService.On("Get", summaries[0].ToTime.T(), summaries[1].FromTime.T(), suite.TestUser, mock.Anything).Return(filterDurations(summaries[0].ToTime.T(), summaries[1].FromTime.T(), suite.TestDurations), nil)
|
||||
|
||||
result, err = sut.Retrieve(from, to, suite.TestUser)
|
||||
result, err = sut.Retrieve(from, to, suite.TestUser, nil)
|
||||
|
||||
assert.Nil(suite.T(), err)
|
||||
assert.NotNil(suite.T(), result)
|
||||
@ -347,10 +347,10 @@ func (suite *SummaryServiceTestSuite) TestSummaryService_Retrieve_DuplicateSumma
|
||||
summaries = append(summaries, &(*summaries[0])) // add same summary again -> mustn't be counted twice!
|
||||
|
||||
suite.SummaryRepository.On("GetByUserWithin", suite.TestUser, from, to).Return(summaries, nil)
|
||||
suite.DurationService.On("Get", from, summaries[0].FromTime.T(), suite.TestUser).Return(models.Durations{}, nil)
|
||||
suite.DurationService.On("Get", summaries[0].ToTime.T(), to, suite.TestUser).Return(models.Durations{}, nil)
|
||||
suite.DurationService.On("Get", from, summaries[0].FromTime.T(), suite.TestUser, mock.Anything).Return(models.Durations{}, nil)
|
||||
suite.DurationService.On("Get", summaries[0].ToTime.T(), to, suite.TestUser, mock.Anything).Return(models.Durations{}, nil)
|
||||
|
||||
result, err = sut.Retrieve(from, to, suite.TestUser)
|
||||
result, err = sut.Retrieve(from, to, suite.TestUser, nil)
|
||||
|
||||
assert.Nil(suite.T(), err)
|
||||
assert.NotNil(suite.T(), result)
|
||||
@ -362,6 +362,7 @@ func (suite *SummaryServiceTestSuite) TestSummaryService_Retrieve_DuplicateSumma
|
||||
func (suite *SummaryServiceTestSuite) TestSummaryService_Aliased() {
|
||||
sut := NewSummaryService(suite.SummaryRepository, suite.DurationService, suite.AliasService, suite.ProjectLabelService)
|
||||
|
||||
suite.AliasService.On("InitializeUser", suite.TestUser.ID).Return(nil)
|
||||
suite.ProjectLabelService.On("GetByUser", suite.TestUser.ID).Return([]*models.ProjectLabel{}, nil)
|
||||
|
||||
var (
|
||||
@ -385,14 +386,14 @@ func (suite *SummaryServiceTestSuite) TestSummaryService_Aliased() {
|
||||
Duration: 0, // not relevant here
|
||||
})
|
||||
|
||||
suite.DurationService.On("Get", from, to, suite.TestUser).Return(models.Durations(durations), nil)
|
||||
suite.DurationService.On("Get", from, to, suite.TestUser, mock.Anything).Return(models.Durations(durations), nil)
|
||||
suite.AliasService.On("InitializeUser", TestUserId).Return(nil)
|
||||
suite.AliasService.On("GetAliasOrDefault", TestUserId, mock.Anything, TestProject1).Return(TestProject2, nil)
|
||||
suite.AliasService.On("GetAliasOrDefault", TestUserId, mock.Anything, TestProject2).Return(TestProject2, nil)
|
||||
suite.AliasService.On("GetAliasOrDefault", TestUserId, mock.Anything, mock.Anything).Return("", nil)
|
||||
suite.ProjectLabelService.On("GetByUser", suite.TestUser.ID).Return(suite.TestLabels, nil).Once()
|
||||
|
||||
result, err = sut.Aliased(from, to, suite.TestUser, sut.Summarize, false)
|
||||
result, err = sut.Aliased(from, to, suite.TestUser, sut.Summarize, nil, false)
|
||||
|
||||
assert.Nil(suite.T(), err)
|
||||
assert.NotNil(suite.T(), result)
|
||||
@ -426,13 +427,13 @@ func (suite *SummaryServiceTestSuite) TestSummaryService_Aliased_ProjectLabels()
|
||||
})
|
||||
|
||||
suite.ProjectLabelService.On("GetByUser", suite.TestUser.ID).Return(suite.TestLabels, nil).Once()
|
||||
suite.DurationService.On("Get", from, to, suite.TestUser).Return(models.Durations(durations), nil)
|
||||
suite.DurationService.On("Get", from, to, suite.TestUser, mock.Anything).Return(models.Durations(durations), nil)
|
||||
suite.AliasService.On("InitializeUser", TestUserId).Return(nil)
|
||||
suite.AliasService.On("GetAliasOrDefault", TestUserId, mock.Anything, TestProject1).Return(TestProject1, nil)
|
||||
suite.AliasService.On("GetAliasOrDefault", TestUserId, mock.Anything, TestProject2).Return(TestProject1, nil)
|
||||
suite.AliasService.On("GetAliasOrDefault", TestUserId, mock.Anything, mock.Anything).Return("", nil)
|
||||
|
||||
result, err = sut.Aliased(from, to, suite.TestUser, sut.Summarize, false)
|
||||
result, err = sut.Aliased(from, to, suite.TestUser, sut.Summarize, nil, false)
|
||||
|
||||
assert.Nil(suite.T(), err)
|
||||
assert.NotNil(suite.T(), result)
|
||||
|
Loading…
x
Reference in New Issue
Block a user