From a2c25af4357e414d9213a933185346329cfd5ff5 Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Sun, 12 Sep 2021 21:49:54 +0000 Subject: [PATCH] Actually moving tasks --- asanaclient/client.go | 32 ++--- asanaclient/workspaceclient.go | 63 +++++++-- asanarules/rules.go | 239 ++++++++++++++++++++------------- main.go | 24 ++-- 4 files changed, 234 insertions(+), 124 deletions(-) diff --git a/asanaclient/client.go b/asanaclient/client.go index fe475a7..ece94bd 100644 --- a/asanaclient/client.go +++ b/asanaclient/client.go @@ -40,15 +40,15 @@ func NewClientFromEnv() *Client { } func (c *Client) InWorkspace(name string) (*WorkspaceClient, error) { - wrk, err := c.getWorkspaceByName(name) - if err != nil { - return nil, err - } + wrk, err := c.getWorkspaceByName(name) + if err != nil { + return nil, err + } - return &WorkspaceClient{ - client: c, - workspace: wrk, - }, nil + return &WorkspaceClient{ + client: c, + workspace: wrk, + }, nil } func (c *Client) getWorkspaces() ([]*workspace, error) { @@ -66,13 +66,13 @@ func (c *Client) getWorkspaceByName(name string) (*workspace, error) { return nil, err } - for _, wrk := range wrks { - if wrk.Name == name { - return wrk, nil - } - } + for _, wrk := range wrks { + if wrk.Name == name { + return wrk, nil + } + } - return nil, fmt.Errorf("Workspace `%s` not found", name) + return nil, fmt.Errorf("Workspace `%s` not found", name) } const baseURL = "https://app.asana.com/api/1.0/" @@ -98,8 +98,8 @@ func (c *Client) get(path string, values *url.Values, out interface{}) error { dec := json.NewDecoder(resp.Body) if resp.StatusCode != 200 { - errorResp := &errorResponse{} - err = dec.Decode(errorResp) + errorResp := &errorResponse{} + err = dec.Decode(errorResp) if err != nil { return err } diff --git a/asanaclient/workspaceclient.go b/asanaclient/workspaceclient.go index 9d4546d..4262b21 100644 --- a/asanaclient/workspaceclient.go +++ b/asanaclient/workspaceclient.go @@ -13,14 +13,16 @@ var _FALSE = false var FALSE = &_FALSE type WorkspaceClient struct { - client *Client - workspace *workspace + client *Client + workspace *workspace } type SearchQuery struct { SectionsAny []*Section Completed *bool - DueOn *string + DueOn *civil.Date + DueBefore *civil.Date + DueAfter *civil.Date } type Project struct { @@ -61,11 +63,11 @@ type emptyResponse struct { } type errorDetails struct { - Message string `json:"message"` + Message string `json:"message"` } type errorResponse struct { - Errors []*errorDetails `json:"errors"` + Errors []*errorDetails `json:"errors"` } type projectResponse struct { @@ -135,6 +137,34 @@ func (wc *WorkspaceClient) GetSections(project *Project) ([]*Section, error) { return resp.Data, nil } +func (wc *WorkspaceClient) GetSectionsByName(project *Project) (map[string]*Section, error) { + secs, err := wc.GetSections(project) + if err != nil { + return nil, err + } + + secsByName := map[string]*Section{} + for _, sec := range secs { + secsByName[sec.Name] = sec + } + + return secsByName, err +} + +func (wc *WorkspaceClient) GetSectionByName(project *Project, name string) (*Section, error) { + secsByName, err := wc.GetSectionsByName(project) + if err != nil { + return nil, err + } + + sec, found := secsByName[name] + if !found { + return nil, fmt.Errorf("Section '%s' not found", name) + } + + return sec, nil +} + func (wc *WorkspaceClient) GetTasksFromSection(section *Section) ([]*Task, error) { path := fmt.Sprintf("sections/%s/tasks", section.GID) resp := &tasksResponse{} @@ -157,6 +187,15 @@ func (wc *WorkspaceClient) GetUserTaskList(user *User) (*Project, error) { return resp.Data, nil } +func (wc *WorkspaceClient) GetMyUserTaskList() (*Project, error) { + me, err := wc.GetMe() + if err != nil { + return nil, err + } + + return wc.GetUserTaskList(me) +} + func (wc *WorkspaceClient) Search(q *SearchQuery) ([]*Task, error) { path := fmt.Sprintf("workspaces/%s/tasks/search", wc.workspace.GID) @@ -176,9 +215,17 @@ func (wc *WorkspaceClient) Search(q *SearchQuery) ([]*Task, error) { values.Add("completed", fmt.Sprintf("%t", *q.Completed)) } - if q.DueOn != nil { - values.Add("due_on", *q.DueOn) - } + if q.DueOn != nil { + values.Add("due_on", q.DueOn.String()) + } + + if q.DueBefore != nil { + values.Add("due_on.before", q.DueBefore.String()) + } + + if q.DueAfter != nil { + values.Add("due_on.after", q.DueAfter.String()) + } resp := &tasksResponse{} err := wc.client.get(path, values, resp) diff --git a/asanarules/rules.go b/asanarules/rules.go index ab69e16..f5a80a0 100644 --- a/asanarules/rules.go +++ b/asanarules/rules.go @@ -14,16 +14,16 @@ type periodic struct { duration time.Duration done chan bool - workspaceClientGetter workspaceClientGetter - queryMutators []queryMutator - taskActors []taskActor + workspaceClientGetter workspaceClientGetter + queryMutators []queryMutator + taskActors []taskActor } var periodics = []*periodic{} -func Every(d time.Duration) *periodic { +func EverySeconds(seconds int) *periodic { ret := &periodic{ - duration: d, + duration: time.Duration(seconds) * time.Second, done: make(chan bool), } @@ -44,101 +44,156 @@ func Loop() { } } +func (p *periodic) InWorkspace(name string) *periodic { + if p.workspaceClientGetter != nil { + panic("Multiple calls to InWorkspace()") + } + + p.workspaceClientGetter = func(c *asanaclient.Client) (*asanaclient.WorkspaceClient, error) { + return c.InWorkspace(name) + } + + return p +} + +// Query mutators func (p *periodic) InMyTasksSections(names ...string) *periodic { - p.queryMutators = append(p.queryMutators, func(wc *asanaclient.WorkspaceClient, q *asanaclient.SearchQuery) error { - me, err := wc.GetMe() + p.queryMutators = append(p.queryMutators, func(wc *asanaclient.WorkspaceClient, q *asanaclient.SearchQuery) error { + utl, err := wc.GetMyUserTaskList() if err != nil { - return err + return err } - utl, err := wc.GetUserTaskList(me) + secsByName, err := wc.GetSectionsByName(utl) if err != nil { - return err + return err } - secs, err := wc.GetSections(utl) - if err != nil { - return err + for _, name := range names { + sec, found := secsByName[name] + if !found { + return fmt.Errorf("Section '%s' not found", name) + } + + q.SectionsAny = append(q.SectionsAny, sec) } - secsByName := map[string]*asanaclient.Section{} - for _, sec := range secs { - secsByName[sec.Name] = sec - } + return nil + }) - for _, name := range names { - sec, found := secsByName[name] - if !found { - return fmt.Errorf("Section '%s' not found", name) - } - - q.SectionsAny = append(q.SectionsAny, sec) - } - - return nil - }) - - return p + return p } func (p *periodic) DueInDays(days int) *periodic { - p.queryMutators = append(p.queryMutators, func(wc *asanaclient.WorkspaceClient, q *asanaclient.SearchQuery) error { - d := civil.DateOf(time.Now()) - d = d.AddDays(days) - dueOn := d.String() - q.DueOn = &dueOn - return nil - }) + p.queryMutators = append(p.queryMutators, func(wc *asanaclient.WorkspaceClient, q *asanaclient.SearchQuery) error { + if q.DueOn != nil { + return fmt.Errorf("Multiple clauses set DueOn") + } - return p + d := civil.DateOf(time.Now()) + d = d.AddDays(days) + q.DueOn = &d + return nil + }) + + return p } -func (p *periodic) InWorkspace(name string) *periodic { - p.workspaceClientGetter = func(c *asanaclient.Client) (*asanaclient.WorkspaceClient, error) { - return c.InWorkspace(name) - } +func (p *periodic) DueInAtLeastDays(days int) *periodic { + p.queryMutators = append(p.queryMutators, func(wc *asanaclient.WorkspaceClient, q *asanaclient.SearchQuery) error { + if q.DueAfter != nil { + return fmt.Errorf("Multiple clauses set DueAfter") + } - return p + d := civil.DateOf(time.Now()) + d = d.AddDays(days) + q.DueAfter = &d + return nil + }) + + return p +} + +func (p *periodic) DueInAtMostDays(days int) *periodic { + p.queryMutators = append(p.queryMutators, func(wc *asanaclient.WorkspaceClient, q *asanaclient.SearchQuery) error { + if q.DueBefore != nil { + return fmt.Errorf("Multiple clauses set DueBefore") + } + + d := civil.DateOf(time.Now()) + d = d.AddDays(days) + q.DueBefore = &d + return nil + }) + + return p } func (p *periodic) OnlyIncomplete() *periodic { - p.queryMutators = append(p.queryMutators, func(wc *asanaclient.WorkspaceClient, q *asanaclient.SearchQuery) error { - q.Completed = asanaclient.FALSE - return nil - }) + p.queryMutators = append(p.queryMutators, func(wc *asanaclient.WorkspaceClient, q *asanaclient.SearchQuery) error { + if q.Completed != nil { + return fmt.Errorf("Multiple clauses set Completed") + } - return p + q.Completed = asanaclient.FALSE + return nil + }) + + return p } func (p *periodic) OnlyComplete() *periodic { - p.queryMutators = append(p.queryMutators, func(wc *asanaclient.WorkspaceClient, q *asanaclient.SearchQuery) error { - q.Completed = asanaclient.TRUE - return nil - }) + p.queryMutators = append(p.queryMutators, func(wc *asanaclient.WorkspaceClient, q *asanaclient.SearchQuery) error { + if q.Completed != nil { + return fmt.Errorf("Multiple clauses set Completed") + } - return p + q.Completed = asanaclient.TRUE + return nil + }) + + return p +} + +// Task actors +func (p *periodic) MoveToMyTasksSection(name string) *periodic { + p.taskActors = append(p.taskActors, func(wc *asanaclient.WorkspaceClient, t *asanaclient.Task) error { + utl, err := wc.GetMyUserTaskList() + if err != nil { + return err + } + + sec, err := wc.GetSectionByName(utl, name) + if err != nil { + return err + } + + return wc.AddTaskToSection(t, sec) + }) + + return p } func (p *periodic) PrintTasks() *periodic { - p.taskActors = append(p.taskActors, func(wc *asanaclient.WorkspaceClient, t *asanaclient.Task) error { - fmt.Printf("%s\n", t) - return nil - }) + p.taskActors = append(p.taskActors, func(wc *asanaclient.WorkspaceClient, t *asanaclient.Task) error { + fmt.Printf("%s\n", t) + return nil + }) - return p + return p } func (p *periodic) start(client *asanaclient.Client) { - err := p.validate() - if err != nil { - panic(err) - } + err := p.validate() + if err != nil { + panic(err) + } go p.loop(client) } func (p *periodic) validate() error { - return nil + return nil } func (p *periodic) wait() { @@ -150,44 +205,44 @@ func (p *periodic) loop(client *asanaclient.Client) { for { <-ticker.C - err := p.exec(client) - if err != nil { - fmt.Printf("%s\n", err) - // continue - } + err := p.exec(client) + if err != nil { + fmt.Printf("ERROR: %s\n", err) + // continue + } } close(p.done) } func (p *periodic) exec(c *asanaclient.Client) error { - wc, err := p.workspaceClientGetter(c) - if err != nil { - return err - } + wc, err := p.workspaceClientGetter(c) + if err != nil { + return err + } - q := &asanaclient.SearchQuery{} + q := &asanaclient.SearchQuery{} - for _, mut := range p.queryMutators { - err = mut(wc, q) - if err != nil { - return err - } - } + for _, mut := range p.queryMutators { + err = mut(wc, q) + if err != nil { + return err + } + } - tasks, err := wc.Search(q) - if err != nil { - return err - } + tasks, err := wc.Search(q) + if err != nil { + return err + } - for _, task := range tasks { - for _, act := range p.taskActors { - err = act(wc, task) - if err != nil { - return err - } - } - } + for _, task := range tasks { + for _, act := range p.taskActors { + err = act(wc, task) + if err != nil { + return err + } + } + } - return nil + return nil } diff --git a/main.go b/main.go index 6e23970..3c5114d 100644 --- a/main.go +++ b/main.go @@ -1,16 +1,24 @@ package main -import "time" - import . "github.com/firestuff/asana-rules/asanarules" func main() { - Every(5 * time.Second). - InWorkspace("flamingcow.io"). - InMyTasksSections("Recently Assigned"). - OnlyIncomplete(). - DueInDays(0). - PrintTasks() + EverySeconds(30). + InWorkspace("flamingcow.io"). + InMyTasksSections("Recently Assigned"). + OnlyIncomplete(). + DueInDays(0). + PrintTasks(). + MoveToMyTasksSection("Today") + + EverySeconds(30). + InWorkspace("flamingcow.io"). + InMyTasksSections("Recently Assigned"). + OnlyIncomplete(). + DueInAtLeastDays(1). + DueInAtMostDays(7). + PrintTasks(). + MoveToMyTasksSection("Upcoming") Loop() }