A Faster Task Organization with ZenHub & Ruboty & peekabow

26173c2f52b5aa5d06e6806c7a9478d3?s=47 laprasDrum
February 10, 2018

A Faster Task Organization with ZenHub & Ruboty & peekabow

26173c2f52b5aa5d06e6806c7a9478d3?s=128

laprasDrum

February 10, 2018
Tweet

Transcript

  1. Ruboty と 自作 Golang ツールで 朝会のタスク整理を時間短縮してる話 laprasDrum

  2. 誰 @laprasDrum(らぷらぷ) 一児のパパ iOS/Android エンジニア たまに Rails と Go freee

    K.K. 生まれ育ちは福岡でした
  3. None
  4. None
  5. None
  6. !

  7. チームの話

  8. 現在5名で4アプリ プレイングマネージャー見習い

  9. プレイングマネージャーになってみて 各アプリのコードを見る時間が減った タスクの仕分け作業からはじめてみる 単純作業とはいえ時間がかかる 大事なんだが退屈

  10. これは人じゃなくてもよいのでは?

  11. • チームの Eng/UX Designer/PM/Biz が Issue 作成 • ZenHub でカンバン管理

    • Issue 作成直後は必ず1番左の Pipeline に入る • 自分のチームでは Inbox と名付けている • 毎日の朝会で 4 Repo の Inbox Issue を共有 • Issue を開いて情報見て文章整形 • #123 設定画面の項目を分かりやすくしたい https://github.com/Org/repo/issues/123 現状を整理してみよう
  12. • チームの Eng/UX Designer/PM/Biz が Issue 作成 • ZenHub でカンバン管理

    • Issue 作成直後は必ず1番左の Pipeline に入る • 自分のチームでは Inbox と名付けている • 毎日の朝会で 4 Repo の Inbox Issue を共有 • Issue を開いて情報見て文章整形 • #123 設定画面の項目を分かりやすくしたい https://github.com/Org/repo/issues/123 現状を整理してみよう トータルで約5分掛かる
  13. 自動化するぞ

  14. 文章整形後は チーム用の Ruboty に投稿を任せたい

  15. 文章整形後は チーム用の Ruboty に投稿を任せたい 朝会の司会を教えてくれたり

  16. 文章整形後は iOS アプリの申請状況を教えてくれる チーム用の Ruboty に投稿を任せたい

  17. ここから設計の話 &

  18. やることざっくり Org の Repo って名前の Inbox Issue が知りたい 1. Org/Repo

    の Repo ID 取得 2. Repo ID から Inbox Pipeline の Issue ID を取得 3. Issue ID からタイトルとリンクを取得 & 文章整形
  19. laprasdrum/peekabow peek a boo(いないいないばぁ)と peek (from) a bow (坊の覗き見)

  20. 順を追って説明 peekabow —owner Org —repo Repo —pipeline Inbox 1. Org/Repo

    の Repo ID 取得 2. Repo ID から Inbox Pipeline の Issue ID を取得 3. Issue ID からタイトルとリンクを取得 & 文章整形
  21. 解説ざっくり 1. Org/Repo の Repo ID 取得 var findRepoId struct

    { Repository struct { DatabaseId int } `graphql:"repository(owner: $owner, name: $repo)"` } log(c, " Search repository ID from GitHub...") src := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: config.Token.GitHub}) httpClient := oauth2.NewClient(context.Background(), src) github = githubql.NewClient(httpClient) // owner ͱ repo ͸ CLI Ҿ਺Ͱड͚औ͍ͬͯΔ args := map[string]interface{}{ "owner": githubql.String(owner), "repo": githubql.String(repo), } query := findRepoId if err := github.Query(context.Background(), &query, args); err != nil { panic(err) } log(c, " Found repository ID: "+strconv.Itoa(query.Repository.DatabaseId))
  22. // ZenHub Types type ( Pipelines []Pipeline Board struct {

    Pipelines Pipelines } Pipeline struct { Name string Issues []Issue } Issue struct { IssueNumber int `json:"issue_number"` } ) func (self Pipelines) Find(f func(Pipeline) bool) (Pipeline, bool) { for _, pipeline := range self { if f(pipeline) { return pipeline, true } } return Pipeline{}, false } 解説ざっくり 2. Repo ID から Inbox Pipeline の Issue ID を取得
  23. log(c, " Search pipeline issues from ZenHub...") zenHub := &http.Client{}

    req, _ := http.NewRequest( "GET", "https://api.zenhub.io/p1/repositories/"+strconv.Itoa(query.Repository.DatabaseId)+"/board", nil, ) req.Header.Add("X-Authentication-Token", config.Token.ZenHub) resp, _ := zenHub.Do(req) defer resp.Body.Close() body, _ := ioutil.ReadAll(resp.Body) board := new(Board) if err := json.Unmarshal(body, board); err != nil { panic(err) } p, found := board.Pipelines.Find(func(p Pipeline) bool { return p.Name == pipelineName }) 解説ざっくり 2. Repo ID から Inbox Pipeline の Issue ID を取得
  24. 解説ざっくり 3. Issue ID からタイトルとリンクを取得 & 文章整形 // pipeline ໊͸

    CLI Ҿ਺Ͱड͚औ͍ͬͯΔ if !found { fmt.Println("❌ Not Found: " + pipelineName) fmt.Println("❌ Please check whether if your ZenHub pipeline's name is correct.") return } else { fmt.Println(" " + owner + "/" + repo + ": " + pipelineName + "'s issues here:") c := toNumber(p.Issues) // Issue ൪߸ͷ഑ྻग़ྗ༻ channel ʹม׵ out := message(c) // ੔ܗ͞Εͨจষͷग़ྗ༻ channel ʹม׵ var buffer bytes.Buffer for msg := range out { buffer.WriteString(msg + "\n") } if summary := strings.TrimSuffix(buffer.String(), "\n"); summary != "" { fmt.Println(summary) } else { fmt.Println(" No Issue") } }
  25. 解説ざっくり 3. Issue ID からタイトルとリンクを取得 & 文章整形 // GitHub Queries

    type IssueFragment struct { Title string Url string } var findIssue struct { Repository struct { IssueOrPullRequest struct { IssueFragment `graphql:"... on Issue"` } `graphql:"issueOrPullRequest(number: $number)"` } `graphql:"repository(owner: $owner, name: $repo)"` } // Issue ൪߸ͷ഑ྻग़ྗ༻ channel ʹม׵ func toNumber(issues []Issue) <-chan int { out := make(chan int) go func() { for _, issue := range issues { out <- issue.IssueNumber } close(out) }() return out }
  26. // ੔ܗ͞Εͨจষͷग़ྗ༻ channel ʹม׵ func message(in <-chan int) <-chan string

    { out := make(chan string) go func() { for num := range in { args := map[string]interface{}{ "owner": githubql.String(owner), "repo": githubql.String(repo), "number": githubql.Int(num), } query := findIssue err := github.Query(context.Background(), &query, args) if err != nil { panic(err) } if issue := query.Repository.IssueOrPullRequest. IssueFragment; issue.Title != "" { out <- "#" + strconv.Itoa(num) + ": " + issue.Title + " : " + issue.Url } } close(out) }() return out } 解説ざっくり 3. Issue ID からタイトルとリンクを取得 & 文章整形
  27. あとは Bow の出番

  28. None
  29. 5分 → 5秒

  30. None
  31. 気づき 朝会前に私の余裕が生まれた オーナー不在・議論無しな施策はほとんどなくなった 退屈で属人的なことを仕組みに落とし込む価値

  32. Thank you