こちらは「コドモンAdvent Calendar 2023」の16日目の記事です。
こんにちは! プロダクト開発部エンジニアの宮島です!
そろそろクリスマスが近づいてきましたね! 私は家族でクリスマスプレゼントを毎年渡し合っているのですが、サンタさん(パートナー)からクリスマスプレゼントとして何をもらおうか考え中です。 最近はランニングにハマっているので冬用のランニングウェアか、お高い技術書でももらおうかなと思っています。
先日、デプロイフローを改善するための一環としてChrome拡張ツールを開発しました。 Git差分を絞り込み表示できるツールになりますが、本記事では作ろうと思った背景、改善ポイント、実装内容について紹介したいと思います。
拡張ツールを作ろうと思った背景
コドモンでは、技術負債の解消やサービスの拡張性を広げるために、現行のシステムのマイクロサービス化を進めています。切り出した各マイクロサービスは、すべて単一のモノレポ※として管理しています。
※モノレポとは、複数のアプリケーションやマイクロサービスのコードを単一のリポジトリで管理するパターンのこと。
構成は下記の形となっています。
root-project ├── services ├── microServiceA ├── microServiceB ├── microServiceC
モノレポにすることで、チーム間でのコードやツールの標準化が簡単にできますし、コード全体を一目で把握することができます。 メリットとして大きい点はたくさんありますが、一方でデメリットもあります。
それは「デプロイ対象となるマイクロサービスが、前回からのデプロイ差分をGithub上で把握しづらい」という点です。
モノレポのメリットを享受するため、どのマイクロサービスでも本番デプロイ対象ブランチはmainブランチとし、最新のコード全体が一目でmainブランチにて把握できるようにしています。 そのため、リリースタグを使ったバージョン管理は行っておらず、各マイクロサービスは特定のコミットハッシュを指定することでデプロイ範囲を決める仕組みとしています。
しかし、この形をとることで
「前回リリースからの差分を見たいときに、Githubのcomparing commitsを使えばブラウザ上で差分表示はできる」
けど、
「リポジトリ内にある差分すべてが表示されるため、別のマイクロサービスで行われたcommitの差分も表示されてしまう」
という状態になります。
デプロイのワークフロー実行時に、「この差分でリリースして問題ないよね」の最終確認がGithub上ですぐにできると嬉しいので、理想のワークフローとしては
1. デプロイしたいコミットハッシュをパラメータとして、ワークフローを開始
2. コミット差分を表示するリンクを出力
3. デプロイレビューがpendingになる
4. コミット差分を表示するリンクに飛んで、差分を確認する
5. 承認をする
という形を想定しています。
この理想のワークフローを実現する方法がないかと、Github DocsのComparing commitsを確認しましたが、現時点ではComparing commitsによって表示された差分ファイルを絞り込み表示することはできませんでした。
そのような経緯から、自分でChrome拡張ツールを作ることでこの課題を解決ができるのでは? と考えました。
こうやって開発を進めました
チュートリアルを見る
まずはスタートガイドを参考に、どのようにGoogle Extensionツールを作成できるのかを確認しました。 developer.chrome.com
チュートリアルのステップに沿って行えばサクッと簡単に作り方がわかるのでおすすめです。
ほしい機能を洗い出す
次に、ツールとしてどのような機能を持たせたいかを洗い出しました。
- ディレクトリやファイル名の部分一致検索で差分を確認したいファイルを絞り込める
- フィルタした後、別の文字列で再検索を行うことができる
- 拡張ツールを開いた際、前回のフィルタ条件が残っている
- フィルタ後のファイル数がGithub上でわかる
上記を踏まえて作成したツールのソースコードを一部紹介します。
ソースコードの紹介
構成は以下の通りです。
git-diff-filter ├── scripts │ ├── initial.js # 拡張ツールアイコンクリック時の処理 │ ├── filter.js # フィルタボタン押下時の処理 ├── public │ ├── popup.html # 拡張ツールアイコンクリック時に表示されるhtml └── manifest.json # 拡張機能を定義するためのjsonファイル
manifest.jsonとは、拡張機能を定義するためのjsonファイルのことです。
manifest.json
{ "manifest_version": 3, "name": "git diff filter", "version": "1.0", "description": "filter git diff by specific directories or files", "action": { "default_popup": "public/popup.html" }, "permissions": [ "activeTab", "scripting", "storage" ] }
actionのdefault_popup
により、拡張ツールアイコンクリック時に表示されるhtmlを指定できます。
また、permissionsにより、この拡張ツールの権限を指定できます。アクティブなタブで開いているGithubのページに対してスクリプト実行を行い、かつフィルタ時の文字列を保持するために、 activeTab
、scripting
、storage
を権限許可しています。
popup.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>git diff filter extension</title> <style> ...省略 </style> </head> <body id="body"> <div id="container"> <div>フィルタ対象ディレクトリorファイル名</div> <form id="textInputForm"> <input type="text" id="textInput" placeholder="Enter text"> <button type="submit" id="runScriptButton">フィルタ</button> </form> </div> <script src="../scripts/initial.js"></script> <script src="../scripts/filter.js"></script> </body> </html>
popup.html
は、拡張ツールアイコンクリック時に表示されるhtmlです。
UIは文字入力フォームと、フィルタボタンのみとシンプルにしています。
実際の見た目は下記画像のような形となります。
filter.js
// ファイルのフィルタ処理 const filterFiles = (inputText) => { ...省略 // 入力文字により表示するファイルをフィルターする const displayFilesOperator = createDisplayFilesOperator(); displayFilesOperator.filterDisplayFiles(inputText); // 変更差分ファイル数の表示を変更 const filesChangedTab = createFilesChangedTab(); filesChangedTab.changeTextContent(displayFilesOperator.visibleFilesLength()); // 読み込みがされてないファイルをロード createFilesLoader().load(); // ページの一番上までスクロール window.scrollTo({ top: 0, behavior: 'smooth' }); } // フィルタボタン押下時のイベント document.getElementById("textInputForm").addEventListener("submit", (event) => { event.preventDefault(); const inputText = document.getElementById("textInput").value; // 入力値を chrome.storage に保存 chrome.storage.sync.set({ inputText: inputText }, () => { console.log("Input values saved"); }); // アクティブタブのページに対してフィルタ処理を実行 chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { chrome.scripting.executeScript({ target: { tabId: tabs[0].id }, args: [inputText], function: filterFiles, }); }); });
フィルタボタンクリック時の処理をfilter.js
に実装しています。
工夫した点は、「フィルタ処理後、ファイルの差分が省略されないようにしている」ことです。 例えば、差分ファイルが数百ある場合、すべてのファイルのコード差分表示がされず、「Load Diff」と表示されることがあります。 OSSであるopenapi-generatorで、特定コミット差分表示を例に紹介します。
下記リンクで、特定コミットとの差分が確認できますが、
一部の差分ファイルは「Load diff」と表示されてしまいます。
そのため、フィルタするだけだと、「Load diff」を押さないとそれぞれのファイル差分が確認できない、、ということが起こりえます。
// 読み込みがされてないファイルをロード createFilesLoader().load(); // ページの一番上までスクロール window.scrollTo({ top: 0, behavior: 'smooth' });
その問題に対処するために、フィルタ後にLoadがされていないファイルのロード処理も行うようにしています。 また、ロード処理を行う際にLoad対象のファイルを表示するところまでページの下スクロールが走るため、ロード完了後にページ一番上までスクロールするようにしています。
デモ
ソースコードだけだと、実際にどのような挙動になるのかイメージが湧きづらいと思います。 openapi-generatorでの特定のコミット差分を使って、どのような挙動をするのかをお見せします!
(クリックすると拡大できます)
samples/client/echo_api/python/docs/
配下の差分のみを表示するようフィルタをかけています。
690ファイル差分があったのが、17ファイルに絞られています!
また、フィルタ後のファイルはすべて差分が表示されるようになっており、かつ画面上までスクロールするようになっています。
まとめ
今回、Chrome拡張ツールを開発することで、デプロイフローの改善に取り組みました。 今はまだ、拡張ツールを実装したのみで、ローカル環境でしか動かせていない状態です。 まずはストア公開した上で、実際にワークフローで利用してどのような使い勝手になるか、検証してみたいと思います!
普段業務を行う中で大小さまざまな課題がありますが、このような形でちょっとずつでも、課題解決のためにできることを積み上げていきたいなと思います。
最後までお読みいただき、ありがとうございました!