mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
feat: implement project labels (resolve #204)
This commit is contained in:
73
services/project_label.go
Normal file
73
services/project_label.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/models"
|
||||
"github.com/muety/wakapi/repositories"
|
||||
"github.com/patrickmn/go-cache"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ProjectLabelService struct {
|
||||
config *config.Config
|
||||
cache *cache.Cache
|
||||
repository repositories.IProjectLabelRepository
|
||||
}
|
||||
|
||||
func NewProjectLabelService(projectLabelRepository repositories.IProjectLabelRepository) *ProjectLabelService {
|
||||
return &ProjectLabelService{
|
||||
config: config.Get(),
|
||||
repository: projectLabelRepository,
|
||||
cache: cache.New(24*time.Hour, 24*time.Hour),
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *ProjectLabelService) GetById(id uint) (*models.ProjectLabel, error) {
|
||||
return srv.repository.GetById(id)
|
||||
}
|
||||
|
||||
func (srv *ProjectLabelService) GetByUser(userId string) ([]*models.ProjectLabel, error) {
|
||||
if labels, found := srv.cache.Get(userId); found {
|
||||
return labels.([]*models.ProjectLabel), nil
|
||||
}
|
||||
|
||||
labels, err := srv.repository.GetByUser(userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
srv.cache.Set(userId, labels, cache.DefaultExpiration)
|
||||
return labels, nil
|
||||
}
|
||||
|
||||
func (srv *ProjectLabelService) ResolveByUser(userId string) (map[string]string, error) {
|
||||
labels := make(map[string]string)
|
||||
userLabels, err := srv.GetByUser(userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, m := range userLabels {
|
||||
labels[m.ProjectKey] = m.Label
|
||||
}
|
||||
return labels, nil
|
||||
}
|
||||
|
||||
func (srv *ProjectLabelService) Create(label *models.ProjectLabel) (*models.ProjectLabel, error) {
|
||||
result, err := srv.repository.Insert(label)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
srv.cache.Delete(result.UserID)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (srv *ProjectLabelService) Delete(label *models.ProjectLabel) error {
|
||||
if label.UserID == "" {
|
||||
return errors.New("no user id specified")
|
||||
}
|
||||
err := srv.repository.Delete(label.ID)
|
||||
srv.cache.Delete(label.UserID)
|
||||
return err
|
||||
}
|
||||
@@ -54,6 +54,14 @@ type ILanguageMappingService interface {
|
||||
Delete(mapping *models.LanguageMapping) error
|
||||
}
|
||||
|
||||
type IProjectLabelService interface {
|
||||
GetById(uint) (*models.ProjectLabel, error)
|
||||
GetByUser(string) ([]*models.ProjectLabel, error)
|
||||
ResolveByUser(string) (map[string]string, error)
|
||||
Create(*models.ProjectLabel) (*models.ProjectLabel, error)
|
||||
Delete(mapping *models.ProjectLabel) error
|
||||
}
|
||||
|
||||
type IMailService interface {
|
||||
SendPasswordReset(*models.User, string) error
|
||||
SendImportNotification(*models.User, time.Duration, int) error
|
||||
|
||||
@@ -16,22 +16,24 @@ import (
|
||||
const HeartbeatDiffThreshold = 2 * time.Minute
|
||||
|
||||
type SummaryService struct {
|
||||
config *config.Config
|
||||
cache *cache.Cache
|
||||
repository repositories.ISummaryRepository
|
||||
heartbeatService IHeartbeatService
|
||||
aliasService IAliasService
|
||||
config *config.Config
|
||||
cache *cache.Cache
|
||||
repository repositories.ISummaryRepository
|
||||
heartbeatService IHeartbeatService
|
||||
aliasService IAliasService
|
||||
projectLabelService IProjectLabelService
|
||||
}
|
||||
|
||||
type SummaryRetriever func(f, t time.Time, u *models.User) (*models.Summary, error)
|
||||
|
||||
func NewSummaryService(summaryRepo repositories.ISummaryRepository, heartbeatService IHeartbeatService, aliasService IAliasService) *SummaryService {
|
||||
func NewSummaryService(summaryRepo repositories.ISummaryRepository, heartbeatService IHeartbeatService, aliasService IAliasService, projectLabelService IProjectLabelService) *SummaryService {
|
||||
return &SummaryService{
|
||||
config: config.Get(),
|
||||
cache: cache.New(24*time.Hour, 24*time.Hour),
|
||||
repository: summaryRepo,
|
||||
heartbeatService: heartbeatService,
|
||||
aliasService: aliasService,
|
||||
config: config.Get(),
|
||||
cache: cache.New(24*time.Hour, 24*time.Hour),
|
||||
repository: summaryRepo,
|
||||
heartbeatService: heartbeatService,
|
||||
aliasService: aliasService,
|
||||
projectLabelService: projectLabelService,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +65,9 @@ func (srv *SummaryService) Aliased(from, to time.Time, user *models.User, f Summ
|
||||
|
||||
// Post-process summary and cache it
|
||||
summary := s.WithResolvedAliases(resolve)
|
||||
summary.FillBy(models.SummaryProject, models.SummaryLabel) // first fill up labels from projects
|
||||
summary.FillMissing() // then, full up types which are entirely missing
|
||||
|
||||
srv.cache.SetDefault(cacheKey, summary)
|
||||
return summary.Sorted(), nil
|
||||
}
|
||||
@@ -110,7 +115,7 @@ func (srv *SummaryService) Summarize(from, to time.Time, user *models.User) (*mo
|
||||
return nil, err
|
||||
}
|
||||
|
||||
types := models.SummaryTypes()
|
||||
types := models.NativeSummaryTypes()
|
||||
|
||||
typedAggregations := make(chan models.SummaryItemContainer)
|
||||
defer close(typedAggregations)
|
||||
@@ -156,8 +161,7 @@ func (srv *SummaryService) Summarize(from, to time.Time, user *models.User) (*mo
|
||||
OperatingSystems: osItems,
|
||||
Machines: machineItems,
|
||||
}
|
||||
|
||||
//summary.FillUnknown()
|
||||
summary = srv.withProjectLabels(summary)
|
||||
|
||||
return summary.Sorted(), nil
|
||||
}
|
||||
@@ -220,6 +224,47 @@ func (srv *SummaryService) aggregateBy(heartbeats []*models.Heartbeat, summaryTy
|
||||
c <- models.SummaryItemContainer{Type: summaryType, Items: items}
|
||||
}
|
||||
|
||||
func (srv *SummaryService) withProjectLabels(summary *models.Summary) *models.Summary {
|
||||
newEntry := func(key string, total time.Duration) *models.SummaryItem {
|
||||
return &models.SummaryItem{
|
||||
Type: models.SummaryLabel,
|
||||
Key: key,
|
||||
Total: total,
|
||||
}
|
||||
}
|
||||
|
||||
allLabels, err := srv.projectLabelService.GetByUser(summary.UserID)
|
||||
if err != nil {
|
||||
logbuch.Error("failed to retrieve project labels for user summary ('%s', '%s', '%s')", summary.UserID, summary.FromTime.String(), summary.ToTime.String())
|
||||
return summary
|
||||
}
|
||||
|
||||
mappedProjects := make(map[string]*models.SummaryItem, len(summary.Projects))
|
||||
for _, p := range summary.Projects {
|
||||
mappedProjects[p.Key] = p
|
||||
}
|
||||
|
||||
var totalLabelTime time.Duration
|
||||
labelMap := make(map[string]*models.SummaryItem, 0)
|
||||
for _, l := range allLabels {
|
||||
if p, ok := mappedProjects[l.ProjectKey]; ok {
|
||||
if _, ok2 := labelMap[l.Label]; !ok2 {
|
||||
labelMap[l.Label] = newEntry(l.Label, 0)
|
||||
}
|
||||
labelMap[l.Label].Total += p.Total
|
||||
totalLabelTime += p.Total
|
||||
}
|
||||
}
|
||||
//labelMap[models.DefaultProjectLabel] = newEntry(models.DefaultProjectLabel, summary.TotalTimeBy(models.SummaryProject) / time.Second-totalLabelTime)
|
||||
|
||||
labels := make([]*models.SummaryItem, 0, len(labelMap))
|
||||
for _, v := range labelMap {
|
||||
labels = append(labels, v)
|
||||
}
|
||||
summary.Labels = labels
|
||||
return summary
|
||||
}
|
||||
|
||||
func (srv *SummaryService) mergeSummaries(summaries []*models.Summary) (*models.Summary, error) {
|
||||
if len(summaries) < 1 {
|
||||
return nil, errors.New("no summaries given")
|
||||
@@ -235,6 +280,7 @@ func (srv *SummaryService) mergeSummaries(summaries []*models.Summary) (*models.
|
||||
Editors: make([]*models.SummaryItem, 0),
|
||||
OperatingSystems: make([]*models.SummaryItem, 0),
|
||||
Machines: make([]*models.SummaryItem, 0),
|
||||
Labels: make([]*models.SummaryItem, 0),
|
||||
}
|
||||
|
||||
var processed = map[time.Time]bool{}
|
||||
@@ -263,6 +309,7 @@ func (srv *SummaryService) mergeSummaries(summaries []*models.Summary) (*models.
|
||||
finalSummary.Editors = srv.mergeSummaryItems(finalSummary.Editors, s.Editors)
|
||||
finalSummary.OperatingSystems = srv.mergeSummaryItems(finalSummary.OperatingSystems, s.OperatingSystems)
|
||||
finalSummary.Machines = srv.mergeSummaryItems(finalSummary.Machines, s.Machines)
|
||||
finalSummary.Labels = srv.mergeSummaryItems(finalSummary.Labels, s.Labels)
|
||||
|
||||
processed[hash] = true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user