From 910d636b92320d79073ee849d45f3dd6fbda2acf Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Sun, 12 Sep 2021 23:33:25 +0000 Subject: [PATCH] WithTagsAnyOf() and WithoutTagsAnyOf() --- asanaclient/workspaceclient.go | 51 ++++++++++++++++++++++++++ asanarules/rules.go | 67 ++++++++++++++++++++++++++++++---- main.go | 12 +++++- 3 files changed, 122 insertions(+), 8 deletions(-) diff --git a/asanaclient/workspaceclient.go b/asanaclient/workspaceclient.go index 74f9c67..68d779c 100644 --- a/asanaclient/workspaceclient.go +++ b/asanaclient/workspaceclient.go @@ -24,6 +24,8 @@ type SearchQuery struct { DueOn *civil.Date DueBefore *civil.Date DueAfter *civil.Date + TagsAny []*Tag + TagsNot []*Tag } type Project struct { @@ -36,6 +38,11 @@ type Section struct { Name string `json:"name"` } +type Tag struct { + GID string `json:"gid"` + Name string `json:"name"` +} + type Task struct { GID string `json:"gid"` Name string `json:"name"` @@ -83,6 +90,10 @@ type sectionsResponse struct { Data []*Section `json:"data"` } +type tagsResponse struct { + Data []*Tag `json:"data"` +} + type tasksResponse struct { Data []*Task `json:"data"` } @@ -166,6 +177,30 @@ func (wc *WorkspaceClient) GetSectionByName(project *Project, name string) (*Sec return sec, nil } +func (wc *WorkspaceClient) GetTags() ([]*Tag, error) { + path := fmt.Sprintf("workspaces/%s/tags", wc.workspace.GID) + resp := &tagsResponse{} + err := wc.client.get(path, nil, resp) + if err != nil { + return nil, err + } + return resp.Data, nil +} + +func (wc *WorkspaceClient) GetTagsByName() (map[string]*Tag, error) { + tags, err := wc.GetTags() + if err != nil { + return nil, err + } + + tagsByName := map[string]*Tag{} + for _, tag := range tags { + tagsByName[tag.Name] = tag + } + + return tagsByName, err +} + func (wc *WorkspaceClient) GetTasksFromSection(section *Section) ([]*Task, error) { path := fmt.Sprintf("sections/%s/tasks", section.GID) resp := &tasksResponse{} @@ -236,6 +271,22 @@ func (wc *WorkspaceClient) Search(q *SearchQuery) ([]*Task, error) { values.Add("due_on.after", q.DueAfter.String()) } + if len(q.TagsAny) > 0 { + gids := []string{} + for _, sec := range q.TagsAny { + gids = append(gids, sec.GID) + } + values.Add("tags.any", strings.Join(gids, ",")) + } + + if len(q.TagsNot) > 0 { + gids := []string{} + for _, sec := range q.TagsNot { + gids = append(gids, sec.GID) + } + values.Add("tags.not", strings.Join(gids, ",")) + } + resp := &tasksResponse{} err := wc.client.get(path, values, resp) if err != nil { diff --git a/asanarules/rules.go b/asanarules/rules.go index 66e2bac..b57d157 100644 --- a/asanarules/rules.go +++ b/asanarules/rules.go @@ -1,6 +1,7 @@ package asanarules import "fmt" +import "math/rand" import "time" import "cloud.google.com/go/civil" @@ -12,8 +13,8 @@ type taskFilter func(*asanaclient.WorkspaceClient, *asanaclient.Task) (bool, err type workspaceClientGetter func(*asanaclient.Client) (*asanaclient.WorkspaceClient, error) type periodic struct { - duration time.Duration - done chan bool + period int + done chan bool workspaceClientGetter workspaceClientGetter queryMutators []queryMutator @@ -25,8 +26,8 @@ var periodics = []*periodic{} func EverySeconds(seconds int) *periodic { ret := &periodic{ - duration: time.Duration(seconds) * time.Second, - done: make(chan bool), + period: seconds, + done: make(chan bool), } periodics = append(periodics, ret) @@ -157,6 +158,59 @@ func (p *periodic) OnlyComplete() *periodic { return p } +func (p *periodic) WithTagsAnyOf(names ...string) *periodic { + p.queryMutators = append(p.queryMutators, func(wc *asanaclient.WorkspaceClient, q *asanaclient.SearchQuery) error { + if len(q.TagsAny) > 0 { + return fmt.Errorf("Multiple clauses set TagsAny") + } + + tagsByName, err := wc.GetTagsByName() + if err != nil { + return err + } + + for _, name := range names { + tag, found := tagsByName[name] + if !found { + return fmt.Errorf("Tag '%s' not found", name) + } + + q.TagsAny = append(q.TagsAny, tag) + } + + return nil + }) + + return p +} + +func (p *periodic) WithoutTagsAnyOf(names ...string) *periodic { + p.queryMutators = append(p.queryMutators, func(wc *asanaclient.WorkspaceClient, q *asanaclient.SearchQuery) error { + if len(q.TagsNot) > 0 { + return fmt.Errorf("Multiple clauses set TagsNot") + } + + tagsByName, err := wc.GetTagsByName() + if err != nil { + return err + } + + for _, name := range names { + tag, found := tagsByName[name] + if !found { + return fmt.Errorf("Tag '%s' not found", name) + } + + q.TagsNot = append(q.TagsNot, tag) + } + + return nil + }) + + return p +} + +// Task filters func (p *periodic) WithoutDue() *periodic { // We can't mutate the query because due_on=null is buggy in the Asana API p.taskFilters = append(p.taskFilters, func(wc *asanaclient.WorkspaceClient, t *asanaclient.Task) (bool, error) { @@ -212,10 +266,9 @@ func (p *periodic) wait() { } func (p *periodic) loop(client *asanaclient.Client) { - ticker := time.NewTicker(p.duration) - for { - <-ticker.C + time.Sleep(time.Duration(rand.Intn(p.period)) * time.Second) + err := p.exec(client) if err != nil { fmt.Printf("ERROR: %s\n", err) diff --git a/main.go b/main.go index f7a47a2..6314957 100644 --- a/main.go +++ b/main.go @@ -5,12 +5,22 @@ import . "github.com/firestuff/asana-rules/asanarules" func main() { EverySeconds(30). InWorkspace("flamingcow.io"). - InMyTasksSections("Recently Assigned", "Upcoming", "Later", "Someday"). + InMyTasksSections("Recently Assigned", "Tonight", "Upcoming", "Later", "Someday"). OnlyIncomplete(). DueInDays(0). + WithoutTagsAnyOf("section=Tonight"). PrintTasks(). MoveToMyTasksSection("Today") + EverySeconds(30). + InWorkspace("flamingcow.io"). + InMyTasksSections("Recently Assigned", "Today", "Meetings", "Maybe Today", "Upcoming", "Later", "Someday"). + OnlyIncomplete(). + DueInDays(0). + WithTagsAnyOf("section=Tonight"). + PrintTasks(). + MoveToMyTasksSection("Tonight") + EverySeconds(30). InWorkspace("flamingcow.io"). InMyTasksSections("Recently Assigned", "Today", "Meetings", "Maybe Today", "Tonight", "Later", "Someday").