Skip to content

Commit

Permalink
Merge pull request #176 from lucassabreu/feat/174
Browse files Browse the repository at this point in the history
feat: command to mark tasks as done
  • Loading branch information
lucassabreu authored May 3, 2022
2 parents 759e7a2 + 129008c commit 3e0117a
Show file tree
Hide file tree
Showing 10 changed files with 651 additions and 83 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- `task edit` command allows changing a existing task and changing its status.
- `task delete` command allows removing a existing task.
- `task done` command is a helper for `task edit --done`.

### Changed

- `task add` command now accepts `assignees` and `estimate` for task creation
- `end` argument of `report` command accepts the alias `yesterday` for previous date.

## [v0.34.0] - 2022-04-27
Expand Down
179 changes: 173 additions & 6 deletions api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ const (
projectField = field("project id")
timeEntryIDField = field("time entry id")
nameField = field("name")
taskIDField = field("task id")
)

func required(action string, values map[field]string) error {
Expand Down Expand Up @@ -667,11 +668,63 @@ func (c *Client) GetTasks(p GetTasksParam) ([]dto.Task, error) {
return ps, err
}

// AddTaskParam param to add tasks to a project
type AddTaskParam struct {
// GetTaskParam param to get a task on a project
type GetTaskParam struct {
Workspace string
ProjectID string
Name string
TaskID string
}

// GetTasks get tasks of a project
func (c *Client) GetTask(p GetTaskParam) (dto.Task, error) {
var t dto.Task

err := required("get task", map[field]string{
workspaceField: p.Workspace,
projectField: p.ProjectID,
taskIDField: p.TaskID,
})

if err != nil {
return t, err
}

r, err := c.NewRequest(
"GET",
fmt.Sprintf(
"v1/workspaces/%s/projects/%s/tasks/%s",
p.Workspace,
p.ProjectID,
p.TaskID,
),
nil,
)

if err != nil {
return t, err
}

_, err = c.Do(r, &t, "GetTask")
return t, err
}

type TaskStatus string

const (
TaskStatusDefault = ""
TaskStatusDone = "DONE"
TaskStatusActive = "ACTIVE"
)

// AddTaskParam param to add tasks to a project
type AddTaskParam struct {
Workspace string
ProjectID string
Name string
AssigneeIDs *[]string
Estimate *time.Duration
Status TaskStatus
Billable *bool
}

func (c *Client) AddTask(p AddTaskParam) (dto.Task, error) {
Expand All @@ -687,16 +740,30 @@ func (c *Client) AddTask(p AddTaskParam) (dto.Task, error) {
return task, err
}

r := dto.AddTaskRequest{
Name: p.Name,
AssigneeIDs: p.AssigneeIDs,
Billable: p.Billable,
}

if p.Status != TaskStatus("") {
s := string(p.Status)
r.Status = &s
}

if p.Estimate != nil {
e := dto.Duration{Duration: *p.Estimate}
r.Estimate = &e
}

req, err := c.NewRequest(
"POST",
fmt.Sprintf(
"v1/workspaces/%s/projects/%s/tasks",
p.Workspace,
p.ProjectID,
),
dto.AddTaskRequest{
Name: p.Name,
},
r,
)

if err != nil {
Expand All @@ -707,6 +774,106 @@ func (c *Client) AddTask(p AddTaskParam) (dto.Task, error) {
return task, err
}

// UpdateTaskParam param to update tasks to a project
type UpdateTaskParam struct {
Workspace string
ProjectID string
TaskID string
Name string
AssigneeIDs *[]string
Estimate *time.Duration
Status TaskStatus
Billable *bool
}

func (c *Client) UpdateTask(p UpdateTaskParam) (dto.Task, error) {
var task dto.Task

err := required("update task", map[field]string{
nameField: p.Name,
taskIDField: p.TaskID,
workspaceField: p.Workspace,
projectField: p.ProjectID,
})

if err != nil {
return task, err
}

r := dto.UpdateTaskRequest{
Name: p.Name,
AssigneeIDs: p.AssigneeIDs,
Billable: p.Billable,
}

if p.Status != TaskStatus("") {
s := string(p.Status)
r.Status = &s
}

if p.Estimate != nil {
e := dto.Duration{Duration: *p.Estimate}
r.Estimate = &e
}

req, err := c.NewRequest(
"PUT",
fmt.Sprintf(
"v1/workspaces/%s/projects/%s/tasks/%s",
p.Workspace,
p.ProjectID,
p.TaskID,
),
r,
)

if err != nil {
return task, err
}

_, err = c.Do(req, &task, "UpdateTask")
return task, err
}

// DeleteTaskParam param to update tasks to a project
type DeleteTaskParam struct {
Workspace string
ProjectID string
TaskID string
}

func (c *Client) DeleteTask(p DeleteTaskParam) (dto.Task, error) {
var task dto.Task

err := required("delete task", map[field]string{
taskIDField: p.TaskID,
workspaceField: p.Workspace,
projectField: p.ProjectID,
})

if err != nil {
return task, err
}

req, err := c.NewRequest(
"DELETE",
fmt.Sprintf(
"v1/workspaces/%s/projects/%s/tasks/%s",
p.Workspace,
p.ProjectID,
p.TaskID,
),
nil,
)

if err != nil {
return task, err
}

_, err = c.Do(req, &task, "DeleteTask")
return task, err
}

// CreateTimeEntryParam params to create a new time entry
type CreateTimeEntryParam struct {
Workspace string
Expand Down
2 changes: 1 addition & 1 deletion api/dto/dto.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ const TaskStatusDone = TaskStatus("DONE")
// Task DTO
type Task struct {
AssigneeIDs []string `json:"assigneeIds"`
Estimate string `json:"estimate"`
Estimate Duration `json:"estimate"`
ID string `json:"id"`
Name string `json:"name"`
ProjectID string `json:"projectId"`
Expand Down
74 changes: 73 additions & 1 deletion api/dto/request.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package dto

import (
"encoding/json"
"net/url"
"strconv"
"strings"
"time"

"github.com/pkg/errors"
)

// DateTime is a time presentation for parameters
Expand All @@ -21,6 +24,63 @@ func (d DateTime) String() string {
return d.Time.UTC().Format("2006-01-02T15:04:05Z")
}

// Duration is a time presentation for parameters
type Duration struct {
time.Duration
}

// MarshalJSON converts Duration correctly
func (d Duration) MarshalJSON() ([]byte, error) {
return []byte("\"" + d.String() + "\""), nil
}

// UnmarshalJSON converts a JSON value to Duration correctly
func (d *Duration) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return errors.Wrap(err, "unmarshal duration")
}

if len(s) < 4 {
return errors.Errorf("duration %s is invalid", b)
}

var u, dc time.Duration
var j, i int
for ; i < len(s); i++ {
switch s[i] {
case 'P', 'T':
j = i + 1
continue
case 'H':
u = time.Hour
case 'M':
u = time.Minute
case 'S':
u = time.Second
default:
continue
}

v, err := strconv.Atoi(s[j:i])
if err != nil {
return errors.Wrap(err, "unmarshal duration")
}
dc = dc + time.Duration(v)*u
j = i + 1
}

*d = Duration{Duration: dc}
return nil
}

func (d Duration) String() string {
return "PT" +
strconv.Itoa(int(d.Duration.Hours())) + "H" +
strconv.Itoa(int(d.Duration.Minutes())) + "M" +
strconv.Itoa(int(d.Duration.Seconds())) + "S"
}

type pagination struct {
page int
pageSize int
Expand Down Expand Up @@ -333,7 +393,19 @@ func (r GetTasksRequest) AppendToQuery(u url.URL) url.URL {
}

type AddTaskRequest struct {
Name string `json:"name"`
Name string `json:"name"`
AssigneeIDs *[]string `json:"assigneeIds,omitempty"`
Billable *bool `json:"billable,omitempty"`
Estimate *Duration `json:"estimate,omitempty"`
Status *string `json:"status,omitempty"`
}

type UpdateTaskRequest struct {
Name string `json:"name"`
AssigneeIDs *[]string `json:"assigneeIds,omitempty"`
Billable *bool `json:"billable,omitempty"`
Estimate *Duration `json:"estimate,omitempty"`
Status *string `json:"status,omitempty"`
}

type ChangeTimeEntriesInvoicedRequest struct {
Expand Down
Loading

0 comments on commit 3e0117a

Please sign in to comment.