🏋️‍♀️

GitHubだけでPJ管理を頑張ってみる兄貴 Part1 〜GithubでWBS作ったら、リマインドまで自動化できた話〜

2024/11/19に公開

はじめに

初めまして。株式会社フェズで開発のディレクションを担当している増田といいます。

入社して半年、小売様のデータ基盤や販促支援の開発管理を担当しています。

同僚からは何故か兄貴と呼ばれている末っ子です。

想定読者

  • PJ管理者になりたての人
  • PJ管理したいけど、現状ツールがない人(RedMine、Jiraなど)

なんでこんなことをやり始めたか

フェズに入社して、開発管理をする中で

  • As Is
    • GitHubでIssue管理をやっている。
      • ただ、Issueはあるが、スケジュール管理(特に締切)が管理できていない。
  • To Be
    • DeadLine管理をちゃんとやりたい。
    • Issue制でガントチャート・WBS管理やりたい。
    • そうした方が、所属するグループの開発管理はスムーズ。

ただ、ツールがない!

という壁に当たりました。どうしよう・・・。

だったら作ってやんよ!

というわけで、GitHubだけでPJ管理を頑張ってみる兄貴の奮闘記です。

DeadLineカスタムフィールド作成&SlackへDL通知

SIや、プロダクトの個別カスタマイズにおいて、DeadLine(以下DL)は重要な概念です。

DeadLineまでに、該当タスクが終わっていないと・・・。

  • 後続のタスクスケジュールが壊れてしまう。
  • 納品できない。ローンチできない。

等、ビジネスにとってクリティカルなことが起きます。

詳しくはこちら

https://www.amazon.co.jp/デッドライン-トム-デマルコ/dp/4822280535

今まではGitHubとは連動していない別のポータルサイト上でプロジェクトの進捗を管理していましたが、GitHubと非連動なこともあり二重管理になってしまっており、厳密なDL管理もできていない状態でした。

そこで、

  1. GitHub Issueにカスタムフィールドを追加し、
  2. GitHub Aciton+PythonでIssue抽出
  3. Slackでアラートする方式で管理しようと画策しました。

GitHub Issueにカスタムフィールドを追加

  • こんな感じでDLというカスタムフィールドを作りました。

  • 作り方

    • GitHub PJのTable ViewでAdd Fieldする。
    • データ型はdateを選択。

GitHub Aciton+PythonでIssue抽出

続いては、GitHub Actionの準備です。

今回は毎朝9時に明日DLを迎えるIssueを抽出してみるActionを組んでみました。

(GitHub APIで簡単にいけるだろ・・・と思っていた。当時は。)

GitHub APIを使って、Issueを抜いてみた。

と言うわけで当初のコード
Python側

import os
import requests
from datetime import datetime, timedelta

# GitHub APIの設定
GITHUB_API_URL = "https://api.github.com"
REPO = os.getenv("GITHUB_REPOSITORY") 
TOKEN = os.getenv("GITHUB_TOKEN")

# 日本時間に合わせた日付の設定
JST = timedelta(hours=9)
today = datetime.utcnow() + JST
tomorrow = (today + timedelta(days=1)).date()

# GitHub APIでIssuesを取得
response = requests.get(
    f"{GITHUB_API_URL}/repos/{REPO}/issues",
    headers={"Authorization": f"token {TOKEN}"}
)

issues = response.json()

# DeadLineが明日の日付のIssueをフィルタリング
deadline_issues = []
for issue in issues:
    print(issue)
    # カスタムフィールドのデッドラインを取得
    deadline_str = ""  # APIに応じてフィールドを変更
    if deadline_str:
        deadline_date = datetime.strptime(deadline_str, "%Y-%m-%d").date()
        if deadline_date == tomorrow:
            deadline_issues.append(issue)

# 結果を出力
if deadline_issues:
    issue_list = "\n".join([f"- {issue['title']} ({issue['html_url']})" for issue in deadline_issues])
    message = f"以下のIssueの締め切りが明日です:\n{issue_list}"
    print(f"::set-output name=has_issues::true")
else:
    message = "明日の締め切りのIssueはありません。"
    print(f"::set-output name=has_issues::true")

GitHubActionのyaml側

name: Notify DeadLine Issues

on:
  schedule:
    # 日本時間午前9時に実行(UTCで0時)
    - cron: '0 0 * * *'
  workflow_dispatch: # 手動トリガーのための設定
    # 何もしない

