8000 Report filter module #140 by KonstantinGasser · Pull Request #143 · dominikbraun/timetrace · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 22 additions & 10 deletions cli/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ import (
)

type reportOptions struct {
isBillable bool
projectKey string
outputFormat string
filePath string
startTime string
endTime string
isBillable bool
isNonBillable bool
projectKey string
outputFormat string
filePath string
startTime string
endTime string
}

func generateReportCommand(t *core.Timetrace) *cobra.Command {
Expand Down Expand Up @@ -57,8 +58,16 @@ func generateReportCommand(t *core.Timetrace) *cobra.Command {
if options.projectKey != "" {
filter = append(filter, core.FilterByProject(options.projectKey))
}
// wont hurt table will just be empty but makes sense to let the user know
if options.isBillable && options.isNonBillable {
out.Err("Cannot filter for billable and none billable records")
return
}
if options.isBillable {
filter = append(filter, core.FilterBillable)
filter = append(filter, core.FilterBillable(true))
}
if options.isNonBillable {
filter = append(filter, core.FilterBillable(false))
}

report, err := t.Report(filter...)
Expand All @@ -79,13 +88,13 @@ func generateReportCommand(t *core.Timetrace) *cobra.Command {
default:
projects, total := report.Table()
out.Table(
[]string{"Project", "Date", "Start", "End", "Billable", "Total"},
[]string{"Project", "Module", "Date", "Start", "End", "Billable", "Total"},
projects,
[]string{"", "", "", "", "TOTAL", total},
[]string{"", "", "", "", "", "TOTAL", total},
out.TableWithCellMerge(0), // merge cells over "Project" (index:0) column
out.TableFooterColor(
tablewriter.Colors{}, tablewriter.Colors{},
tablewriter.Colors{}, tablewriter.Colors{},
tablewriter.Colors{}, tablewriter.Colors{}, tablewriter.Colors{},
tablewriter.Colors{tablewriter.Bold}, // text "TOTAL"
tablewriter.Colors{tablewriter.FgGreenColor}), // digit of "TOTAL"
)
Expand All @@ -96,6 +105,9 @@ func generateReportCommand(t *core.Timetrace) *cobra.Command {
report.Flags().BoolVarP(&options.isBillable, "billable", "b",
false, "filter for only billable records")

report.Flags().BoolVarP(&options.isNonBillable, "non-billable", "n",
false, "filter for only none billable records")

report.Flags().StringVarP(&options.startTime, "start", "s",
"", "filter records from a given start date <YYYY-MM-DD>")

Expand Down
55 changes: 42 additions & 13 deletions core/reporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package core
import (
"encoding/json"
"fmt"
"strings"
"time"
)

Expand All @@ -14,15 +15,32 @@ func FilterNoneNilEndTime(r *Record) bool {
return r.End != nil
}

// FilterBillable returns true if a records is listed as billable
func FilterBillable(r *Record) bool {
return r.IsBillable
// FilterBillable returns a record filter for the billable flag
// that filters based on the passed display value
func FilterBillable(display bool) func(*Record) bool {
return func(r *Record) bool {
return r.IsBillable == display
}
}

// FilterByProject returns true if the record matches the given project keys
// if a module is given "mod@project" filter checks if module and project match
func FilterByProject(key string) func(*Record) bool {
module := strings.Split(key, "@") // if a module is given mod@project
return func(r *Record) bool {
return r.Project.Key == key
recordModule := strings.Split(r.Project.Key, "@")
// mod@key - mod@key -> search for project with module X
if r.Project.IsModule() && len(module) > 1 {
return recordModule[0] == module[0] && recordModule[1] == module[1]
}
// key - mod@key || key - key -> search for project where key
if len(module) == 1 {
if r.Project.IsModule() {
return recordModule[1] == key
}
return r.Project.Key == key
}
return false
}
}

Expand Down Expand Up @@ -66,18 +84,24 @@ type Reporter struct {

// sortAndMerge assigns each record in the given slice to the correct project key in the
// Reporter.report map and computes each projects total time.Duration
// projects with module will be grouped by
func (r *Reporter) sortAndMerge(records []*Record) {
for _, record := range records {
cached, ok := r.report[record.Project.Key]
key := record.Project.Key
keyParts := strings.Split(key, "@")
if len(keyParts) > 1 {
key = keyParts[1]
}
cached, ok := r.report[key]
if !ok {
r.report[record.Project.Key] = []*Record{record}
r.totals[record.Project.Key] = record.End.Sub(record.Start)
r.report[key] = []*Record{record}
r.totals[key] = record.End.Sub(record.Start)
continue
}
r.report[record.Project.Key] = append(cached, record)
r.report[key] = append(cached, record)
// compute updated total
tmp := r.totals[record.Project.Key] + record.End.Sub(record.Start)
r.totals[record.Project.Key] = tmp
tmp := r.totals[key] + record.End.Sub(record.Start)
r.totals[key] = tmp
}
}

Expand All @@ -90,7 +114,12 @@ func (r Reporter) Table() ([][]string, string) {

for key, records := range r.report {
for _, record := range records {
project := key
keyParts := strings.Split(record.Project.Key, "@")
module, key := "", keyParts[0]
if len(keyParts) > 1 {
module = keyParts[0]
key = keyParts[1]
}
billable := "no"
if record.IsBillable {
billable = "yes"
Expand All @@ -99,10 +128,10 @@ func (r Reporter) Table() ([][]string, string) {
start := r.t.Formatter().TimeString(record.Start)
end := r.t.Formatter().TimeString(*record.End)

rows = append(rows, []string{project, date, start, end, billable, ""})
rows = append(rows, []string{key, module, date, start, end, billable, ""})
}
// append with last row for total of tracked time for project
rows = append(rows, []string{key, "", "", "", defaultTotalSymbol, r.t.Formatter().FormatDuration(r.totals[key])})
rows = append(rows, []string{"", "", "", "", "", defaultTotalSymbol, r.t.Formatter().FormatDuration(r.totals[key])})
totalSum += r.totals[key]
}
return rows, r.t.Formatter().FormatDuration(totalSum)
Expand Down
71 changes: 71 additions & 0 deletions core/reporter_test.go
8000
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,74 @@ func TestReportFilterTimeRange(t *testing.T) {
}
}
}

func TestProjectFilter(t *testing.T) {
tt := []struct {
Key string
R Record
Expected bool
}{
{
Key: "test",
R: Record{
Project: &Project{
Key: "test",
},
},
Expected: true,
},
{
Key: "mod@test",
R: Record{
Project: &Project{
Key: "mod@test",
},
},
Expected: true,
},
{
Key: "test",
R: Record{
Project: &Project{
Key: "mod@test",
},
},
Expected: true,
},
{
Key: "mod@test",
R: Record{
Project: &Project{
Key: "test",
},
},
Expected: false,
},
{
Key: "mod@test",
R: Record{
Project: &Project{
Key: "not-test",
},
},
Expected: false,
},
{
Key: "test",
R: Record{
Project: &Project{
Key: "not-test",
},
},
Expected: false,
},
}

for _, tc := range tt {
filter := FilterByProject(tc.Key)
filtered := filter(&tc.R)
if filtered != tc.Expected {
t.Fatalf("project-filter failed: want %v, have: %v for key: %s and project.key: %s", tc.Expected, filtered, tc.Key, tc.R.Project.Key)
}
}
}
0