fix: generate dummy items for missing types in historic summary data

This commit is contained in:
Ferdinand Mütsch 2020-08-29 23:16:21 +02:00
parent 12cc4cd9cf
commit 82ed386359
4 changed files with 69 additions and 3 deletions

View File

@ -13,6 +13,8 @@ const (
SummaryMachine uint8 = 4
)
const UnknownSummaryKey = "unknown"
type Summary struct {
ID uint `json:"-" gorm:"primary_key"`
UserID string `json:"user_id" gorm:"not null; index:idx_time_summary_user"`
@ -45,3 +47,55 @@ type SummaryViewModel struct {
Success string
ApiKey string
}
/* Augments the summary in a way that at least one item is present for every type.
If a summary has zero items for a given type, but one or more for any of the other types,
the total summary duration can be derived from those and inserted as a dummy-item with key "unknown"
for the missing type.
For instance, the machine type was introduced post hoc. Accordingly, no "machine"-information is present in
the data for old heartbeats and summaries. If a user has two years of data without machine information and
one day with such, a "machine"-chart plotted from that data will reference a way smaller absolute total amount
of time than the other ones.
To avoid having to modify persisted data retrospectively, i.e. inserting a dummy SummaryItem for the new type,
such is generated dynamically here, considering the "machine" for all old heartbeats "unknown".
*/
func (s *Summary) FillUnknown() {
types := []uint8{SummaryProject, SummaryLanguage, SummaryEditor, SummaryOS, SummaryMachine}
missingTypes := make([]uint8, 0)
typeItems := map[uint8]*[]*SummaryItem{
SummaryProject: &s.Projects,
SummaryLanguage: &s.Languages,
SummaryEditor: &s.Editors,
SummaryOS: &s.OperatingSystems,
SummaryMachine: &s.Machines,
}
var somePresentType uint8
for _, t := range types {
if len(*typeItems[t]) == 0 {
missingTypes = append(missingTypes, t)
} else {
somePresentType = t
}
}
// can't proceed if entire summary is empty
if len(missingTypes) == len(types) {
return
}
// calculate total duration from any of the present sets of items
var timeSum time.Duration
for _, item := range *typeItems[somePresentType] {
timeSum += item.Total
}
// construct dummy item for all missing types
for _, t := range missingTypes {
*typeItems[t] = append(*typeItems[t], &SummaryItem{
Type: t,
Key: UnknownSummaryKey,
Total: timeSum,
})
}
}

View File

@ -103,6 +103,10 @@ func (srv *SummaryService) Construct(from, to time.Time, user *models.User, reco
if len(existingSummaries) > 0 {
realFrom = existingSummaries[0].FromTime
realTo = existingSummaries[len(existingSummaries)-1].ToTime
for _, summary := range existingSummaries {
summary.FillUnknown()
}
}
if len(heartbeats) > 0 {
t1, t2 := time.Time(heartbeats[0].Time), time.Time(heartbeats[len(heartbeats)-1].Time)
@ -197,7 +201,7 @@ func (srv *SummaryService) aggregateBy(heartbeats []*models.Heartbeat, summaryTy
}
if key == "" {
key = "unknown"
key = models.UnknownSummaryKey
}
if aliasedKey, err := srv.AliasService.GetAliasOrDefault(user.ID, summaryType, key); err == nil {

View File

@ -57,7 +57,7 @@ function draw() {
.map(p => {
return {
label: p.key,
data: [parseInt(p.total)],
data: [parseInt(p.total) / 60],
backgroundColor: getRandomColor(p.key)
}
})
@ -68,6 +68,14 @@ function draw() {
legend: {
display: false
},
scales: {
xAxes: [{
scaleLabel: {
display: true,
labelString: 'Minutes'
}
}]
},
maintainAspectRatio: false,
onResize: onChartResize
}

View File

@ -1 +1 @@
1.8.0
1.8.1