Skip to content

Commit 76f03d3

Browse files
authored
feat: add command split (#268)
1 parent 9e4a989 commit 76f03d3

File tree

6 files changed

+583
-14
lines changed

6 files changed

+583
-14
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
## [Unreleased]
1313

14+
### Added
15+
16+
- new command `split` to allow break a time entry into others with break points
17+
1418
## [v0.51.1] - 2024-05-30
1519

1620
### Fixed

pkg/cmd/time-entry/edit/edit.go

+1-5
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,7 @@ func NewCmdEdit(
149149
return err
150150
}
151151

152-
if report != nil {
153-
return report(tei, cmd.OutOrStdout(), of)
154-
}
155-
156-
return util.PrintTimeEntryImpl(tei, f, cmd.OutOrStdout(), of)
152+
return report(tei, cmd.OutOrStdout(), of)
157153
},
158154
}
159155

pkg/cmd/time-entry/in/in.go

+2-7
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,8 @@ func NewCmdIn(
136136
return err
137137
}
138138

139-
if report != nil {
140-
return report(
141-
util.TimeEntryDTOToImpl(tei), cmd.OutOrStdout(), of)
142-
}
143-
144-
return util.PrintTimeEntryImpl(
145-
util.TimeEntryDTOToImpl(tei), f, cmd.OutOrStdout(), of)
139+
return report(
140+
util.TimeEntryDTOToImpl(tei), cmd.OutOrStdout(), of)
146141
},
147142
}
148143

pkg/cmd/time-entry/split/split.go

