Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

誰 @laprasDrum(らぷらぷ) 一児のパパ iOS/Android エンジニア たまに Rails と Go freee K.K. 生まれ育ちは福岡でした

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

!

Slide 7

Slide 7 text

チームの話

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

• チームの 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分掛かる

Slide 13

Slide 13 text

自動化するぞ

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

ここから設計の話 &

Slide 18

Slide 18 text

やることざっくり Org の Repo って名前の Inbox Issue が知りたい 1. Org/Repo の Repo ID 取得 2. Repo ID から Inbox Pipeline の Issue ID を取得 3. Issue ID からタイトルとリンクを取得 & 文章整形

Slide 19

Slide 19 text

laprasdrum/peekabow peek a boo(いないいないばぁ)と peek (from) a bow (坊の覗き見)

Slide 20

Slide 20 text

順を追って説明 peekabow —owner Org —repo Repo —pipeline Inbox 1. Org/Repo の Repo ID 取得 2. Repo ID から Inbox Pipeline の Issue ID を取得 3. Issue ID からタイトルとリンクを取得 & 文章整形

Slide 21

Slide 21 text

解説ざっくり 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))

Slide 22

Slide 22 text

// 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 を取得

Slide 23

Slide 23 text

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 を取得

Slide 24

Slide 24 text

解説ざっくり 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") } }

Slide 25

Slide 25 text

解説ざっくり 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 }

Slide 26

Slide 26 text

// ੔ܗ͞Εͨจষͷग़ྗ༻ 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 からタイトルとリンクを取得 & 文章整形

Slide 27

Slide 27 text

あとは Bow の出番

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

5分 → 5秒

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

Thank you