Skip to content

Commit

Permalink
refactor: simplify worklog creation
Browse files Browse the repository at this point in the history
  • Loading branch information
gabor-boros committed Oct 17, 2021
1 parent a7cd96f commit 15bdad7
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 79 deletions.
2 changes: 1 addition & 1 deletion Makefile
Expand Up @@ -33,7 +33,7 @@ test: deps ## Run tests

bench: deps ## Run benchmarks
# ^$ filters out every unit test, so only benchmarks will run
go test -run "^$" -benchmem -bench . ./...
go test -run '^$$' -benchmem -bench . ./...

coverage-report: ## Generate coverage report from previous test run
go tool cover -html "$(COVERAGE_OUT)" -o "$(COVERAGE_HTML)"
Expand Down
28 changes: 17 additions & 11 deletions internal/pkg/worklog/entry_test.go
Expand Up @@ -11,7 +11,7 @@ import (
"github.com/stretchr/testify/assert"
)

func getTestEntry() worklog.Entry {
func getCompleteTestEntry() worklog.Entry {
start := time.Date(2021, 10, 2, 5, 0, 0, 0, time.UTC)
end := start.Add(time.Hour * 2)

Expand All @@ -36,6 +36,12 @@ func getTestEntry() worklog.Entry {
}
}

func getIncompleteTestEntry() worklog.Entry {
entry := getCompleteTestEntry()
entry.Task = worklog.IDNameField{}
return entry
}

func TestIDNameFieldIsComplete(t *testing.T) {
var field worklog.IDNameField

Expand All @@ -54,39 +60,39 @@ func TestIDNameFieldIsComplete(t *testing.T) {
}

func TestEntryKey(t *testing.T) {
entry := getTestEntry()
entry := getCompleteTestEntry()
assert.Equal(t, "Internal projects:TASK-0123:Write worklog transfer CLI tool:2021-10-02", entry.Key())
}

func TestEntryIsComplete(t *testing.T) {
entry := getTestEntry()
entry := getCompleteTestEntry()
assert.True(t, entry.IsComplete())
}

func TestEntryIsCompleteIncomplete(t *testing.T) {
var entry worklog.Entry

entry = getTestEntry()
entry = getCompleteTestEntry()
entry.Client = worklog.IDNameField{}
assert.False(t, entry.IsComplete())

entry = getTestEntry()
entry = getCompleteTestEntry()
entry.Project = worklog.IDNameField{}
assert.False(t, entry.IsComplete())

entry = getTestEntry()
entry = getCompleteTestEntry()
entry.Task = worklog.IDNameField{}
assert.False(t, entry.IsComplete())

entry = getTestEntry()
entry = getCompleteTestEntry()
entry.Summary = ""
assert.False(t, entry.IsComplete())

entry = getTestEntry()
entry = getCompleteTestEntry()
entry.Start = time.Time{}
assert.False(t, entry.IsComplete())

entry = getTestEntry()
entry = getCompleteTestEntry()
entry.BillableDuration = 0
entry.UnbillableDuration = 0
assert.False(t, entry.IsComplete())
Expand All @@ -95,7 +101,7 @@ func TestEntryIsCompleteIncomplete(t *testing.T) {
func TestEntry_SplitDuration(t *testing.T) {
var splitBillable time.Duration
var splitUnbillable time.Duration
entry := getTestEntry()
entry := getCompleteTestEntry()

splitBillable, splitUnbillable = entry.SplitDuration(1)
assert.Equal(t, entry.BillableDuration, splitBillable)
Expand All @@ -108,7 +114,7 @@ func TestEntry_SplitDuration(t *testing.T) {
}

func TestEntry_SplitByTag(t *testing.T) {
entry := getTestEntry()
entry := getCompleteTestEntry()

regex, err := regexp.Compile(`^TASK-\d+$`)
require.Nil(t, err)
Expand Down
75 changes: 29 additions & 46 deletions internal/pkg/worklog/worklog.go
@@ -1,17 +1,32 @@
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{}
// Worklog is the collection of multiple Entries.
type Worklog struct {
completeEntries []Entry
incompleteEntries []Entry
}

// CompleteEntries returns those entries which necessary fields were filled.
func (w *Worklog) CompleteEntries() []Entry {
return w.completeEntries
}

// IncompleteEntries is the opposite of CompleteEntries.
func (w *Worklog) IncompleteEntries() []Entry {
return w.incompleteEntries
}

// NewWorklog creates a worklog from the given set of entries and merges them.
func NewWorklog(entries []Entry) Worklog {
worklog := Worklog{}
mergedEntries := map[string]Entry{}

for _, entry := range entries {
key := entry.Key()
storedEntry, isStored := entryGroup[key]
storedEntry, isStored := mergedEntries[key]

if !isStored {
entryGroup[key] = entry
mergedEntries[key] = entry
continue
}

Expand All @@ -27,48 +42,16 @@ func groupEntries(entries []Entry) []Entry {
storedEntry.Notes = storedEntry.Notes + noteSeparator + entry.Notes
}

entryGroup[key] = storedEntry
mergedEntries[key] = storedEntry
}

groupedEntries := make([]Entry, 0, len(entryGroup))
for _, entry := range entryGroup {
groupedEntries = append(groupedEntries, entry)
}

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)
for _, entry := range mergedEntries {
if entry.IsComplete() {
worklog.completeEntries = append(worklog.completeEntries, entry)
} else {
worklog.incompleteEntries = append(worklog.incompleteEntries, 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),
}
return worklog
}
99 changes: 78 additions & 21 deletions internal/pkg/worklog/worklog_test.go
Expand Up @@ -8,54 +8,111 @@ import (
"github.com/stretchr/testify/assert"
)

var worklogBenchResult worklog.Worklog
var newWorklogBenchResult worklog.Worklog
var completeEntriesBenchResult []worklog.Entry
var incompleteEntriesBenchResult []worklog.Entry

func benchmarkNewWorklog(b *testing.B, entryCount int) {
func benchNewWorklog(b *testing.B, entryCount int) {
b.StopTimer()

var entries []worklog.Entry

for i := 0; i != entryCount; i++ {
entry := getTestEntry()
entry := getCompleteTestEntry()
entry.Start.Add(time.Hour * time.Duration(i))
entries = append(entries, entry)
}

b.StartTimer()

var result worklog.Worklog
for n := 0; n != b.N; n++ {
result = worklog.NewWorklog(entries)
// always store the result to a package level variable
// so the compiler cannot eliminate the Benchmark itself.
newWorklogBenchResult = worklog.NewWorklog(entries)
}
}

func benchmarkCompleteEntries(b *testing.B, entryCount int) {
b.StopTimer()

var entries []worklog.Entry

for i := 0; i != entryCount; i++ {
entry := getCompleteTestEntry()
entry.Start.Add(time.Hour * time.Duration(i))
entries = append(entries, entry)
}

wl := worklog.NewWorklog(entries)

// always store the result to a package level variable
// so the compiler cannot eliminate the Benchmark itself.
worklogBenchResult = result
b.StartTimer()

for n := 0; n != b.N; n++ {
// always store the result to a package level variable
// so the compiler cannot eliminate the Benchmark itself.
completeEntriesBenchResult = wl.CompleteEntries()
}
}

func BenchmarkNewWorklog_5(b *testing.B) {
benchmarkNewWorklog(b, 5)
func benchmarkIncompleteEntries(b *testing.B, entryCount int) {
b.StopTimer()

var entries []worklog.Entry

for i := 0; i != entryCount; i++ {
entry := getIncompleteTestEntry()
entry.Start.Add(time.Hour * time.Duration(i))
entries = append(entries, entry)
}

wl := worklog.NewWorklog(entries)

b.StartTimer()

for n := 0; n != b.N; n++ {
// always store the result to a package level variable
// so the compiler cannot eliminate the Benchmark itself.
incompleteEntriesBenchResult = wl.IncompleteEntries()
}
}

func BenchmarkNewWorklog_10(b *testing.B) {
benchmarkNewWorklog(b, 10)
benchNewWorklog(b, 10)
_ = newWorklogBenchResult // Use the result to eliminate linter issues
}

func BenchmarkNewWorklog_1000(b *testing.B) {
benchNewWorklog(b, 1000)
_ = newWorklogBenchResult // Use the result to eliminate linter issues
}

func BenchmarkCompleteEntries_10(b *testing.B) {
benchmarkCompleteEntries(b, 10)
_ = completeEntriesBenchResult // Use the result to eliminate linter issues
}

func BenchmarkCompleteEntries_1000(b *testing.B) {
benchmarkCompleteEntries(b, 1000)
_ = completeEntriesBenchResult // Use the result to eliminate linter issues
}

func BenchmarkNewWorklog_50(b *testing.B) {
benchmarkNewWorklog(b, 50)
func BenchmarkIncompleteEntries_10(b *testing.B) {
benchmarkIncompleteEntries(b, 10)
_ = incompleteEntriesBenchResult // Use the result to eliminate linter issues
}

func BenchmarkNewWorklog_100(b *testing.B) {
benchmarkNewWorklog(b, 100)
func BenchmarkIncompleteEntries_1000(b *testing.B) {
benchmarkIncompleteEntries(b, 1000)
_ = incompleteEntriesBenchResult // Use the result to eliminate linter issues
}

func TestWorklogCompleteEntries(t *testing.T) {
completeEntry := getTestEntry()
completeEntry := getCompleteTestEntry()

otherCompleteEntry := getTestEntry()
otherCompleteEntry := getCompleteTestEntry()
otherCompleteEntry.Notes = "Really"

incompleteEntry := getTestEntry()
incompleteEntry := getCompleteTestEntry()
incompleteEntry.Task = worklog.IDNameField{}

wl := worklog.NewWorklog([]worklog.Entry{
Expand All @@ -70,12 +127,12 @@ func TestWorklogCompleteEntries(t *testing.T) {
}

func TestWorklogIncompleteEntries(t *testing.T) {
completeEntry := getTestEntry()
completeEntry := getCompleteTestEntry()

incompleteEntry := getTestEntry()
incompleteEntry := getCompleteTestEntry()
incompleteEntry.Task = worklog.IDNameField{}

otherIncompleteEntry := getTestEntry()
otherIncompleteEntry := getCompleteTestEntry()
otherIncompleteEntry.Task = worklog.IDNameField{}
otherIncompleteEntry.Notes = "Well, not that easy"

Expand Down

0 comments on commit 15bdad7

Please sign in to comment.