-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(worklog): initial worklog implementation
- Loading branch information
1 parent
47e5b92
commit b73017b
Showing
4 changed files
with
261 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package worklog | ||
|
||
import ( | ||
"fmt" | ||
"time" | ||
) | ||
|
||
// IDNameField stands for every field that has an ID and Name. | ||
type IDNameField struct { | ||
ID string | ||
Name string | ||
} | ||
|
||
// IsComplete indicates if the field has both ID and Name filled. | ||
// In case both fields are filled, it returns true, otherwise, false. | ||
func (f IDNameField) IsComplete() bool { | ||
return f.ID != "" && f.Name != "" | ||
} | ||
|
||
// Entry represents the worklog entry and contains all the necessary data. | ||
type Entry struct { | ||
Client IDNameField | ||
Project IDNameField | ||
Task IDNameField | ||
Summary string | ||
Notes string | ||
Start time.Time | ||
BillableDuration time.Duration | ||
UnbillableDuration time.Duration | ||
} | ||
|
||
// Key returns a unique, per entry key used for grouping similar entries. | ||
func (e *Entry) Key() string { | ||
return fmt.Sprintf("%s:%s:%s:%s", e.Project.Name, e.Task.Name, e.Summary, e.Start.Format("2006-01-02")) | ||
} | ||
|
||
// IsComplete indicates if the entry has all the necessary fields filled. | ||
// If all the necessary fields are complete it returns true, otherwise, false. | ||
func (e *Entry) IsComplete() bool { | ||
hasClient := e.Client.IsComplete() | ||
hasProject := e.Project.IsComplete() | ||
hasTask := e.Task.IsComplete() | ||
|
||
isMetadataFilled := hasProject && hasClient && hasTask && e.Summary != "" | ||
isTimeFilled := !e.Start.IsZero() && (e.BillableDuration.Seconds() > 0 || e.UnbillableDuration.Seconds() > 0) | ||
|
||
return isMetadataFilled && isTimeFilled | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package worklog_test | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"github.com/gabor-boros/minutes/internal/pkg/worklog" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func getTestEntry() worklog.Entry { | ||
start := time.Date(2021, 10, 2, 5, 0, 0, 0, time.Local) | ||
end := start.Add(time.Hour * 2) | ||
|
||
return worklog.Entry{ | ||
Client: worklog.IDNameField{ | ||
ID: "client-id", | ||
Name: "My Awesome Company", | ||
}, | ||
Project: worklog.IDNameField{ | ||
ID: "project-id", | ||
Name: "Internal projects", | ||
}, | ||
Task: worklog.IDNameField{ | ||
ID: "task-id", | ||
Name: "TASK-0123", | ||
}, | ||
Summary: "Write worklog transfer CLI tool", | ||
Notes: "It is a lot easier than expected", | ||
Start: start, | ||
BillableDuration: end.Sub(start), | ||
UnbillableDuration: 0, | ||
} | ||
} | ||
|
||
func TestIDNameFieldIsComplete(t *testing.T) { | ||
var field worklog.IDNameField | ||
|
||
assert.False(t, field.IsComplete()) | ||
|
||
field = worklog.IDNameField{ | ||
ID: "101", | ||
} | ||
assert.False(t, field.IsComplete()) | ||
|
||
field = worklog.IDNameField{ | ||
ID: "101", | ||
Name: "MARVEL-101", | ||
} | ||
assert.True(t, field.IsComplete()) | ||
} | ||
|
||
func TestEntryKey(t *testing.T) { | ||
entry := getTestEntry() | ||
assert.Equal(t, "Internal projects:TASK-0123:Write worklog transfer CLI tool:2021-10-02", entry.Key()) | ||
} | ||
|
||
func TestEntryIsComplete(t *testing.T) { | ||
entry := getTestEntry() | ||
assert.True(t, entry.IsComplete()) | ||
} | ||
|
||
func TestEntryIsCompleteIncomplete(t *testing.T) { | ||
var entry worklog.Entry | ||
|
||
entry = getTestEntry() | ||
entry.Client = worklog.IDNameField{} | ||
assert.False(t, entry.IsComplete()) | ||
|
||
entry = getTestEntry() | ||
entry.Project = worklog.IDNameField{} | ||
assert.False(t, entry.IsComplete()) | ||
|
||
entry = getTestEntry() | ||
entry.Task = worklog.IDNameField{} | ||
assert.False(t, entry.IsComplete()) | ||
|
||
entry = getTestEntry() | ||
entry.Summary = "" | ||
assert.False(t, entry.IsComplete()) | ||
|
||
entry = getTestEntry() | ||
entry.Start = time.Time{} | ||
assert.False(t, entry.IsComplete()) | ||
|
||
entry = getTestEntry() | ||
entry.BillableDuration = 0 | ||
entry.UnbillableDuration = 0 | ||
assert.False(t, entry.IsComplete()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package worklog | ||
|
||
// groupEntries ensures to group similar entries, identified by their key. | ||
// If the keys are matching for two entries, those will be merged and their duration will be summed up, notes will be | ||
// concatenated. | ||
func groupEntries(entries []Entry) []Entry { | ||
entryGroup := map[string]Entry{} | ||
|
||
for _, entry := range entries { | ||
key := entry.Key() | ||
storedEntry, isStored := entryGroup[key] | ||
|
||
if !isStored { | ||
entryGroup[key] = entry | ||
continue | ||
} | ||
|
||
storedEntry.BillableDuration += entry.BillableDuration | ||
storedEntry.UnbillableDuration += entry.UnbillableDuration | ||
|
||
noteSeparator := "" | ||
if storedEntry.Notes != "" && entry.Notes != storedEntry.Notes { | ||
if entry.Notes != "" { | ||
noteSeparator = "; " | ||
} | ||
|
||
storedEntry.Notes = storedEntry.Notes + noteSeparator + entry.Notes | ||
} | ||
|
||
entryGroup[key] = storedEntry | ||
} | ||
|
||
groupedEntries := make([]Entry, 0, len(entryGroup)) | ||
for _, item := range entryGroup { | ||
groupedEntries = append(groupedEntries, item) | ||
} | ||
|
||
return groupedEntries | ||
} | ||
|
||
// Worklog is the collection of multiple Entries. | ||
type Worklog struct { | ||
entries []Entry | ||
} | ||
|
||
// entryGroup returns those entries that are matching the completeness criteria. | ||
func (w *Worklog) entryGroup(isComplete bool) []Entry { | ||
var entries []Entry | ||
|
||
for _, entry := range w.entries { | ||
if entry.IsComplete() == isComplete { | ||
entries = append(entries, entry) | ||
} | ||
} | ||
|
||
return entries | ||
} | ||
|
||
// CompleteEntries returns those entries which necessary fields were filled. | ||
func (w *Worklog) CompleteEntries() []Entry { | ||
return w.entryGroup(true) | ||
} | ||
|
||
// IncompleteEntries is the opposite of CompleteEntries. | ||
func (w *Worklog) IncompleteEntries() []Entry { | ||
return w.entryGroup(false) | ||
} | ||
|
||
// NewWorklog creates a worklog from the given set of entries and groups them. | ||
func NewWorklog(entries []Entry) Worklog { | ||
return Worklog{ | ||
entries: groupEntries(entries), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package worklog_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/gabor-boros/minutes/internal/pkg/worklog" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestWorklogCompleteEntries(t *testing.T) { | ||
completeEntry := getTestEntry() | ||
|
||
otherCompleteEntry := getTestEntry() | ||
otherCompleteEntry.Notes = "Really" | ||
|
||
incompleteEntry := getTestEntry() | ||
incompleteEntry.Task = worklog.IDNameField{} | ||
|
||
wl := worklog.NewWorklog([]worklog.Entry{ | ||
completeEntry, | ||
otherCompleteEntry, | ||
incompleteEntry, | ||
}) | ||
|
||
entry := wl.CompleteEntries()[0] | ||
assert.Equal(t, "It is a lot easier than expected; Really", entry.Notes) | ||
assert.Equal(t, []worklog.Entry{entry}, wl.CompleteEntries()) | ||
} | ||
|
||
func TestWorklogIncompleteEntries(t *testing.T) { | ||
completeEntry := getTestEntry() | ||
|
||
incompleteEntry := getTestEntry() | ||
incompleteEntry.Task = worklog.IDNameField{} | ||
|
||
otherIncompleteEntry := getTestEntry() | ||
otherIncompleteEntry.Task = worklog.IDNameField{} | ||
otherIncompleteEntry.Notes = "Well, not that easy" | ||
|
||
wl := worklog.NewWorklog([]worklog.Entry{ | ||
completeEntry, | ||
incompleteEntry, | ||
otherIncompleteEntry, | ||
}) | ||
|
||
entry := wl.IncompleteEntries()[0] | ||
assert.Equal(t, "It is a lot easier than expected; Well, not that easy", entry.Notes) | ||
assert.Equal(t, []worklog.Entry{entry}, wl.IncompleteEntries()) | ||
} |