[インデックス 10835] ファイルの概要
このコミットは、Goプロジェクトのビルドダッシュボードにユーザーインターフェースを導入するものです。これまでのバックエンド機能に加えて、ビルドステータスを視覚的に表示するためのWebページが追加されました。具体的には、Go App Engine上で動作するGo言語製のWebアプリケーションとして、コミット情報、ビルド結果、および他のパッケージのビルド状態を表示する機能が実装されています。
コミット
commit 80103cd54fa1a6ae0cd75a8c545a365bf31f58cf
Author: Andrew Gerrand <adg@golang.org>
Date: Fri Dec 16 10:48:06 2011 +1100
misc/dashboard: user interface
R=rsc
CC=golang-dev
https://golang.org/cl/5461047
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/80103cd54fa1a6ae0cd75a8c545a365bf31f58cf
元コミット内容
misc/dashboard: user interface
R=rsc
CC=golang-dev
https://golang.org/cl/5461047
変更の背景
このコミット以前のGoビルドダッシュボードは、主にビルド結果を記録・提供するバックエンドAPIとして機能していました。しかし、ユーザーが現在のビルドステータスを直感的に把握するためには、視覚的なインターフェースが不可欠でした。この変更の背景には、開発者やコミュニティメンバーがGoプロジェクトの継続的インテグレーション(CI)の状態を容易に監視できるように、使いやすいWebベースのダッシュボードを提供する必要性がありました。これにより、どのコミットがどの環境で成功し、どの環境で失敗したかを一目で確認できるようになり、問題の早期発見と解決に貢献します。
前提知識の解説
- Go App Engine: Google App Engineは、Googleのインフラストラクチャ上でWebアプリケーションやモバイルバックエンドを構築・ホストするためのPaaS(Platform as a Service)です。Go言語はApp Engineでサポートされており、スケーラブルなアプリケーションを容易にデプロイできます。このダッシュボードもApp Engine上で動作するように設計されています。
- Google Cloud Datastore: Datastoreは、App Engineアプリケーションが利用するNoSQLドキュメントデータベースです。このコミットでは、
Commit
、Package
、Result
、Tag
などのビルド関連データがDatastoreに保存され、管理されています。Datastoreはスキーマレスであり、柔軟なデータモデルをサポートします。 html/template
パッケージ: Go言語の標準ライブラリに含まれるhtml/template
パッケージは、HTML出力を安全に生成するためのテンプレートエンジンです。クロスサイトスクリプティング(XSS)攻撃を防ぐための自動エスケープ機能が組み込まれており、Webアプリケーションのセキュリティを向上させます。このコミットでは、ui.html
ファイルがこのテンプレートエンジンによってレンダリングされます。- 継続的インテグレーション (CI): ソフトウェア開発手法の一つで、開発者がコードの変更を頻繁にメインブランチにマージし、自動化されたビルドとテストを実行することで、統合の問題を早期に発見します。Goビルドダッシュボードは、GoプロジェクトのCIプロセスの一部として、ビルド結果を可視化する役割を担っています。
- ビルドボット/ビルダー: 特定の環境(OS、アーキテクチャなど)でコードをビルドし、テストを実行する自動化されたシステムまたはプロセスを指します。ダッシュボードでは、
linux-amd64
やwindows-386
などの様々なビルダーからの結果が表示されます。
技術的詳細
このコミットは、Go App Engineアプリケーションのフロントエンド部分を大幅に拡張しています。
-
ルーティングと静的ファイルの提供:
app.yaml
が更新され、/static
パスがstatic
ディレクトリにマッピングされ、status_alert.gif
やstatus_good.gif
といった静的画像ファイルが提供されるようになりました。- ルートパス
/
へのリクエストが新しいUIハンドラにルーティングされるようになり、ダッシュボードのWebページがアプリケーションのトップページとして機能します。
-
データモデルの改善とクエリの最適化 (
build/build.go
):Commit
構造体のResult
フィールドがResultData
にリネームされ、[]string
型でビルド結果の生データを保持するようになりました。これは、Datastoreに非正規化された形で結果を保存し、クエリの効率を高めるための設計変更です。Commit
にResult(builder, goHash string)
およびResults(goHash string)
メソッドが追加され、特定のビルダーやGoハッシュに対応するビルド結果を効率的に取得できるようになりました。これにより、UI側で必要なデータを柔軟にフィルタリング・表示することが可能になります。Package
構造体にLastCommit()
メソッドが追加され、各パッケージの最新コミットをDatastoreから取得できるようになりました。Tag
構造体のValid()
メソッドの論理エラーが修正され、GetTag()
関数が追加されました。todoHandler
やpackagesHandler
などの既存のハンドラも、新しいデータアクセスロジックに合わせて更新されています。
-
UIロジックの実装 (
build/ui.go
):uiHandler
関数がHTTPリクエストを処理し、ダッシュボードのメインページをレンダリングします。goCommits
関数は、DatastoreからGoリポジトリの最新コミットをページネーション付きで取得します。これにより、大量のコミットがあっても効率的に表示できます。commitBuilders
関数は、表示対象のコミットに含まれるユニークなビルダー(ビルド環境)のリストを動的に生成します。これにより、ダッシュボードは利用可能なすべてのビルダーの列を自動的に表示できます。TagState
関数は、"tip"などの特定のタグにおける他のGoパッケージのビルド状態(成功/失敗)を取得し、UIに表示するためのPackageState
構造体のスライスを生成します。html/template
パッケージを利用して、ui.html
テンプレートにデータを渡し、最終的なHTMLを生成します。builderTitle
、shortHash
、repoURL
といったカスタムテンプレート関数が定義され、表示されるデータの整形やリンク生成に利用されます。
-
フロントエンドテンプレート (
build/ui.html
):- HTML5と基本的なCSSで構成されたシンプルなレイアウトです。
- Goのテンプレート構文(
{{if}}
,{{range}}
,{{with}}
)を駆使して、動的にコンテンツを生成します。 - Goリポジトリのコミット一覧と、他のパッケージのビルド状態の2つの主要なセクションがあります。
- コミット一覧では、コミットハッシュ、各ビルダーでのビルド結果(成功の場合は"ok"、失敗の場合は"fail"とログへのリンク)、コミット者、日時、説明が表示されます。
- ページネーションリンクが実装されており、過去のコミットを閲覧できます。
- 他のパッケージのセクションでは、パッケージ名と、"tip"タグでのビルド状態(成功/失敗を示す画像と詳細リンク)が表示されます。
-
テストの改善 (
build/test.go
):tCommit
ヘルパー関数が追加され、テスト用のCommit
オブジェクトをより簡単に、かつ一貫性のあるタイムスタンプで生成できるようになりました。これにより、テストコードの可読性と保守性が向上します。- 新しいテストケースが追加され、UIで表示される可能性のある様々なビルド結果シナリオ(特に失敗したビルドとログへのリンク)がカバーされています。
全体として、このコミットはGoビルドダッシュボードを、単なるデータストアから、開発者がGoプロジェクトの健全性をリアルタイムで監視できるインタラクティブなWebアプリケーションへと進化させました。
コアとなるコードの変更箇所
misc/dashboard/app/app.yaml
:/static
ハンドラの追加: 静的ファイル(画像など)を提供するための設定。/
ハンドラの変更: ルートパスをGoアプリケーションにルーティングし、UIを表示。
misc/dashboard/app/build/build.go
:Commit
構造体のResult
フィールドをResultData []string
にリネーム。Commit
にLastCommit()
,Result()
,Results()
,partsToHash()
メソッドを追加。Tag
にGetTag()
関数を追加し、Valid()
メソッドの論理エラーを修正。todoHandler
やpackagesHandler
などの既存ハンドラで、新しいデータアクセスロジックを使用するように変更。
misc/dashboard/app/build/test.go
:tCommit
ヘルパー関数を追加し、テストデータ生成を改善。- 既存のテストリクエストを
tCommit
を使用するように更新。
misc/dashboard/app/build/ui.go
(新規ファイル):uiHandler
関数: ダッシュボードのメインUIロジック。goCommits
関数: Goコミットのページネーション付き取得。commitBuilders
関数: ビルダーリストの動的生成。TagState
関数: 他のパッケージのビルド状態取得。uiTemplate
変数: HTMLテンプレートの定義とカスタム関数の登録。
misc/dashboard/app/build/ui.html
(新規ファイル):- GoビルドダッシュボードのHTML構造とテンプレートロジック。
- コミット一覧、ビルダーごとの結果表示、ページネーション、他のパッケージの状態表示。
misc/dashboard/app/static/status_alert.gif
(新規ファイル): ビルド失敗を示す画像。misc/dashboard/app/static/status_good.gif
(新規ファイル): ビルド成功を示す画像。
コアとなるコードの解説
misc/dashboard/app/build/build.go
の変更点
// Commit struct: Result field renamed and new methods for result access
type Commit struct {
// ... other fields ...
ResultData []string `datastore:",noindex"` // Renamed from Result
}
// LastCommit returns the most recent Commit for this Package.
func (p *Package) LastCommit(c appengine.Context) (*Commit, os.Error) {
var commits []*Commit
_, err := datastore.NewQuery("Commit").
Ancestor(p.Key(c)).
Order("-Time").
Limit(1).
GetAll(c, &commits)
if err != nil {
return nil, err
}
if len(commits) != 1 {
return nil, datastore.ErrNoSuchEntity
}
return commits[0], nil
}
// Result returns the build Result for this Commit for the given builder/goHash.
func (c *Commit) Result(builder, goHash string) *Result {
for _, r := range c.ResultData {
p := strings.SplitN(r, "|", 4)
if len(p) != 4 || p[0] != builder || p[3] != goHash {
continue
}
return partsToHash(c, p)
}
return nil
}
// Results returns the build Results for this Commit for the given goHash.
func (c *Commit) Results(goHash string) (results []*Result) {
for _, r := range c.ResultData {
p := strings.SplitN(r, "|", 4)
if len(p) != 4 || p[3] != goHash {
continue
}
results = append(results, partsToHash(c, p))
}
return
}
// partsToHash converts a Commit and ResultData substrings to a Result.
func partsToHash(c *Commit, p []string) *Result {
return &Result{
Builder: p[0],
Hash: c.Hash,
PackagePath: c.PackagePath,
GoHash: p[3],
OK: p[1] == "true",
LogHash: p[2],
}
}
Commit
構造体のResult
フィールドがResultData
に変わり、ビルド結果の生データ("builder|ok_status|log_hash|go_hash"
のような文字列)を保持するようになりました。これは、Datastoreへの保存を簡素化し、必要なときにResult
オブジェクトに変換するためのものです。LastCommit
は、特定のパッケージの最新コミットを効率的に取得するためのヘルパーです。Result
とResults
メソッドは、ResultData
スライスを走査し、指定された条件(ビルダーやGoハッシュ)に合致するビルド結果をResult
構造体として返します。partsToHash
は、この文字列データをResult
構造体に変換する内部ヘルパー関数です。これらの変更により、UI側でビルド結果を柔軟に取得・表示するための基盤が強化されました。
misc/dashboard/app/build/ui.go
(新規ファイル)
package build
import (
"appengine"
"appengine/datastore"
"exp/template/html" // Note: exp/template/html is an experimental package at the time
"http"
"os"
"regexp"
"sort"
"strconv"
"strings"
"template" // Note: template is the old text/template, html/template is preferred for HTML
)
func init() {
http.HandleFunc("/", uiHandler)
html.Escape(uiTemplate) // Ensures the template is safely escaped
}
// uiHandler draws the build status page.
func uiHandler(w http.ResponseWriter, r *http.Request) {
c := appengine.NewContext(r)
page, _ := strconv.Atoi(r.FormValue("page"))
if page < 0 {
page = 0
}
commits, err := goCommits(c, page) // Fetch Go commits
if err != nil {
logErr(w, r, err)
return
}
builders := commitBuilders(commits) // Get unique builders from commits
tipState, err := TagState(c, "tip") // Get state of other packages at "tip"
if err != nil {
logErr(w, r, err)
return
}
p := &Pagination{} // Pagination logic
if len(commits) == commitsPerPage {
p.Next = page + 1
}
if page > 0 {
p.Prev = page - 1
p.HasPrev = true
}
data := &uiTemplateData{commits, builders, tipState, p}
if err := uiTemplate.Execute(w, data); err != nil { // Render template
logErr(w, r, err)
}
}
// goCommits gets a slice of the latest Commits to the Go repository.
func goCommits(c appengine.Context, page int) ([]*Commit, os.Error) {
q := datastore.NewQuery("Commit").
Ancestor((&Package{}).Key(c)).
Order("-Time").
Limit(commitsPerPage).
Offset(page * commitsPerPage)
var commits []*Commit
_, err := q.GetAll(c, &commits)
return commits, err
}
// commitBuilders returns the names of the builders that provided
// Results for the provided commits.
func commitBuilders(commits []*Commit) []string {
builders := make(map[string]bool)
for _, commit := range commits {
for _, r := range commit.Results("") { // Get all results for a commit
builders[r.Builder] = true
}
}
return keys(builders) // Return sorted unique builder names
}
// TagState fetches the results for all non-Go packages at the specified tag.
func TagState(c appengine.Context, name string) ([]*PackageState, os.Error) {
tag, err := GetTag(c, name) // Get the tag (e.g., "tip")
if err != nil {
return nil, err
}
pkgs, err := Packages(c) // Get all non-Go packages
if err != nil {
return nil, err
}
var states []*PackageState
for _, pkg := range pkgs {
commit, err := pkg.LastCommit(c) // Get last commit for each package
if err != nil {
c.Errorf("no Commit found: %v", pkg)
continue
}
results := commit.Results(tag.Hash) // Get results for this package at the tag's Go hash
ok := len(results) > 0
for _, r := range results {
ok = ok && r.OK
}
states = append(states, &PackageState{
pkg, commit, results, ok,
})
}
return states, nil
}
// uiTemplate defines the HTML template and its custom functions.
var uiTemplate = template.Must(
template.New("ui").
Funcs(template.FuncMap{
"builderTitle": builderTitle,
"shortHash": shortHash,
"repoURL": repoURL,
}).
ParseFile("build/ui.html"),
)
ui.go
は、ダッシュボードのWebページを生成する中心的なロジックを含んでいます。init
関数でルートパスにuiHandler
を登録し、アプリケーション起動時にUIが利用可能になるようにします。uiHandler
は、HTTPリクエストを受け取り、Datastoreからコミットデータやパッケージの状態を取得し、それらをuiTemplateData
構造体にまとめてuiTemplate
に渡してレンダリングします。goCommits
はGoリポジトリのコミットをページネーション付きで取得し、commitBuilders
は表示すべきビルダーのリストを動的に決定します。TagState
は、"tip"などの特定のタグにおける他のGoパッケージのビルド状態を収集します。uiTemplate
は、ui.html
ファイルを読み込み、builderTitle
、shortHash
、repoURL
といったカスタム関数を登録することで、HTML内でGoのデータを整形して表示できるようにします。
misc/dashboard/app/build/ui.html
(新規ファイル)
<!DOCTYPE HTML>
<html>
<head>
<title>Go Build Dashboard</title>
<style>
/* ... CSS styles ... */
</style>
</head>
<body>
<h1>Go Build Status</h1>
<h2>Go</h2>
{{if $.Commits}}
<table class="build">
<tr>
<th> </th>
{{range $.Builders}}
<th class="result">{{builderTitle .}}</th>
{{end}}
</tr>
{{range $c := $.Commits}}
<tr>
<td class="hash"><a href="{{repoURL .Hash ""}}">{{shortHash .Hash}}</a></td>
{{range $.Builders}}
<td class="result">
{{with $c.Result . ""}}
{{if .OK}}
<span class="ok">ok</span>
{{else}}
<a href="/log/{{.LogHash}}" class="fail">fail</a>
{{end}}
{{else}}
{{end}}
</td>
{{end}}
<td class="user">{{.User}}</td>
<td class="time">{{.Time.Time}}</td>
<td class="desc">{{.Desc}}</td>
</tr>
{{end}}
</table>
{{with $.Pagination}}
<div class="paginate">
<a {{if .HasPrev}}href="?page={{.Prev}}"{{else}}class="inactive"{{end}}>prev</a>
<a {{if .Next}}href="?page={{.Next}}"{{else}}class="inactive"{{end}}>next</a>
<a {{if .HasPrev}}href="?page=0}"{{else}}class="inactive"{{end}}>top</a>
</div>
{{end}}
{{else}}
<p>No commits to display. Hm.</p>
{{end}}
<h2>Other packages</h2>
<table class="packages">
<tr>
<th>State</th>
<th>Package</th>
<th> </th>
</tr>
{{range $state := $.TipState}}
<tr>
<td>
{{if .Results}}
<img src="/static/status_{{if .OK}}good{{else}}alert{{end}}.gif" />
{{else}}
{{end}}
</td>
<td><a title="{{.Package.Path}}">{{.Package.Name}}</a></td>
<td>
{{range .Results}}
<div>
{{$h := $state.Commit.Hash}}
<a href="{{repoURL $h $state.Commit.PackagePath}}">{{shortHash $h}}</a>
<a href="/log/{{.LogHash}}">failed</a>
on {{.Builder}}/<a href="{{repoURL .GoHash ""}}">{{shortHash .GoHash}}</a>
</a></div>
{{end}}
</td>
</tr>
{{end}}
</table>
</body>
</html>
ui.html
は、Goビルドダッシュボードのユーザーインターフェースを定義するHTMLテンプレートです。{{if}}
、{{range}}
、{{with}}
といったGoのテンプレートアクションを使用して、ui.go
から渡されたデータに基づいて動的にコンテンツを生成します。
- Goビルドステータス:
$.Commits
をループして各コミットの情報を表示し、$.Builders
をループして各ビルダーのビルド結果を表示します。ビルドが成功した場合は"ok"、失敗した場合は"fail"と表示され、失敗時にはログへのリンクが提供されます。 - ページネーション:
$.Pagination
データに基づいて、"prev"、"next"、"top"へのリンクを生成し、過去のコミットを閲覧できるようにします。 - Other packages:
$.TipState
をループして、他のGoパッケージのビルド状態を表示します。成功/失敗はstatus_good.gif
またはstatus_alert.gif
画像で視覚的に示され、パッケージ名と詳細なビルド結果(失敗時のログリンクを含む)が表示されます。 このテンプレートは、Goのビルド状態を簡潔かつ効果的にユーザーに伝えるための視覚的な表現を提供します。
関連リンク
- Go言語公式サイト: https://golang.org/
- Google App Engine: https://cloud.google.com/appengine
- Google Cloud Datastore: https://cloud.google.com/datastore
参考にした情報源リンク
- Go言語の公式ドキュメント(
html/template
、net/http
、appengine
パッケージなど) - Google App Engineのドキュメント
- GoプロジェクトのGitHubリポジトリ
- コミットメッセージに記載されているCode Reviewリンク: https://golang.org/cl/5461047