From 80e48ae01c9f43d285f165f803727c7986b3f194 Mon Sep 17 00:00:00 2001 From: Jonathan Schmitt Date: Thu, 27 Jun 2024 17:14:54 -0300 Subject: [PATCH] feat: Add TimeTracking service to ClickUp client --- clickup/client.go | 2 + clickup/spaces.go | 22 +++---- clickup/time_tracking.go | 104 ++++++++++++++++++++++++++++++++++ clickup/time_tracking_test.go | 43 ++++++++++++++ 4 files changed, 160 insertions(+), 11 deletions(-) create mode 100644 clickup/time_tracking.go create mode 100644 clickup/time_tracking_test.go diff --git a/clickup/client.go b/clickup/client.go index 82c05b2..6e3dc0c 100644 --- a/clickup/client.go +++ b/clickup/client.go @@ -55,6 +55,7 @@ type Client struct { Tasks *TasksService TaskTemplates *TaskTemplatesService Teams *TeamsService + TimeTrackings *TimeTrackingsService SharedHierarchy *SharedHierarchyService Spaces *SpacesService Folders *FoldersService @@ -138,6 +139,7 @@ func NewClient(httpClient *http.Client, APIKey string) *Client { c.Tasks = (*TasksService)(&c.common) c.TaskTemplates = (*TaskTemplatesService)(&c.common) c.Teams = (*TeamsService)(&c.common) + c.TimeTrackings = (*TimeTrackingsService)(&c.common) c.SharedHierarchy = (*SharedHierarchyService)(&c.common) c.Spaces = (*SpacesService)(&c.common) c.Folders = (*FoldersService)(&c.common) diff --git a/clickup/spaces.go b/clickup/spaces.go index 983c823..fc8df44 100644 --- a/clickup/spaces.go +++ b/clickup/spaces.go @@ -25,7 +25,7 @@ type DueDates struct { RemapClosedDueDate bool `json:"remap_closed_due_date"` } -type TimeTracking struct { +type TimeTrackingFeature struct { Enabled bool `json:"enabled"` } @@ -60,15 +60,15 @@ type Portfolios struct { } type Features struct { - DueDates DueDates `json:"due_dates"` - TimeTracking TimeTracking `json:"time_tracking"` - Tags Tags `json:"tags"` - TimeEstimates TimeEstimates `json:"time_estimates"` - Checklists Checklists `json:"checklists"` - CustomFields CustomFields `json:"custom_fields"` - RemapDependencies RemapDependencies `json:"remap_dependencies"` - DependencyWarning DependencyWarning `json:"dependency_warning"` - Portfolios Portfolios `json:"portfolios"` + DueDates DueDates `json:"due_dates"` + TimeTracking TimeTrackingFeature `json:"time_tracking"` + Tags Tags `json:"tags"` + TimeEstimates TimeEstimates `json:"time_estimates"` + Checklists Checklists `json:"checklists"` + CustomFields CustomFields `json:"custom_fields"` + RemapDependencies RemapDependencies `json:"remap_dependencies"` + DependencyWarning DependencyWarning `json:"dependency_warning"` + Portfolios Portfolios `json:"portfolios"` } type Space struct { @@ -88,7 +88,7 @@ type Space struct { Sprints struct { Enabled bool `json:"enabled"` } `json:"sprints"` - TimeTracking TimeTracking `json:"time_tracking"` + TimeTracking TimeTrackingFeature `json:"time_tracking"` Points struct { Enabled bool `json:"enabled"` } `json:"points"` diff --git a/clickup/time_tracking.go b/clickup/time_tracking.go new file mode 100644 index 0000000..9a1a794 --- /dev/null +++ b/clickup/time_tracking.go @@ -0,0 +1,104 @@ +package clickup + +import ( + "context" + "fmt" +) + +type TimeTrackingsService service + +type GetTimeTrackingResponse struct { + Data []TimeTracking `json:"data"` +} + +// See https://clickup.com/api/clickupreference/operation/Createatimeentry/ +type TimeTrackingRequest struct { + Description string `json:"description,omitempty"` + Tags []TimeTrackingTag `json:"tags,omitempty"` + Start int64 `json:"start"` + End int64 `json:"end,omitempty"` + Stop int64 `json:"stop,omitempty"` + Billable bool `json:"billable,omitempty"` + Duration int32 `json:"duration"` + Assignee int `json:"assignee,omitempty"` + Tid string `json:"tid,omitempty"` +} + +type TimeTrackingTag struct { + Name string `json:"name"` + TagBg string `json:"tag_bg"` + TagFg string `json:"tag_fg"` + Creator int `json:"creator"` +} + +type TimeTracking struct { + ID string `json:"id"` + Wid string `json:"wid"` + User User `json:"user"` + Billable bool `json:"billable"` + Start string `json:"start"` + End string `json:"end"` + Duration string `json:"duration"` + Description string `json:"description"` + Source string `json:"source"` + At string `json:"at"` + TaskLocation TaskLocation `json:"task_location"` + Task []TimeTrackingTag `json:"task_"` + TaskURL string `json:"task_url"` +} + +type TaskLocation struct { + ListID int `json:"list_id"` + FolderID int `json:"folder_id"` + SpaceID int `json:"space_id"` + ListName string `json:"list_name"` + FolderName string `json:"folder_name"` + SpaceName string `json:"space_name"` +} + +type CreateTimeTrackingOptions struct { + CustomTaskIDs bool `url:"custom_task_ids,omitempty"` + TeamID int `url:"team_id,omitempty"` +} + +func (s *TimeTrackingsService) CreateTimeTracking(ctx context.Context, teamID string, opts *CreateTimeTrackingOptions, ttr *TimeTrackingRequest) (*TimeTracking, *Response, error) { + u := fmt.Sprintf("team/%s/time_entries", teamID) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("POST", u, ttr) + if err != nil { + return nil, nil, err + } + + timeTracking := new(TimeTracking) + resp, err := s.client.Do(ctx, req, timeTracking) + if err != nil { + return nil, resp, err + } + + return timeTracking, resp, nil +} + +func (s *TimeTrackingsService) GetTimeTracking(ctx context.Context, teamID string, timerID string, opts *CreateTimeTrackingOptions, ttr *TimeTrackingRequest) (*GetTimeTrackingResponse, *Response, error) { + u := fmt.Sprintf("team/%s/time_entries/%s", teamID, timerID) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, ttr) + if err != nil { + return nil, nil, err + } + + getTimeTrackingResponse := new(GetTimeTrackingResponse) + resp, err := s.client.Do(ctx, req, getTimeTrackingResponse) + if err != nil { + return nil, resp, err + } + + return getTimeTrackingResponse, resp, nil +} diff --git a/clickup/time_tracking_test.go b/clickup/time_tracking_test.go new file mode 100644 index 0000000..eb82e93 --- /dev/null +++ b/clickup/time_tracking_test.go @@ -0,0 +1,43 @@ +package clickup + +import ( + "context" + "fmt" + "path/filepath" + "runtime" + "testing" + "time" +) + +func TestTimeTrackingService_CreateTimeTracking(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + teamID := "teamID" + taskID := "taskID" + assigneeID := 1234 + var dur int32 = 120000 + + start := time.Now().Add(time.Millisecond * time.Duration(-dur)) + + _, _, err := client.TimeTrackings.CreateTimeTracking(context.Background(), teamID, + &CreateTimeTrackingOptions{}, + &TimeTrackingRequest{ + Description: "description", + Start: start.Unix() * 1000, + Duration: dur, + Assignee: assigneeID, + Tid: taskID, + Billable: true, + }) + + OK(t, err) +} + +func OK(tb testing.TB, err error) { + if err != nil { + _, file, line, _ := runtime.Caller(1) + fmt.Printf("\033[31m%s:%d: unexpected error: %s\033[39m\n\n", filepath.Base(file), line, err.Error()) + tb.FailNow() + } +}