tubone

tubone

Boyaki makes a new world


 Recent posts  6 / 51

SepOctNovDecJanオムロン HV-F312で腰の痛みをなくすGithub Actionを使って、簡単CIを作ってみるChromeDriverがGoogleChrome v76 に対応していないらしい。Googleデータポータルを触ってみるHyper-vにMetasploitableの仮想マシンを立ててみるNightwatch.jsでE2Eテストを回したときにうまく動かないたった一つの理由particles.jsをVue.jsで使ってかっこいいページを作るGitPitchを使ってMarkdownからプレゼンテーションを作ってBadgeをレポジトリに貼るSentryを使ってフロントエンドのエラーを確認するJSON Resume + API With GitHubを使って、さくっと職務経歴書チックなもののAPIなど作ってみるGitHubに30日間草を生やし続けた感想Netlify Formを使って、簡易Contact Formを作ってみる10/1は天下一品の日Ansible + Serverspecを使ってMacの環境構築を自動でする (Ansible編)Ansible + Serverspecを使ってMacの環境構築を自動でする (Serverspec編)今日のラーメン台風の時の我が家のセンサー(netatmo)の値をZabbixで見るGoogle Apps Script(GAS)とAPI FLASHとSlackAPIをClaspとJestとGitHub Actionで調理して定期的にWebページのスクリーンショットを撮るハロウィーンはえちえちでチンパンジーなイベントじゃない。GitHubと向き合うイベントだ昔ながらのラーメンたべたい珠玉の一杯。たくさん残業した日はこのラーメンを食べろ!スープの衝撃!ここまでうまいスープはあるのか!?なラーメンGitHubに草を生やし続け90日が経ったので感想を書くGoでAWS Lambdaを動かして、GitHubAPIv4(GraphQL)を叩いてみた感想Hadoopゾウさんについて本気出して考えてみたNuxt.jsでparticles-bg-vueを使うNuxt.jsのmodulesをCompositionAPIで使ってみる(@nuxtjs/toast編)くろおびらーめん with チャーシュー飯Nuxt.jsのmodulesをCompositionAPIで使ってみる(@nuxtjs/toast Global Option編)Nuxt.js + Composition APIでVuexのStateをReactiveに使う方法【初学者】Juliaを使って円周率を求める初めてプログラミングをした時のゴミソース見つけた面倒なことはPythonにやらせよう@GitHub API v4を使ったリリース実績取得Gatsby.jsで作ったブログに読み終わるまで○○分を追加した話Google広告設定でみる属性情報であなたをもっと知ろう!Blog用に新しいLogo作った話今年1年を振り返ってGoのEchoでJaegerを使ってボトルネックを調査するGatsby.jsで作ったBlogの投稿をGitHubの草にして表示させるWeb Developer Roadmap 2020を眺めながら今年の目標(Frontend)をだらだら考えるの会MonWedFri
Web Developer Roadmap 2020を眺めながら今年の目標(Frontend)をだらだら考えるの会Gatsby.jsで作ったBlogの投稿をGitHubの草にして表示させるGoのEchoでJaegerを使ってボトルネックを調査する今年1年を振り返ってBlog用に新しいLogo作った話Google広告設定でみる属性情報であなたをもっと知ろう!


 SearchBox

Search your interesting by Algolia in this blog.


 All 140 Tags

ぼやき 19JavaScript 12GitHub 7ラーメン 6Vue.js 6TypeScript 6デブ活 5Nuxt.js 4React 3Gatsby.js 3Ansible 3Serverspec 3Mac 3自動テスト 3Python 3Auto Provisioning 3Write Code Every Day 2GitHub Action 2Netlify 2Go 2AWS 2GraphQL 2CompositionAPI 2Chainer 2ChromeDriver 2toast 2昆布 1test 1particle.js 1GitPitch 1GitHub Badges 1Azusa Colors 1GitHubAction 1Sentry 1監視 1 1weed 1CI 1JSON Resume 1GitHub Pages 1Resume 1CV 1Netlify Form 1健康 1Contact Form 1Travis 1天下一品 1腰痛 1Googleデータポータル 1BI 1Google Analytics 1Gatsby 1Azure Devops Build Pipeline 1netatmo 1台風 1IoT 1センシング 1Slack 1Stamp 1Google Apps Script 1API FLASH 1SlackAPI 1Clasp 1E2E Test 1Jest 1Unit Test 1ハロウィーン 1Nim 1docopt 1CLI 1Selenium 1GoogleChrome 1Lambda 1Headless CMS 1仮想化 1Hadoop 1ゾウ 1AWS認定ソリューションアーキテクトプロフェッショナル 1資格 1勉強法 1Hyper-v 1metasploitable 1ひなこのーと 1Deep Learing 1OpenCV 1機械学習 1CNN 1分類学習 1顔認識 1RNN 1LSTM 1Chat BOT 1アイマス 1デレマス 1powerShell 1particles-bg-vue 1particle 1Proton 1particles.js 1チャーシュー飯 1modules 1E2Eテスト 1Nightwatch.js 1Composition API 1Vuex 1ストアパターン 1ギター 1DTM 1エフェクター 1ATELIERZ 1Caparison 1VOCALOID 1Julia 1円周率 1初心者 1The Gauss–Legendre algorithm 1Leibniz formula for π 1かわいい 1恐竜時代 1ことり隊 1Estimated Reading Time 1あと何分 1GitHub API v4 1リリース実績 1早稲田 1Google広告設定 1Google 1属性情報 1Persolal Data 1Logo 1SVG 1振り返り 1Server 1Seti@Home 1Echo 1Jaeger 1ボトルネック調査 1React Calendar Heatmap 1Frontend 1Web Developer Roadmap 2020 1
この記事は454文字2.3で読めます