jobs:
  notify:
    runs-on: ubuntu-latest

    steps:
      - name: Check out the repository
        uses: actions/checkout@v3

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.12'

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install requests pyyaml

      - name: Run Deadline Checker
        id: deadline_checker  # ステップにIDを設定
        env:
          DEVELOPER_GITHUB_TOKEN: ${{ secrets.DEVELOPER_GITHUB_TOKEN }}        
        run: python .github/workflows/scripts/check_deadlines.py

      - name: Send Notification to Slack
        uses: rtCamp/action-slack-notify@v2
        env:
          SLACK_CHANNEL: ${{ secrets.SLACK_DL_ALERT_CHANNEL }}
          SLACK_TITLE: "GitHub Issue DLアラート"
          SLACK_MESSAGE: ${{ steps.deadline_checker.outputs.message_alert }}
          SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}

さてさて。うまく動くかな・・・。
動かない。

デバッグした結果、GitHub APIではカスタムフィールドの抽出ができないことが判明しました。
orz
いや、ドキュメント読んでない自分が悪いんだけどね。

どうしたものか・・・。
ちょっと調査をしてみたら、GitHub GraphQL APIなら抽出できることが判明。
早速試してみましょう。

import urllib.parse
import os
import requests
from datetime import datetime, timedelta

# GitHub APIの設定
GITHUB_API_URL = "https://api.github.com/graphql"
REPO = "kamet"
TOKEN = os.getenv("DEVELOPER_GITHUB_TOKEN")
OWNER = os.getenv("GITHUB_REPOSITORY_OWNER")

# GraphQLクエリ
query = """
query ($owner: String!, $repo: String!) {
  repository(owner: $owner, name: $repo) {
    issues(first: 100, states: OPEN, orderBy: { field: CREATED_AT, direction: DESC }) {
      edges {
        node {
          title
          url
          projectItems(first: 10) {
            nodes {
              fieldValueByName(name: "DL") {
                ... on ProjectV2ItemFieldDateValue {
                  date
                }
              }
            }
          }
        }
      }
    }
  }
}
"""

def log_message(message):
    print(f"::debug::{message}")  # GitHub Actionsのログに出力

# GraphQLリクエストを実行する関数
def run_query(query, variables):
    headers = {"Authorization": f"Bearer {TOKEN}"}
    response = requests.post(GITHUB_API_URL, json={'query': query, 'variables': variables}, headers=headers)
    if response.status_code == 200:
        result = response.json()
        if result is None: 
            raise Exception("result")
        if 'data' not in result:
            raise Exception("result")
        if 'repository' not in result['data']:
            raise Exception("repo")
        
        return response.json()
    else:
        raise Exception(f"Query failed with status code {response.status_code}: {response.text}")



# GitHubのIssuesを取得
variables = {"owner": OWNER, "repo": REPO}
result = run_query(query, variables)
log_message("OWNER:" + OWNER)
log_message("REPO:" + REPO)
log_message("result:" + str(result))


# 今日の日付を取得
today = datetime.today().date()

# +1日
tomorrow = today + timedelta(days=1)


deadline_issues = []

# 締め切りが今日のIssueをSlackに送信
for issue_edge in result['data']['repository']['issues']['edges']:
    issue = issue_edge['node']
    for project_item in issue['projectItems']['nodes']:
        deadline_field = project_item['fieldValueByName']
        if deadline_field and deadline_field.get('date'):
            deadline = datetime.strptime(deadline_field['date'], "%Y-%m-%d").date()
            if deadline == tomorrow:
                deadline_issues.append(f"{issue['title']}")
log_message("deadline_issues:" + str(deadline_issues))
if deadline_issues:
    issue_list = "\n".join(deadline_issues)
    log_message("issue_list:" + str(issue_list))
    message = f"@group_technology_business_promotion 以下のIssueの締め切りが明日です:\n" + str(issue_list)
    log_message("issue_list:" + message)
else:
    message = "明日の締め切りのIssueはありません。"

with open(os.getenv('GITHUB_OUTPUT'), 'a') as gh_output:
    print(f"message_alert<<EOF\n{message}\nEOF\n", file=gh_output)
                

これでどうや!

できた!よかった・・・。
と言うわけで、毎日明日のDL Issueを確認できるようになりました。

終わりに

PJ管理をする中で、ツールがない!と言うのはいろんな現場でありました。
その中でエンジニアならではの解決ができたのは、今回が初めてで嬉しい限りです。
それではまた!

(次回はPJ管理自体のことを書こうかなあ)

フェズ開発ブログ

Discussion