From e16e52255b51deb68a69b5bb18f6461ca6a16df2 Mon Sep 17 00:00:00 2001 From: Lucas dos Santos Abreu Date: Fri, 16 Feb 2024 08:51:06 -0300 Subject: [PATCH] feat: search-project-with-client config (#256) * feat: search-project-with-client config * fix: tests * fix: doc block --- CHANGELOG.md | 5 +++ internal/mocks/mock_Config.go | 45 +++++++++++++++++++ internal/mocks/simple_config.go | 35 +++++++++------ pkg/cmd/config/init/init.go | 10 +++++ pkg/cmd/config/init/init_test.go | 11 ++++- pkg/cmd/project/get/get.go | 2 +- pkg/cmd/project/get/get_test.go | 2 + pkg/cmd/task/add/add_test.go | 32 ++++++------- pkg/cmd/task/delete/delete.go | 2 +- pkg/cmd/task/delete/delete_test.go | 4 ++ pkg/cmd/task/done/done.go | 2 +- pkg/cmd/task/done/done_test.go | 3 ++ pkg/cmd/task/edit/edit_test.go | 28 ++++++------ pkg/cmd/task/list/list.go | 2 +- pkg/cmd/task/list/list_test.go | 2 + pkg/cmd/task/quick-add/quick-add.go | 3 +- pkg/cmd/task/quick-add/quick-add_test.go | 12 ++--- pkg/cmd/task/util/read-flags.go | 2 +- pkg/cmd/time-entry/in/in_test.go | 28 +++++++++++- pkg/cmd/time-entry/report/today/today_test.go | 7 +-- pkg/cmd/time-entry/report/util/report.go | 5 ++- .../report/util/reportwithrange_test.go | 3 ++ pkg/cmd/time-entry/util/name-for-id.go | 6 +-- pkg/cmdutil/config.go | 38 ++++++++++------ pkg/search/project.go | 14 +++++- 25 files changed, 219 insertions(+), 84 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2b48e3c..93a5bf76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- new config `search-project-with-client` to set whether or not the cli should lookup projects using the + client's name too + ## [v0.47.0] - 2024-02-09 ### Added diff --git a/internal/mocks/mock_Config.go b/internal/mocks/mock_Config.go index 2b851f8f..4c3023b3 100644 --- a/internal/mocks/mock_Config.go +++ b/internal/mocks/mock_Config.go @@ -525,6 +525,51 @@ func (_c *MockConfig_IsInteractive_Call) RunAndReturn(run func() bool) *MockConf return _c } +// IsSearchProjectWithClientsName provides a mock function with given fields: +func (_m *MockConfig) IsSearchProjectWithClientsName() bool { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for IsSearchProjectWithClientsName") + } + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// MockConfig_IsSearchProjectWithClientsName_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsSearchProjectWithClientsName' +type MockConfig_IsSearchProjectWithClientsName_Call struct { + *mock.Call +} + +// IsSearchProjectWithClientsName is a helper method to define mock.On call +func (_e *MockConfig_Expecter) IsSearchProjectWithClientsName() *MockConfig_IsSearchProjectWithClientsName_Call { + return &MockConfig_IsSearchProjectWithClientsName_Call{Call: _e.mock.On("IsSearchProjectWithClientsName")} +} + +func (_c *MockConfig_IsSearchProjectWithClientsName_Call) Run(run func()) *MockConfig_IsSearchProjectWithClientsName_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockConfig_IsSearchProjectWithClientsName_Call) Return(_a0 bool) *MockConfig_IsSearchProjectWithClientsName_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockConfig_IsSearchProjectWithClientsName_Call) RunAndReturn(run func() bool) *MockConfig_IsSearchProjectWithClientsName_Call { + _c.Call.Return(run) + return _c +} + // LogLevel provides a mock function with given fields: func (_m *MockConfig) LogLevel() string { ret := _m.Called() diff --git a/internal/mocks/simple_config.go b/internal/mocks/simple_config.go index 8f0e858d..29d35827 100644 --- a/internal/mocks/simple_config.go +++ b/internal/mocks/simple_config.go @@ -5,20 +5,27 @@ import "github.com/lucassabreu/clockify-cli/pkg/cmdutil" // SimpleConfig is used to set configs for tests were changing the config or // accessing them with Get and All is not important type SimpleConfig struct { - WorkweekDays []string - Interactive bool - InteractivePageSizeNumber int - AllowNameForID bool - UserID string - Workspace string - Token string - AllowIncomplete bool - ShowTask bool - DescriptionAutocomplete bool - DescriptionAutocompleteDays int - ShowTotalDuration bool - LogLevelValue string - AllowArchivedTags bool + WorkweekDays []string + Interactive bool + InteractivePageSizeNumber int + AllowNameForID bool + UserID string + Workspace string + Token string + AllowIncomplete bool + ShowTask bool + DescriptionAutocomplete bool + DescriptionAutocompleteDays int + ShowTotalDuration bool + LogLevelValue string + AllowArchivedTags bool + SearchProjectWithClientsName bool +} + +// IsSearchProjectWithClientsName defines if the project name for ID should +// include the client's name +func (s *SimpleConfig) IsSearchProjectWithClientsName() bool { + return s.SearchProjectWithClientsName } // InteractivePageSize sets how many items are shown when prompting diff --git a/pkg/cmd/config/init/init.go b/pkg/cmd/config/init/init.go index f51914a9..55193585 100644 --- a/pkg/cmd/config/init/init.go +++ b/pkg/cmd/config/init/init.go @@ -95,6 +95,16 @@ func NewCmdInit(f cmdutil.Factory) *cobra.Command { return err } + if config.IsAllowNameForID() { + if err := updateFlag(i, config, + cmdutil.CONF_SEARCH_PROJECTS_WITH_CLIENT_NAME, + `Should search projects looking into their `+ + `client's name too?`, + ); err != nil { + return err + } + } + if err := updateFlag(i, config, cmdutil.CONF_INTERACTIVE, `Should use "Interactive Mode" by default?`, ); err != nil { diff --git a/pkg/cmd/config/init/init_test.go b/pkg/cmd/config/init/init_test.go index 4134ee46..b8a7bebb 100644 --- a/pkg/cmd/config/init/init_test.go +++ b/pkg/cmd/config/init/init_test.go @@ -73,7 +73,12 @@ func TestInitCmd(t *testing.T) { setStringFn(config, cmdutil.CONF_USER_ID, "user-1") - setBoolFn(config, cmdutil.CONF_ALLOW_NAME_FOR_ID, false, true) + setBoolFn(config, cmdutil.CONF_ALLOW_NAME_FOR_ID, false, true). + Run(func(args mock.Arguments) { + config.EXPECT().IsAllowNameForID().Return(true) + }) + setBoolFn(config, cmdutil.CONF_SEARCH_PROJECTS_WITH_CLIENT_NAME, + false, true) setBoolFn(config, cmdutil.CONF_INTERACTIVE, false, false) config.EXPECT().GetInt(cmdutil.CONF_INTERACTIVE_PAGE_SIZE). @@ -130,6 +135,10 @@ func TestInitCmd(t *testing.T) { c.SendLine("y") c.ExpectString("Yes") + c.ExpectString("search projects looking into their client's name") + c.SendLine("y") + c.ExpectString("Yes") + c.ExpectString("Interactive Mode\" by default?") c.SendLine("n") c.ExpectString("No") diff --git a/pkg/cmd/project/get/get.go b/pkg/cmd/project/get/get.go index 4bfdc2e5..14d2af62 100644 --- a/pkg/cmd/project/get/get.go +++ b/pkg/cmd/project/get/get.go @@ -63,7 +63,7 @@ func NewCmdGet( if f.Config().IsAllowNameForID() { if p.ProjectID, err = search.GetProjectByName( - c, p.Workspace, p.ProjectID, ""); err != nil { + c, f.Config(), p.Workspace, p.ProjectID, ""); err != nil { return err } } diff --git a/pkg/cmd/project/get/get_test.go b/pkg/cmd/project/get/get_test.go index 3db7d220..107cb585 100644 --- a/pkg/cmd/project/get/get_test.go +++ b/pkg/cmd/project/get/get_test.go @@ -124,6 +124,7 @@ func TestCmdGet(t *testing.T) { cf := mocks.NewMockConfig(t) f.EXPECT().Config().Return(cf) cf.EXPECT().IsAllowNameForID().Return(true) + cf.EXPECT().IsSearchProjectWithClientsName().Return(true) c := mocks.NewMockClient(t) f.EXPECT().Client().Return(c, nil) @@ -155,6 +156,7 @@ func TestCmdGet(t *testing.T) { cf := mocks.NewMockConfig(t) f.EXPECT().Config().Return(cf) cf.EXPECT().IsAllowNameForID().Return(true) + cf.EXPECT().IsSearchProjectWithClientsName().Return(true) c := mocks.NewMockClient(t) f.EXPECT().Client().Return(c, nil) diff --git a/pkg/cmd/task/add/add_test.go b/pkg/cmd/task/add/add_test.go index eb475abe..c2387dee 100644 --- a/pkg/cmd/task/add/add_test.go +++ b/pkg/cmd/task/add/add_test.go @@ -85,9 +85,7 @@ func TestCmdAdd(t *testing.T) { f.On("GetWorkspaceID"). Return("w", nil) - cf := mocks.NewMockConfig(t) - f.On("Config").Return(cf) - cf.On("IsAllowNameForID").Return(false) + f.EXPECT().Config().Return(&mocks.SimpleConfig{}) f.On("Client").Return(nil, errors.New("client error")) return f @@ -115,9 +113,9 @@ func TestCmdAdd(t *testing.T) { Return("w", nil) f.On("Client").Return(c, nil) - cf := mocks.NewMockConfig(t) - f.On("Config").Return(cf) - cf.On("IsAllowNameForID").Return(true) + f.EXPECT().Config().Return(&mocks.SimpleConfig{ + AllowNameForID: true, + }) c.On("GetProjects", api.GetProjectsParam{ Workspace: "w", @@ -138,9 +136,9 @@ func TestCmdAdd(t *testing.T) { Return("w", nil) f.On("Client").Return(c, nil) - cf := mocks.NewMockConfig(t) - f.On("Config").Return(cf) - cf.On("IsAllowNameForID").Return(true) + f.EXPECT().Config().Return(&mocks.SimpleConfig{ + AllowNameForID: true, + }) c.On("GetProjects", api.GetProjectsParam{ Workspace: "w", @@ -168,9 +166,7 @@ func TestCmdAdd(t *testing.T) { Return("w", nil) f.On("Client").Return(c, nil) - cf := mocks.NewMockConfig(t) - f.On("Config").Return(cf) - cf.On("IsAllowNameForID").Return(false) + f.EXPECT().Config().Return(&mocks.SimpleConfig{}) c.On("AddTask", api.AddTaskParam{ Workspace: "w", @@ -196,9 +192,9 @@ func TestCmdAdd(t *testing.T) { Return("w", nil) f.On("Client").Return(c, nil) - cf := mocks.NewMockConfig(t) - f.On("Config").Return(cf) - cf.On("IsAllowNameForID").Return(true) + f.EXPECT().Config().Return(&mocks.SimpleConfig{ + AllowNameForID: true, + }) c.On("GetProjects", api.GetProjectsParam{ Workspace: "w", @@ -237,9 +233,9 @@ func TestCmdAdd(t *testing.T) { Return("w", nil) f.On("Client").Return(c, nil) - cf := mocks.NewMockConfig(t) - f.On("Config").Return(cf) - cf.On("IsAllowNameForID").Return(true) + f.EXPECT().Config().Return(&mocks.SimpleConfig{ + AllowNameForID: true, + }) c.On("GetProjects", api.GetProjectsParam{ Workspace: "w", diff --git a/pkg/cmd/task/delete/delete.go b/pkg/cmd/task/delete/delete.go index 0647478a..092c73bf 100644 --- a/pkg/cmd/task/delete/delete.go +++ b/pkg/cmd/task/delete/delete.go @@ -70,7 +70,7 @@ func NewCmdDelete( if f.Config().IsAllowNameForID() { if project, err = search.GetProjectByName( - c, w, project, ""); err != nil { + c, f.Config(), w, project, ""); err != nil { return err } diff --git a/pkg/cmd/task/delete/delete_test.go b/pkg/cmd/task/delete/delete_test.go index 77f558a3..3b92fc11 100644 --- a/pkg/cmd/task/delete/delete_test.go +++ b/pkg/cmd/task/delete/delete_test.go @@ -74,6 +74,7 @@ func TestCmdDelete(t *testing.T) { cf := mocks.NewMockConfig(t) f.On("Config").Return(cf) cf.On("IsAllowNameForID").Return(true) + cf.On("IsSearchProjectWithClientsName").Return(true) c := mocks.NewMockClient(t) f.On("Client").Return(c, nil) @@ -96,6 +97,7 @@ func TestCmdDelete(t *testing.T) { cf := mocks.NewMockConfig(t) f.On("Config").Return(cf) cf.On("IsAllowNameForID").Return(true) + cf.On("IsSearchProjectWithClientsName").Return(true) c := mocks.NewMockClient(t) f.On("Client").Return(c, nil) @@ -124,6 +126,7 @@ func TestCmdDelete(t *testing.T) { cf := mocks.NewMockConfig(t) f.On("Config").Return(cf) cf.On("IsAllowNameForID").Return(true) + cf.On("IsSearchProjectWithClientsName").Return(true) c := mocks.NewMockClient(t) f.On("Client").Return(c, nil) @@ -162,6 +165,7 @@ func TestCmdDelete(t *testing.T) { cf := mocks.NewMockConfig(t) f.On("Config").Return(cf) cf.On("IsAllowNameForID").Return(true) + cf.On("IsSearchProjectWithClientsName").Return(true) c := mocks.NewMockClient(t) f.On("Client").Return(c, nil) diff --git a/pkg/cmd/task/done/done.go b/pkg/cmd/task/done/done.go index eec5468c..8cfdafcf 100644 --- a/pkg/cmd/task/done/done.go +++ b/pkg/cmd/task/done/done.go @@ -88,7 +88,7 @@ func NewCmdDone( if f.Config().IsAllowNameForID() { if project, err = search.GetProjectByName( - c, workspace, project, ""); err != nil { + c, f.Config(), workspace, project, ""); err != nil { return err } diff --git a/pkg/cmd/task/done/done_test.go b/pkg/cmd/task/done/done_test.go index fc3369cc..e710922d 100644 --- a/pkg/cmd/task/done/done_test.go +++ b/pkg/cmd/task/done/done_test.go @@ -146,6 +146,7 @@ func TestCmdDone(t *testing.T) { cf := mocks.NewMockConfig(t) f.On("Config").Return(cf) cf.On("IsAllowNameForID").Return(true) + cf.On("IsSearchProjectWithClientsName").Return(true) c.On("GetProjects", api.GetProjectsParam{ Workspace: "w", @@ -178,6 +179,7 @@ func TestCmdDone(t *testing.T) { cf := mocks.NewMockConfig(t) f.On("Config").Return(cf) cf.On("IsAllowNameForID").Return(true) + cf.On("IsSearchProjectWithClientsName").Return(true) c.On("GetProjects", api.GetProjectsParam{ Workspace: "w", @@ -237,6 +239,7 @@ func TestCmdDone(t *testing.T) { cf := mocks.NewMockConfig(t) f.On("Config").Return(cf) cf.On("IsAllowNameForID").Return(true) + cf.On("IsSearchProjectWithClientsName").Return(true) c.On("GetProjects", api.GetProjectsParam{ Workspace: "w", diff --git a/pkg/cmd/task/edit/edit_test.go b/pkg/cmd/task/edit/edit_test.go index 1df4eca3..ca3eca4f 100644 --- a/pkg/cmd/task/edit/edit_test.go +++ b/pkg/cmd/task/edit/edit_test.go @@ -94,9 +94,7 @@ func TestCmdEdit(t *testing.T) { f.On("GetWorkspaceID"). Return("w", nil) - cf := mocks.NewMockConfig(t) - f.On("Config").Return(cf) - cf.On("IsAllowNameForID").Return(false) + f.EXPECT().Config().Return(&mocks.SimpleConfig{}) f.On("Client").Return(nil, errors.New("client error")) return f @@ -147,9 +145,9 @@ func TestCmdEdit(t *testing.T) { Return("w", nil) f.On("Client").Return(c, nil) - cf := mocks.NewMockConfig(t) - f.On("Config").Return(cf) - cf.On("IsAllowNameForID").Return(true) + f.EXPECT().Config().Return(&mocks.SimpleConfig{ + AllowNameForID: true, + }) c.On("GetProjects", api.GetProjectsParam{ Workspace: "w", @@ -177,9 +175,9 @@ func TestCmdEdit(t *testing.T) { Return("w", nil) f.On("Client").Return(c, nil) - cf := mocks.NewMockConfig(t) - f.On("Config").Return(cf) - cf.On("IsAllowNameForID").Return(true) + f.EXPECT().Config().Return(&mocks.SimpleConfig{ + AllowNameForID: true, + }) c.On("GetProjects", api.GetProjectsParam{ Workspace: "w", @@ -239,9 +237,9 @@ func TestCmdEdit(t *testing.T) { Return("w", nil) f.On("Client").Return(c, nil) - cf := mocks.NewMockConfig(t) - f.On("Config").Return(cf) - cf.On("IsAllowNameForID").Return(true) + f.EXPECT().Config().Return(&mocks.SimpleConfig{ + AllowNameForID: true, + }) c.On("GetProjects", api.GetProjectsParam{ Workspace: "w", @@ -290,9 +288,9 @@ func TestCmdEdit(t *testing.T) { Return("w", nil) f.On("Client").Return(c, nil) - cf := mocks.NewMockConfig(t) - f.On("Config").Return(cf) - cf.On("IsAllowNameForID").Return(true) + f.EXPECT().Config().Return(&mocks.SimpleConfig{ + AllowNameForID: true, + }) c.On("GetProjects", api.GetProjectsParam{ Workspace: "w", diff --git a/pkg/cmd/task/list/list.go b/pkg/cmd/task/list/list.go index 78c3a76b..1fc0c956 100644 --- a/pkg/cmd/task/list/list.go +++ b/pkg/cmd/task/list/list.go @@ -63,7 +63,7 @@ func NewCmdList( if f.Config().IsAllowNameForID() && p.ProjectID != "" { if p.ProjectID, err = search.GetProjectByName( - c, workspace, p.ProjectID, ""); err != nil { + c, f.Config(), workspace, p.ProjectID, ""); err != nil { return err } } diff --git a/pkg/cmd/task/list/list_test.go b/pkg/cmd/task/list/list_test.go index d1259fef..08376301 100644 --- a/pkg/cmd/task/list/list_test.go +++ b/pkg/cmd/task/list/list_test.go @@ -63,6 +63,7 @@ func TestCmdList(t *testing.T) { f.On("Config").Return(cf) cf.On("IsAllowNameForID").Return(true) + cf.On("IsSearchProjectWithClientsName").Return(false) c := mocks.NewMockClient(t) f.On("Client").Return(c, nil) @@ -111,6 +112,7 @@ func TestCmdList(t *testing.T) { f.On("Config").Return(cf) cf.On("IsAllowNameForID").Return(true) + cf.On("IsSearchProjectWithClientsName").Return(false) c := mocks.NewMockClient(t) f.On("Client").Return(c, nil) diff --git a/pkg/cmd/task/quick-add/quick-add.go b/pkg/cmd/task/quick-add/quick-add.go index c41f5a77..ca75e6e6 100644 --- a/pkg/cmd/task/quick-add/quick-add.go +++ b/pkg/cmd/task/quick-add/quick-add.go @@ -61,7 +61,8 @@ func NewCmdQuickAdd( p, _ := cmd.Flags().GetString("project") if f.Config().IsAllowNameForID() { - if p, err = search.GetProjectByName(c, w, p, ""); err != nil { + if p, err = search.GetProjectByName( + c, f.Config(), w, p, ""); err != nil { return err } } diff --git a/pkg/cmd/task/quick-add/quick-add_test.go b/pkg/cmd/task/quick-add/quick-add_test.go index 9d6c5662..ac9aec43 100644 --- a/pkg/cmd/task/quick-add/quick-add_test.go +++ b/pkg/cmd/task/quick-add/quick-add_test.go @@ -140,9 +140,9 @@ func TestCmdQuickAdd(t *testing.T) { Return("w", nil) f.EXPECT().Client().Return(c, nil) - cf := mocks.NewMockConfig(t) - f.EXPECT().Config().Return(cf) - cf.EXPECT().IsAllowNameForID().Return(true) + f.EXPECT().Config().Return(&mocks.SimpleConfig{ + AllowNameForID: true, + }) c.EXPECT().GetProjects(api.GetProjectsParam{ Workspace: "w", @@ -176,9 +176,9 @@ func TestCmdQuickAdd(t *testing.T) { Return("w", nil) f.EXPECT().Client().Return(c, nil) - cf := mocks.NewMockConfig(t) - f.EXPECT().Config().Return(cf) - cf.EXPECT().IsAllowNameForID().Return(true) + f.EXPECT().Config().Return(&mocks.SimpleConfig{ + AllowNameForID: true, + }) c.EXPECT().GetProjects(api.GetProjectsParam{ Workspace: "w", diff --git a/pkg/cmd/task/util/read-flags.go b/pkg/cmd/task/util/read-flags.go index 7a002e2b..51818f3b 100644 --- a/pkg/cmd/task/util/read-flags.go +++ b/pkg/cmd/task/util/read-flags.go @@ -79,7 +79,7 @@ func TaskReadFlags(cmd *cobra.Command, f cmdutil.Factory) (p FlagsDTO, err error } if p.ProjectID, err = search.GetProjectByName( - c, p.Workspace, p.ProjectID, ""); err != nil { + c, f.Config(), p.Workspace, p.ProjectID, ""); err != nil { return p, err } diff --git a/pkg/cmd/time-entry/in/in_test.go b/pkg/cmd/time-entry/in/in_test.go index cc08298f..2490b52d 100644 --- a/pkg/cmd/time-entry/in/in_test.go +++ b/pkg/cmd/time-entry/in/in_test.go @@ -223,6 +223,31 @@ func TestNewCmdIn_ShouldLookupProject_WithAndWithoutClient(t *testing.T) { "No project with id or name containing 'notonclient' " + "was found for client 'me'"), }, + { + name: "project with client name does not exist", + args: []string{"-s=08:00", "-p", "notonclient me"}, + err: errors.New( + "No project with id or name containing 'notonclient me' " + + "was found"), + }, + { + name: "project and client's name", + args: []string{"-s=08:00", "-p", "second me"}, + param: api.CreateTimeEntryParam{ + Workspace: w.ID, + Start: defaultStart, + ProjectID: projects[1].ID, + }, + }, + { + name: "project and client's name (other)", + args: []string{"-s=08:00", "-p=second clockify"}, + param: api.CreateTimeEntryParam{ + Workspace: w.ID, + Start: defaultStart, + ProjectID: projects[2].ID, + }, + }, } for i := range tts { @@ -235,7 +260,8 @@ func TestNewCmdIn_ShouldLookupProject_WithAndWithoutClient(t *testing.T) { f.EXPECT().GetWorkspaceID().Return(w.ID, nil) f.EXPECT().Config().Return(&mocks.SimpleConfig{ - AllowNameForID: true, + AllowNameForID: true, + SearchProjectWithClientsName: true, }) c := mocks.NewMockClient(t) diff --git a/pkg/cmd/time-entry/report/today/today_test.go b/pkg/cmd/time-entry/report/today/today_test.go index 3d1249a1..aace98f1 100644 --- a/pkg/cmd/time-entry/report/today/today_test.go +++ b/pkg/cmd/time-entry/report/today/today_test.go @@ -86,6 +86,7 @@ func TestCmdToday(t *testing.T) { f := mocks.NewMockFactory(t) f.On("GetUserID").Return("user-id", nil) f.On("GetWorkspaceID").Return("w-id", nil) + f.EXPECT().Config().Return(&mocks.SimpleConfig{}) c := mocks.NewMockClient(t) f.On("Client").Return(c, nil) @@ -114,9 +115,9 @@ func TestCmdToday(t *testing.T) { f.On("GetUserID").Return("user-id", nil) f.On("GetWorkspaceID").Return("w-id", nil) - cf := mocks.NewMockConfig(t) - f.On("Config").Return(cf) - cf.On("IsAllowNameForID").Return(true) + f.EXPECT().Config().Return(&mocks.SimpleConfig{ + AllowNameForID: true, + }) c := mocks.NewMockClient(t) f.On("Client").Return(c, nil) diff --git a/pkg/cmd/time-entry/report/util/report.go b/pkg/cmd/time-entry/report/util/report.go index aaa3f712..7cb233e0 100644 --- a/pkg/cmd/time-entry/report/util/report.go +++ b/pkg/cmd/time-entry/report/util/report.go @@ -112,9 +112,10 @@ func ReportWithRange( return err } + cnf := f.Config() if rf.Project != "" && f.Config().IsAllowNameForID() { if rf.Project, err = search.GetProjectByName( - c, workspace, rf.Project, rf.Client); err != nil { + c, cnf, workspace, rf.Project, rf.Client); err != nil { return err } } @@ -171,7 +172,7 @@ func ReportWithRange( } return util.PrintTimeEntries( - log, out, f.Config(), rf.OutputFlags) + log, out, cnf, rf.OutputFlags) } func filterBilling(l []dto.TimeEntry, billable bool) []dto.TimeEntry { diff --git a/pkg/cmd/time-entry/report/util/reportwithrange_test.go b/pkg/cmd/time-entry/report/util/reportwithrange_test.go index 1a5493c8..0d5680fe 100644 --- a/pkg/cmd/time-entry/report/util/reportwithrange_test.go +++ b/pkg/cmd/time-entry/report/util/reportwithrange_test.go @@ -117,6 +117,7 @@ func TestReportWithRange(t *testing.T) { cf := mocks.NewMockConfig(t) f.On("Config").Return(cf) cf.On("IsAllowNameForID").Return(true) + cf.On("IsSearchProjectWithClientsName").Return(false) c := mocks.NewMockClient(t) f.On("Client").Return(c, nil) @@ -174,6 +175,7 @@ func TestReportWithRange(t *testing.T) { cf := mocks.NewMockConfig(t) f.On("Config").Return(cf) cf.On("IsAllowNameForID").Return(true) + cf.On("IsSearchProjectWithClientsName").Return(false) c := mocks.NewMockClient(t) f.On("Client").Return(c, nil) @@ -210,6 +212,7 @@ func TestReportWithRange(t *testing.T) { cf := mocks.NewMockConfig(t) f.On("Config").Return(cf) cf.On("IsAllowNameForID").Return(true) + cf.On("IsSearchProjectWithClientsName").Return(false) c := mocks.NewMockClient(t) f.On("Client").Return(c, nil) diff --git a/pkg/cmd/time-entry/util/name-for-id.go b/pkg/cmd/time-entry/util/name-for-id.go index 05e0407e..dbef21d8 100644 --- a/pkg/cmd/time-entry/util/name-for-id.go +++ b/pkg/cmd/time-entry/util/name-for-id.go @@ -14,7 +14,7 @@ func GetAllowNameForIDsFn(config cmdutil.Config, c api.Client) Step { } cbs := []Step{ - lookupProject(c), + lookupProject(c, config), lookupTask(c), lookupTags(c), } @@ -26,7 +26,7 @@ func GetAllowNameForIDsFn(config cmdutil.Config, c api.Client) Step { return compose(cbs...) } -func lookupProject(c api.Client) Step { +func lookupProject(c api.Client, cnf cmdutil.Config) Step { return func(te TimeEntryDTO) (TimeEntryDTO, error) { if te.ProjectID == "" { return te, nil @@ -34,7 +34,7 @@ func lookupProject(c api.Client) Step { var err error te.ProjectID, err = search.GetProjectByName( - c, te.Workspace, te.ProjectID, te.Client) + c, cnf, te.Workspace, te.ProjectID, te.Client) return te, err } diff --git a/pkg/cmdutil/config.go b/pkg/cmdutil/config.go index 5ec5689f..309fa143 100644 --- a/pkg/cmdutil/config.go +++ b/pkg/cmdutil/config.go @@ -11,20 +11,21 @@ import ( ) const ( - CONF_WORKWEEK_DAYS = "workweek-days" - CONF_INTERACTIVE = "interactive" - CONF_ALLOW_NAME_FOR_ID = "allow-name-for-id" - CONF_USER_ID = "user.id" - CONF_WORKSPACE = "workspace" - CONF_TOKEN = "token" - CONF_ALLOW_INCOMPLETE = "allow-incomplete" - CONF_SHOW_TASKS = "show-task" - CONF_DESCR_AUTOCOMP = "description-autocomplete" - CONF_DESCR_AUTOCOMP_DAYS = "description-autocomplete-days" - CONF_SHOW_TOTAL_DURATION = "show-total-duration" - CONF_LOG_LEVEL = "log-level" - CONF_ALLOW_ARCHIVED_TAGS = "allow-archived-tags" - CONF_INTERACTIVE_PAGE_SIZE = "interactive-page-size" + CONF_WORKWEEK_DAYS = "workweek-days" + CONF_INTERACTIVE = "interactive" + CONF_ALLOW_NAME_FOR_ID = "allow-name-for-id" + CONF_SEARCH_PROJECTS_WITH_CLIENT_NAME = "search-project-with-client" + CONF_USER_ID = "user.id" + CONF_WORKSPACE = "workspace" + CONF_TOKEN = "token" + CONF_ALLOW_INCOMPLETE = "allow-incomplete" + CONF_SHOW_TASKS = "show-task" + CONF_DESCR_AUTOCOMP = "description-autocomplete" + CONF_DESCR_AUTOCOMP_DAYS = "description-autocomplete-days" + CONF_SHOW_TOTAL_DURATION = "show-total-duration" + CONF_LOG_LEVEL = "log-level" + CONF_ALLOW_ARCHIVED_TAGS = "allow-archived-tags" + CONF_INTERACTIVE_PAGE_SIZE = "interactive-page-size" ) const ( @@ -66,6 +67,9 @@ type Config interface { // InteractivePageSize sets how many items are shown when prompting // projects InteractivePageSize() int + // IsSearchProjectWithClientsName defines if the project name for ID should + // include the client's name + IsSearchProjectWithClientsName() bool // Get retrieves a config by its name Get(string) interface{} @@ -81,6 +85,12 @@ type Config interface { type config struct{} +// IsSearchProjectWithClientsName defines if the project name for ID should +// include the client's name +func (c *config) IsSearchProjectWithClientsName() bool { + return c.GetBool(CONF_SEARCH_PROJECTS_WITH_CLIENT_NAME) +} + func (c *config) InteractivePageSize() int { i := c.GetInt(CONF_INTERACTIVE_PAGE_SIZE) if i <= 0 { diff --git a/pkg/search/project.go b/pkg/search/project.go index cd63fa77..a86f1a0b 100644 --- a/pkg/search/project.go +++ b/pkg/search/project.go @@ -3,12 +3,14 @@ package search import ( "github.com/lucassabreu/clockify-cli/api" "github.com/lucassabreu/clockify-cli/api/dto" + "github.com/lucassabreu/clockify-cli/pkg/cmdutil" "github.com/pkg/errors" "golang.org/x/sync/errgroup" ) func GetProjectByName( c api.Client, + cnf cmdutil.Config, workspace string, project string, client string, @@ -25,10 +27,20 @@ func GetProjectByName( return "", err } + toNamed := func(p dto.Project) named { return p } + if cnf.IsSearchProjectWithClientsName() { + toNamed = func(p dto.Project) named { + return namedStruct{ + ID: p.ID, + Name: p.Name + " " + p.ClientName, + } + } + } + id, err := findByName(project, "project", func() ([]named, error) { ns := make([]named, len(ps)) for i := 0; i < len(ps); i++ { - ns[i] = ps[i] + ns[i] = toNamed(ps[i]) } return ns, nil