システムのリリース実績を取る必要がでてきたものの、管理表にはしてなかったのでGitHub API v4から取得し、まとめましたというお話

Table of Contents

背景

どうしてこんなことしなきゃいけなくなったかというとちょっとした頼まれごときっかけです。

ふつうシステムをリリースするとリリース管理というリリース日やリリースの内容などをまとめておく管理表があって然るべきなんですが、それがまとまっていなくて困った!なんとかしてというものでした。

困りましたね…。

ただ、我々のソースはGitHubで管理していて、かならずGit-flowというブランチ戦略を取っているのでPRやコミットの履歴を集めればええやんけ!となったわけです。

Git-flow

上記の通りProduction環境へのリリースはmasterブランチから行います。

GitHub API v4を使う

このブログだと2回目の取り上げになりますが、GitHubのAPIには2種類あります。

GitHub API v3 (RestAPI)v4(GraphQL)です。

もちろん使い慣れたRestAPIを使ってもいいのですが、GraphQLくらい使えないと会社でバカにされそうなのでGraphQLを使うことにします。

なぜGraphQLを使わないと会社でバカにされるのか

ちょっと閑話休題。

最近流行ってますね。GraphQL。

こういった新しい技術は会社の若い人たちがどんどん使っていくので追いつくのに必死です。

APIサービスを提供する人も使う人両者からみてRESTよりはいいところがあるから流行るわけですがどういった点でしょうか?

リソースごとにAPIをコールしなくていい

利用側からするとこれが一番大きいのではないでしょうか?

ちょっと複雑なAPIになるとアルアルなのが、

記事情報APIからコメントIDを取得して、コメントIDを使ってコメントAPIを叩いて、結果からユーザIDをとってユーザーIDを使ってユーザーAPIから……

どんだけリクエストするんや…と。

GraphQLなら自分が必要な項目を必要なだけ取得できます。

BFFとして使うことでたくさんのエンドポイントを作らなくてよい

Backends For Frontendsな世界では、フロントエンドが使いやすい形式で様々な業務ロジックをGatewayしてあげることが求められます。

GraphQLはまさに呼び手、つまりフロントエンドがデータの形式を決められるためにBFFとの親和性が高いと思います。

PythonではRequestsが便利だよねっていう話

タイトルの通り面倒なことはPythonに任せようなのでPythonで実装していくわけですが、PythonにはRequestsという超便利ライブラリがあるのでこちらを使ってGraphQLコール部を作っていきます。

次のような形で作るとGraphQLのリクエストができ、PythonのDictで結果を受け取ることができます。

def post(query):
    headers = {"Authorization": "bearer " + token}
 # tokenはbearer
    res = requests.post(endpoint, json=query, headers=headers)
    return res.json() # res.json()でJSONをPytnon Dict形式に

それでは早速、作っていきましょう。

リリース実績に必要な項目は?

今回のリリース実績取得に当たって下記のものがリリース実績としてカウントするものとします。

  • masterブランチへのPRで

    • マージされてるものが対象
    • マージされずにCloseしているものは対象外
    • マージ日がリリース日とする
    • どんな変更が当たっているか後追いしたいのでPRに含まれるCommitのメッセージも添付する
    • 結果は標準出力とCSVで出す

さて、こちらを実現したソースがこちら

import requests
import json
import csv
import pytz
from datetime import datetime

import os from os.path import join, dirnamefrom dotenv import load_dotenvdotenv_path = join(dirname(__file__), "../.env")load_dotenv(dotenv_path) # dotenv経由で環境変数取得TOKEN = os.environ.get("TOKEN")
ENDPOINT = os.environ.get("ENDPOINT")

