diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f96df47..53ff17db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- new config `show-client` that sets the reports/output of time entries to show its client, if exists + ## [v0.50.1] - 2024-05-25 ### Fixed diff --git a/pkg/cmd/config/config.go b/pkg/cmd/config/config.go index a408f7af..69a8437e 100644 --- a/pkg/cmd/config/config.go +++ b/pkg/cmd/config/config.go @@ -25,6 +25,8 @@ var validParameters = cmdcompl.ValidArgsMap{ "missing required values", cmdutil.CONF_SHOW_TASKS: "should show an extra column with the task " + "description", + cmdutil.CONF_SHOW_CLIENT: "should show an extra column with the client " + + "description", cmdutil.CONF_DESCR_AUTOCOMP: "autocomplete description looking at " + "recent time entries", cmdutil.CONF_DESCR_AUTOCOMP_DAYS: "how many days should be considered " + diff --git a/pkg/cmd/config/init/init.go b/pkg/cmd/config/init/init.go index 55193585..e2bf0469 100644 --- a/pkg/cmd/config/init/init.go +++ b/pkg/cmd/config/init/init.go @@ -140,6 +140,12 @@ func NewCmdInit(f cmdutil.Factory) *cobra.Command { return err } + if err := updateFlag(i, config, cmdutil.CONF_SHOW_CLIENT, + `Should show client on time entries as a separated column?`, + ); err != nil { + return err + } + if err := updateFlag(i, config, cmdutil.CONF_SHOW_TOTAL_DURATION, `Should show a line with the sum of `+ `the time entries duration?`, diff --git a/pkg/cmd/config/init/init_test.go b/pkg/cmd/config/init/init_test.go index b8a7bebb..f488b817 100644 --- a/pkg/cmd/config/init/init_test.go +++ b/pkg/cmd/config/init/init_test.go @@ -98,6 +98,7 @@ func TestInitCmd(t *testing.T) { setBoolFn(config, cmdutil.CONF_ALLOW_INCOMPLETE, false, false) setBoolFn(config, cmdutil.CONF_SHOW_TASKS, true, true) + setBoolFn(config, cmdutil.CONF_SHOW_CLIENT, true, true) setBoolFn(config, cmdutil.CONF_SHOW_TOTAL_DURATION, true, true) setBoolFn(config, cmdutil.CONF_DESCR_AUTOCOMP, false, true) @@ -180,6 +181,10 @@ func TestInitCmd(t *testing.T) { c.SendLine("") c.ExpectString("Yes") + c.ExpectString("show client on time entries") + c.SendLine("") + c.ExpectString("Yes") + c.ExpectString("sum of the time entries duration?") c.SendLine("yes") c.ExpectString("Yes") diff --git a/pkg/cmd/config/set/set_test.go b/pkg/cmd/config/set/set_test.go index 32c71297..61c9a7f3 100644 --- a/pkg/cmd/config/set/set_test.go +++ b/pkg/cmd/config/set/set_test.go @@ -4,10 +4,10 @@ import ( "bytes" "testing" + "github.com/lucassabreu/clockify-cli/internal/mocks" "github.com/lucassabreu/clockify-cli/pkg/cmd/config/set" "github.com/lucassabreu/clockify-cli/pkg/cmdcompl" "github.com/lucassabreu/clockify-cli/pkg/cmdutil" - "github.com/lucassabreu/clockify-cli/internal/mocks" "github.com/stretchr/testify/assert" ) @@ -77,6 +77,18 @@ func TestSetCmdRun(t *testing.T) { return c }, }, + tc{ + name: "set show client", + args: []string{cmdutil.CONF_SHOW_CLIENT, "true"}, + config: func(t *testing.T) cmdutil.Config { + c := mocks.NewMockConfig(t) + c.On("SetString", cmdutil.CONF_SHOW_CLIENT, + "true"). + Return(nil).Once() + c.On("Save").Once().Return(nil) + return c + }, + }, } for _, tc := range ts { diff --git a/pkg/cmd/time-entry/util/report.go b/pkg/cmd/time-entry/util/report.go index b71ddae0..caddfe33 100644 --- a/pkg/cmd/time-entry/util/report.go +++ b/pkg/cmd/time-entry/util/report.go @@ -119,17 +119,21 @@ func PrintTimeEntries( case of.DurationFormatted: return output.TimeEntriesTotalDurationOnlyFormatted(tes, out) default: - opts := []output.TimeEntryOutputOpt{ - output.WithTimeFormat(of.TimeFormat)} + opts := output.NewTimeEntryOutputOptions(). + WithTimeFormat(of.TimeFormat) if config.GetBool(cmdutil.CONF_SHOW_TASKS) { - opts = append(opts, output.WithShowTasks()) + opts = opts.WithShowTasks() + } + + if config.GetBool(cmdutil.CONF_SHOW_CLIENT) { + opts = opts.WithShowClients() } if config.GetBool(cmdutil.CONF_SHOW_TOTAL_DURATION) { - opts = append(opts, output.WithTotalDuration()) + opts = opts.WithTotalDuration() } - return output.TimeEntriesPrint(opts...)(tes, out) + return output.TimeEntriesPrint(opts)(tes, out) } } diff --git a/pkg/cmdutil/config.go b/pkg/cmdutil/config.go index 309fa143..75e69c8b 100644 --- a/pkg/cmdutil/config.go +++ b/pkg/cmdutil/config.go @@ -20,6 +20,7 @@ const ( CONF_TOKEN = "token" CONF_ALLOW_INCOMPLETE = "allow-incomplete" CONF_SHOW_TASKS = "show-task" + CONF_SHOW_CLIENT = "show-client" CONF_DESCR_AUTOCOMP = "description-autocomplete" CONF_DESCR_AUTOCOMP_DAYS = "description-autocomplete-days" CONF_SHOW_TOTAL_DURATION = "show-total-duration" diff --git a/pkg/output/time-entry/default.go b/pkg/output/time-entry/default.go index 2b4c380c..cfc2d3ad 100644 --- a/pkg/output/time-entry/default.go +++ b/pkg/output/time-entry/default.go @@ -36,67 +36,66 @@ const ( // entries type TimeEntryOutputOptions struct { ShowTasks bool + ShowClients bool ShowTotalDuration bool TimeFormat string } -// WithTimeFormat sets the date-time output format -func WithTimeFormat(format string) TimeEntryOutputOpt { - return func(teo *TimeEntryOutputOptions) error { - teo.TimeFormat = format - return nil +// NewTimeEntryOutputOptions creates a default TimeEntryOutputOptions +func NewTimeEntryOutputOptions() TimeEntryOutputOptions { + return TimeEntryOutputOptions{ + TimeFormat: TimeFormatSimple, + ShowTasks: false, + ShowClients: false, + ShowTotalDuration: false, } } +// WithTimeFormat sets the date-time output format +func (teo TimeEntryOutputOptions) WithTimeFormat( + format string) TimeEntryOutputOptions { + teo.TimeFormat = format + return teo + +} + // WithShowTasks shows a new column with the task of the time entry -func WithShowTasks() TimeEntryOutputOpt { - return func(teoo *TimeEntryOutputOptions) error { - teoo.ShowTasks = true - return nil - } +func (teo TimeEntryOutputOptions) WithShowTasks() TimeEntryOutputOptions { + teo.ShowTasks = true + return teo +} + +// WithShowCliens shows a new column with the client of the time entry +func (teo TimeEntryOutputOptions) WithShowClients() TimeEntryOutputOptions { + teo.ShowClients = true + return teo } // WithTotalDuration shows a footer with the sum of the durations of the time // entries -func WithTotalDuration() TimeEntryOutputOpt { - return func(teoo *TimeEntryOutputOptions) error { - teoo.ShowTotalDuration = true - return nil - } +func (teo TimeEntryOutputOptions) WithTotalDuration() TimeEntryOutputOptions { + teo.ShowTotalDuration = true + return teo } -// TimeEntryOutputOpt allows the setting of TimeEntryOutputOptions values -type TimeEntryOutputOpt func(*TimeEntryOutputOptions) error - // TimeEntriesPrint will print more details -func TimeEntriesPrint(opts ...TimeEntryOutputOpt) func([]dto.TimeEntry, io.Writer) error { - options := &TimeEntryOutputOptions{ - TimeFormat: TimeFormatSimple, - ShowTasks: false, - ShowTotalDuration: false, - } - - for _, o := range opts { - err := o(options) - if err != nil { - return func(te []dto.TimeEntry, w io.Writer) error { return err } - } - } - +func TimeEntriesPrint( + options TimeEntryOutputOptions) func([]dto.TimeEntry, io.Writer) error { return func(timeEntries []dto.TimeEntry, w io.Writer) error { tw := tablewriter.NewWriter(w) - taskColumn := 6 projectColumn := 4 - header := []string{"ID", "Start", "End", "Dur", - "Project", "Description", "Tags"} + header := []string{"ID", "Start", "End", "Dur", "Project"} + if options.ShowTasks { - header = append( - header[:taskColumn], - header[taskColumn-1:]..., - ) - header[taskColumn] = "Task" + header = append(header, "Client") } + if options.ShowTasks { + header = append(header, "Task") + } + + header = append(header, "Description", "Tags") + tw.SetHeader(header) tw.SetRowLine(true) if width, _, err := term.GetSize(int(os.Stdout.Fd())); err == nil { @@ -124,18 +123,31 @@ func TimeEntriesPrint(opts ...TimeEntryOutputOpt) func([]dto.TimeEntry, io.Write end.In(time.Local).Format(options.TimeFormat), durationToString(end.Sub(t.TimeInterval.Start)), projectName, - t.Description, - strings.Join(tagsToStringSlice(t.Tags), "\n"), + } + + if options.ShowClients { + client := "" + if t.Project.ClientName != "" { + colors[len(line)] = colors[projectColumn] + client = t.Project.ClientName + } + line = append(line, client) } if options.ShowTasks { - line = append(line[:taskColumn], line[taskColumn-1:]...) - line[taskColumn] = "" + task := "" if t.Task != nil { - line[taskColumn] = fmt.Sprintf("%s (%s)", t.Task.Name, t.Task.ID) + task = fmt.Sprintf("%s (%s)", t.Task.Name, t.Task.ID) } + line = append(line, task) } + line = append( + line, + t.Description, + strings.Join(tagsToStringSlice(t.Tags), "\n"), + ) + tw.Rich(line, colors) }