+206
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
package split
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"io"
7+
"time"
8+
9+
"github.com/MakeNowJust/heredoc"
10+
"github.com/lucassabreu/clockify-cli/api"
11+
"github.com/lucassabreu/clockify-cli/api/dto"
12+
"github.com/lucassabreu/clockify-cli/pkg/cmd/time-entry/util"
13+
"github.com/lucassabreu/clockify-cli/pkg/cmdcompl"
14+
"github.com/lucassabreu/clockify-cli/pkg/cmdutil"
15+
"github.com/lucassabreu/clockify-cli/pkg/timeentryhlp"
16+
"github.com/lucassabreu/clockify-cli/pkg/timehlp"
17+
"github.com/spf13/cobra"
18+
"golang.org/x/sync/errgroup"
19+
)
20+
21+
func NewCmdSplit(
22+
f cmdutil.Factory,
23+
report func([]dto.TimeEntry, io.Writer, util.OutputFlags) error,
24+
) *cobra.Command {
25+
of := util.OutputFlags{TimeFormat: timehlp.OnlyTimeFormat}
26+
va := cmdcompl.ValidArgsSlide{
27+
timeentryhlp.AliasCurrent,
28+
timeentryhlp.AliasLast,
29+
timeentryhlp.AliasLatest,
30+
}
31+
32+
cmd := &cobra.Command{
33+
Use: "split { <time-entry-id> | " + va.IntoUseOptions() + " | ^n } " +
34+
" <time>...",
35+
Args: cobra.MatchAll(
36+
cmdutil.RequiredNamedArgs("time entry id"),
37+
cobra.MinimumNArgs(2),
38+
),
39+
ValidArgs: va.IntoValidArgs(),
40+
Short: `Splits a time entry into multiple time entries`,
41+
Long: heredoc.Docf(`
42+
Split a time entry.
43+
The time arguments can be more than one, but must be increasing.
44+
45+
%s
46+
%s
47+
%s
48+
%s
49+
%s
50+
`,
51+
util.HelpTimeEntriesAliasForEdit,
52+
util.HelpInteractiveByDefault,
53+
util.HelpDateTimeFormats,
54+
util.HelpNamesForIds,
55+
util.HelpMoreInfoAboutPrinting,
56+
),
57+
Example: heredoc.Docf(`
58+
# starting a time entry
59+
$ %[1]s in --project cli --tag dev -d "Doing work before lunch" --task "edit" --md
60+
ID: %[2]s62ae4b304ebb4f143c931d50%[2]s
61+
Billable: %[2]syes%[2]s
62+
Locked: %[2]sno%[2]s
63+
Project: Clockify Cli (%[2]s621948458cb9606d934ebb1c%[2]s)
64+
Task: Edit Command (%[2]s62ae4af04ebb4f143c931d2e%[2]s)
65+
Interval: %[2]s2022-06-18 11:01:16%[2]s until %[2]snow%[2]s
66+
Description:
67+
> Adding docs to edit
68+
69+
Tags:
70+
* Development (%[2]s62ae28b72518aa18da2acb49%[2]s)
71+
72+
# splits the time entry at lunch and now
73+
$ %[1]s split 12:00 13:30 --format '{{.ID}},{{.TimeInterval.Start|ft}}'
74+
62ae4b304ebb4f143c931d50,11:01
75+
3c931d502ae4b3064ebb4f14,12:00
76+
ebb4f143c962ae4b30431d50,13:30
77+
`, "clockify-cli", "`"),
78+
RunE: func(cmd *cobra.Command, args []string) error {
79+
if err := of.Check(); err != nil {
80+
return err
81+
}
82+
83+
splits := make([]time.Time, len(args)-1)
84+
for i := range splits {
85+
t, err := timehlp.ConvertToTime(args[1+i])
86+
if err != nil {
87+
return fmt.Errorf(
88+
"argument %d could not be converted to time: %w",
89+
i+2, err)
90+
}
91+
92+
if i > 0 && t.Before(splits[i-1]) {
93+
return errors.New("splits must be in increasing order")
94+
}
95+
96+
splits[i] = t
97+
}
98+
99+
c, err := f.Client()
100+
if err != nil {
101+
return err
102+
}
103+
104+
userID, err := f.GetUserID()
105+
if err != nil {
106+
return err
107+
}
108+
109+
w, err := f.GetWorkspaceID()
110+
if err != nil {
111+
return err
112+
}
113+
114+
te, err := timeentryhlp.GetTimeEntry(
115+
c,
116+
w,
117+
userID,
118+
args[0],
119+
)
120+
if err != nil {
121+
return err
122+
}
123+
124+
if te.TimeInterval.Start.After(splits[0]) {
125+
return errors.New("time splits must be after " +
126+
te.TimeInterval.Start.Format(timehlp.FullTimeFormat))
127+
}
128+
129+
if te.TimeInterval.End != nil && te.TimeInterval.End.Before(
130+
splits[len(splits)-1]) {
131+
return errors.New("time splits must be before " +
132+
te.TimeInterval.End.Format(timehlp.FullTimeFormat))
133+
}
134+
135+
if _, err = c.UpdateTimeEntry(api.UpdateTimeEntryParam{
136+
Workspace: te.WorkspaceID,
137+
TimeEntryID: te.ID,
138+
Description: te.Description,
139+
Start: te.TimeInterval.Start,
140+
End: &splits[0],
141+
Billable: te.Billable,
142+
ProjectID: te.ProjectID,
143+
TaskID: te.TaskID,
144+
TagIDs: te.TagIDs,
145+
}); err != nil {
146+
return err
147+
}
148+
149+
tes := make([]dto.TimeEntry, len(splits)+1)
150+
getHydrated := func(i int, id string) error {
151+
t, err := c.GetHydratedTimeEntry(api.GetTimeEntryParam{
152+
TimeEntryID: id,
153+
Workspace: w,
154+
})
155+
156+
if err != nil {
157+
return err
158+
}
159+
tes[i] = *t
160+
return nil
161+
}
162+
163+
eg := errgroup.Group{}
164+
eg.Go(func() error { return getHydrated(0, te.ID) })
165+
166+
for i := range splits {
167+
i := i
168+
eg.Go(func() error {
169+
end := te.TimeInterval.End
170+
if i < len(splits)-1 {
171+
end = &splits[i+1]
172+
}
173+
174+
te, err := c.CreateTimeEntry(api.CreateTimeEntryParam{
175+
Workspace: te.WorkspaceID,
176+
Billable: &te.Billable,
177+
Start: splits[i],
178+
End: end,
179+
ProjectID: te.ProjectID,
180+
Description: te.Description,
181+
TagIDs: te.TagIDs,
182+
TaskID: te.TaskID,
183+
})
184+
185+
if err != nil {
186+
return err
187+
}
188+
189+
return getHydrated(i+1, te.ID)
190+
})
191+
192+
}
193+
194+
if err := eg.Wait(); err != nil {
195+
return err
196+
}
197+
198+
return report(tes, cmd.OutOrStdout(), of)
199+
},
200+
}
201+
202+
util.AddPrintTimeEntriesFlags(cmd, &of)
203+
util.AddPrintMultipleTimeEntriesFlags(cmd)
204+
205+
return cmd
206+
}

0 commit comments

Comments
 (0)