# token
token = TOKEN

# endpoint
endpoint = ENDPOINT


# If you want to search repo's in organization `org:hoge` instead of user:hogeget_master_pr = {    "query": """  query {  search(query: "user:tubone24", type: REPOSITORY, first: 100) {      pageInfo {      endCursor      startCursor    }    edges {      node {        ... on Repository {          name          url          pullRequests (first: 100){            edges {            node {              baseRefName              createdAt              closedAt              merged              mergedAt              mergedBy {                login              }              commits (first: 45){                nodes {                  commit {                    message                  }                }              }              title              url              headRefName              }            }            }          }        }    }  }  }  """
}


def post(query):
    headers = {"Authorization": "bearer " + token}
    res = requests.post(endpoint, json=query, headers=headers)
    if res.status_code != 200:
        raise Exception("failed : {}".format(res.status_code))
    return res.json()
def iso_to_jst(iso_str): # ISO8601(RFC3339)形式をJST %Y/%m/%d %H:%M:%Sに変換    dt = None    try:        dt = datetime.strptime(iso_str, "%Y-%m-%dT%H:%M:%SZ")        dt = pytz.utc.localize(dt).astimezone(pytz.timezone("Asia/Tokyo"))    except ValueError:        try:            dt = datetime.strptime(iso_str, "%Y-%m-%dT%H:%M:%Sz")            dt = dt.astimezone(pytz.timezone("Asia/Tokyo"))        except ValueError:            pass    if dt is None:
        return ""
    return dt.strftime("%Y/%m/%d %H:%M:%S")

def create_csv_header():
    with open("master_pr.csv", "w", encoding="utf_8_sig") as f:
 # BOM付UTF-8
        writer = csv.writer(f)
        writer.writerow(
            [
                "Repository",
                "Repository URL",
                "PR#",
                "PR Title",
                "Target Branch",
                "Merged By",
                "Merged at",
                "Created at",
                "PR URL",
                "Commit Msgs",
            ]
        )


def main():
    create_csv_header()
    res = post(get_master_pr)
    print("{}".format(json.dumps(res)))
    for node in res["data"]["search"]["edges"]:
        repo_name = node["node"]["name"]
        repo_url = node["node"]["url"]
        pr_count = 0
        for pr in node["node"]["pullRequests"]["edges"]:
            base_ref_name = pr["node"]["baseRefName"]
            if base_ref_name != "master":
                continue
            head_ref_name = pr["node"]["headRefName"]
            created_at = iso_to_jst(pr["node"]["createdAt"])
            if pr["node"]["merged"]:
                pr_count += 1
                merged_at = iso_to_jst(pr["node"]["mergedAt"])
                merged_by = pr["node"]["mergedBy"]["login"]
                pr_title = pr["node"]["title"]
                pr_url = pr["node"]["url"]
                commit_list = [
                    x["commit"]["message"] for x in pr["node"]["commits"]["nodes"]
                ]
                if pr_count == 1:
                    print("\n")
                    print(
                        "{repo_name}:  {repo_url}".format(
                            repo_name=repo_name, repo_url=repo_url
                        )
                    )
                print(
                    "  #{pr_count} {pr_title} for {head_ref_name} by {merged_by} at {merged_at}".format(
                        pr_count=pr_count,
                        pr_title=pr_title,
                        head_ref_name=head_ref_name,
                        merged_by=merged_by,
                        merged_at=merged_at,
                    )
                )
                print("        {pr_url}".format(pr_url=pr_url))
                with open("master_pr.csv", "a", encoding="utf_8_sig") as f:
                    writer = csv.writer(f)
                    writer.writerow(
                        [
                            repo_name,
                            repo_url,
                            pr_count,
                            pr_title,
                            head_ref_name,
                            merged_by,
                            merged_at,
                            created_at,
                            pr_url,
                            "\n".join(commit_list),
                        ]
                    )


if __name__ == "__main__":
    main()

TOKENとENDPOINTは.envファイルから取得するようにしてます。

また、GitHubのAPIから帰ってくる時刻はISO8601(RFC3339)形式なのでJSTに変換してます。

細かいですが、CSVはUTF-8で作成しますが、Excelで開けるようにBOM付UTF-8にしてます。

print文が残念なかんじですね。今回は急ぎ作ってしまいましたが、今後ちょっとリファクタします。

結論

急ぎの依頼でしたが楽しくプログラミングできました。

Terminalでこんな感じで出ます。

terminal

集計用のCSVもばっちりです。

csv

今回はレポジトリが100件未満だったので特にベージネーションをしてませんが次はベージネーションも実装しようかと思います。

˚