Photosynth の Redmine おじさん @ishturnk です
これはAkerun Advent Calendar12日目の記事です。
進捗どうですか?
耳が痛いどの組織でも課題になる話題
- スケジュールの可視化
- 進捗の更新
について運用してみたメソッドを紹介します
ワークフロー
- 計画
まずはスケジュールを立てます。
- 作業→進捗ステータスの更新
仕様作成や実装、評価など、実際の作業を実施します。終わったものには完了のフラグをつけていきます。
- リリース
開発と評価が終わったのでリリースします。
- 計画の見直し
ほとんどのケースでは計画通りにはいきません。遅延や前倒しをなど、スケジュールをアップデートします。
計画の可視化と進捗更新ツールに Redmine のEasyGantt をつかってみた
エンジニアは僕は面倒な作業が嫌いなので、
- ひと目でわかる
- 更新がしやすい
ツールでなければ、運用が回らなくなります。
Redmineにプラグインを導入
こちらのプラグインを導入します。
Redmine Gantt プラグイン - Easy Redmine
使用感については公式の動画をご覧ください。
一部の機能を除き、無料で使えます。(弊社は課金しました)
特徴として、
- ドラッグ操作でスケジュールの変更(移動や期間)が可能
- 進捗(%)もドラッグ操作で更新可能
- 後続チケットをバインドできる(テストは実装の後、など)
実際にチケットにあてはめるとこのようになります。
見やすくてなかなか良いです。
それでも滞る更新
それでも更新が滞ってしまい、期日を過ぎてしまっているチケットがしばしば。
Redmine おじさんの出番です。
期日が過ぎているチケットをメンション付きで slack 通知する
メールで通知する方法はよく見るのですが、弊社はslackでコミュニケーションをしており、メールだと埋もれがち。 なのでslack通知する方法を実装しました。
// User : user の型 type User struct { ID int `json:"id"` Name string `json:"name"` Login string `json:"login"` } // Issue : issueの型 type Issue struct { ID int `json:"id"` Project struct { ID int `json:"id"` } `json:"project"` Progress int `json:"done_ratio"` Due string `json:"due_date"` Subject string `json:"subject"` Assign User `json:"assigned_to"` Status struct { ID int `json:"id"` } `json:"status"` } // Resp : レスポンス type Resp struct { Issues []Issue `json:"issues"` User User `json:"user"` } func post(message string) { type Message struct { Username string `json:"username"` Icon string `json:"icon_url"` Text string `json:"text"` Linkname int `json:"link_names"` Channel string `json:"channel"` } mes := Message{ Username: "Redmineおじさん", Icon: "http://www.redmine.org/attachments/3462/redmine_fluid_icon.png", Text: message, Linkname: 1, Channel: "#投稿チャンネル", } url := "https://hooks.slack.com/services/XXXXXXXXXXXXXXX" // webhook url payload, _ := json.Marshal(&mes) req, err := http.NewRequest( "POST", url, bytes.NewBuffer([]byte(payload)), ) if err != nil { return } // Content-Type 設定 req.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, err := client.Do(req) if err != nil { return } defer resp.Body.Close() } func issues() ([]Issue, error) { values := url.Values{} values.Add("key", {Redmineのアクセストークン}) values.Add("tracker_id", {指定するトラッカー}) values.Add("limit", "100") res, err := http.Get("https://{RedmineのURL}/issues.json?" + values.Encode()) if err != nil { log.Fatal(err) } robots, err := ioutil.ReadAll(res.Body) res.Body.Close() if err != nil { log.Fatal(err) } var r Resp err = json.Unmarshal(robots, &r) if err != nil { log.Fatal(err) } fmt.Println(len(r.Issues)) return r.Issues, err } func user(id int) (User, error) { values := url.Values{} values.Add("key", {Redmineのアクセストークン}) res, err := http.Get("https://redmine.akerun.com/users/" + strconv.Itoa(id) + ".json?" + values.Encode()) if err != nil { log.Fatal(err) } robots, err := ioutil.ReadAll(res.Body) res.Body.Close() if err != nil { log.Fatal(err) } var r Resp err = json.Unmarshal(robots, &r) if err != nil { log.Fatal(err) } return r.User, err } func main() { issues, err := issues() if err != nil { log.Fatal(err) } fmt.Println(issues) now := time.Now() str := "" for _, s := range issues { if s.Project.ID != 4 { // sandbox continue } if s.Status.ID == 3 { // pending continue } if s.Progress < 100 { d, err := time.Parse("2006-01-02", s.Due) if err != nil { log.Fatal(err) } if now.After(d) { assign, err := user(s.Assign.ID) mention := assign.Login if err != nil { continue } str += fmt.Sprintf("@%s\n %s | %3d%% | %s %s%d\n", mention, s.Due, s.Progress, s.Subject, "https://{RedmineのURL}/issues/", s.ID) } } } if str != "" { str = "Redmineおじさんです。チケット更新してね\n" + str post(str) } }
やっているのは
- Redmine のAPIでチケット一覧を取得
- チケットのプロジェクトを指定(フィルタ)
- チケットのステータスを指定(フィルタ)
- 進捗が100%になってないもの &$ 期日が過ぎているチケットを抽出
- チケットのアサインユーザーにメンション通知
という流れです。
Redmine のユーザーとslackユーザーの対応ですが、両者を同じにするという運用をしています。メンションで連携しやすくなるので、slack連携している組織ではおすすめの運用です。
おわり
株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 hrmos.co
Akerun Proの購入はこちらから akerun.com