Add full git log entry to the details display on the Explore page.

Bug: skia:13726
Change-Id: I97e6badd976915edaf507f8ae157693ef280aa48
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/579299
Reviewed-by: Kevin Lubick <kjlubick@google.com>
Commit-Queue: Joe Gregorio <jcgregorio@google.com>
diff --git a/perf/go/frontend/frontend.go b/perf/go/frontend/frontend.go
index 3b8f216..299f725 100644
--- a/perf/go/frontend/frontend.go
+++ b/perf/go/frontend/frontend.go
@@ -619,6 +619,16 @@
 	}
 }
 
+// CIDHandlerResponse is the form of the response from the /_/cid/ endpoint.
+type CIDHandlerResponse struct {
+	// CommitSlice describes all the commits requested.
+	CommitSlice []perfgit.Commit `json:"commitSlice"`
+
+	// LogEntry is the full git log entry for the first commit in the
+	// CommitSlice.
+	LogEntry string `json:"logEntry"`
+}
+
 // cidHandler takes the POST'd list of dataframe.ColumnHeaders, and returns a
 // serialized slice of cid.CommitDetails.
 func (f *Frontend) cidHandler(w http.ResponseWriter, r *http.Request) {
@@ -629,11 +639,21 @@
 		httputils.ReportError(w, err, "Could not decode POST body.", http.StatusInternalServerError)
 		return
 	}
-	resp, err := f.perfGit.CommitSliceFromCommitNumberSlice(ctx, cids)
+	commits, err := f.perfGit.CommitSliceFromCommitNumberSlice(ctx, cids)
 	if err != nil {
 		httputils.ReportError(w, err, "Failed to lookup all commit ids", http.StatusInternalServerError)
 		return
 	}
+	logEntry, err := f.perfGit.LogEntry(ctx, cids[0])
+	if err != nil {
+		httputils.ReportError(w, err, "Failed to get log entry", http.StatusInternalServerError)
+		return
+	}
+
+	resp := CIDHandlerResponse{
+		CommitSlice: commits,
+		LogEntry:    logEntry,
+	}
 
 	if err := json.NewEncoder(w).Encode(resp); err != nil {
 		sklog.Errorf("Failed to encode paramset: %s", err)
diff --git a/perf/go/git/git.go b/perf/go/git/git.go
index 0fdde81..b4d3cfc 100644
--- a/perf/go/git/git.go
+++ b/perf/go/git/git.go
@@ -7,6 +7,7 @@
 
 import (
 	"bufio"
+	"bytes"
 	"context"
 	"fmt"
 	"io"
@@ -662,3 +663,23 @@
 
 	return ret, nil
 }
+
+// LogEntry returns the full log entry of a commit (minus the diff) as a string.
+func (g *Git) LogEntry(ctx context.Context, commit types.CommitNumber) (string, error) {
+	hash, err := g.GitHashFromCommitNumber(ctx, commit)
+	if err != nil {
+		return "", skerr.Wrap(err)
+	}
+
+	// Build the git log command to run.
+	cmd := exec.CommandContext(ctx, g.gitFullPath, "show", "-s", hash)
+	cmd.Dir = g.instanceConfig.GitRepoConfig.Dir
+	var out bytes.Buffer
+	cmd.Stdout = &out
+
+	if err := cmd.Run(); err != nil {
+		return "", skerr.Wrapf(err, "Failed running git show.")
+	}
+
+	return out.String(), nil
+}
diff --git a/perf/go/git/git_test.go b/perf/go/git/git_test.go
index 0ad848f..560556e 100644
--- a/perf/go/git/git_test.go
+++ b/perf/go/git/git_test.go
@@ -60,6 +60,8 @@
 	"testCommitNumbersWhenFileChangesInCommitNumberRange_RangeIsInclusiveOfEnd":          testCommitNumbersWhenFileChangesInCommitNumberRange_RangeIsInclusiveOfEnd,
 	"testCommitNumbersWhenFileChangesInCommitNumberRange_ResultsWhenBeginEqualsEnd":      testCommitNumbersWhenFileChangesInCommitNumberRange_ResultsWhenBeginEqualsEnd,
 	"testCommitNumbersWhenFileChangesInCommitNumberRange_HandlesZeroAsBeginCommitNumber": testCommitNumbersWhenFileChangesInCommitNumberRange_HandlesZeroAsBeginCommitNumber,
+	"testLogEntry_Success":                                                               testLogEntry_Success,
+	"testLogEntry_BadCommitId_ReturnsError":                                              testLogEntry_BadCommitId_ReturnsError,
 }
 
 func testUpdate_NewCommitsAreFoundAfterUpdate(t *testing.T, ctx context.Context, g *Git, gb *testutils.GitBuilder, hashes []string, cleanup gittest.CleanupFunc) {
@@ -321,6 +323,27 @@
 	assert.Equal(t, []types.CommitNumber{3}, commits)
 }
 
+func testLogEntry_Success(t *testing.T, ctx context.Context, g *Git, gb *testutils.GitBuilder, hashes []string, cleanup gittest.CleanupFunc) {
+	defer cleanup()
+
+	got, err := g.LogEntry(ctx, types.CommitNumber(1))
+	require.NoError(t, err)
+	expected := `commit 881dfc43620250859549bb7e0301b6910d9b8e70
+Author: test <test@google.com>
+Date:   Tue Mar 28 10:41:00 2023 +0000
+
+    501233450539197794
+`
+	require.Equal(t, expected, got)
+}
+
+func testLogEntry_BadCommitId_ReturnsError(t *testing.T, ctx context.Context, g *Git, gb *testutils.GitBuilder, hashes []string, cleanup gittest.CleanupFunc) {
+	defer cleanup()
+
+	_, err := g.LogEntry(ctx, types.BadCommitNumber)
+	require.Error(t, err)
+}
+
 func TestParseGitRevLogStream_Success(t *testing.T) {
 	r := strings.NewReader(
 		`commit 6079a7810530025d9877916895dd14eb8bb454c0
diff --git a/perf/go/ts/main.go b/perf/go/ts/main.go
index b610d91..16ac5b2 100644
--- a/perf/go/ts/main.go
+++ b/perf/go/ts/main.go
@@ -62,6 +62,7 @@
 		frame.FrameRequest{},
 		frame.FrameResponse{},
 		frontend.AlertUpdateResponse{},
+		frontend.CIDHandlerResponse{},
 		frontend.ClusterStartResponse{},
 		frontend.CommitDetailsRequest{},
 		frontend.CountHandlerRequest{},
diff --git a/perf/modules/explore-sk/explore-sk.scss b/perf/modules/explore-sk/explore-sk.scss
index 3d63213..9bd16e6 100644
--- a/perf/modules/explore-sk/explore-sk.scss
+++ b/perf/modules/explore-sk/explore-sk.scss
@@ -67,7 +67,11 @@
   }
 
   #simple_paramset {
-    margin: 1em 0 1em 1em;
+    margin: 1em;
+  }
+
+  #params_and_logentry {
+    display: flex;
   }
 
   #percent {
diff --git a/perf/modules/explore-sk/explore-sk.ts b/perf/modules/explore-sk/explore-sk.ts
index d4dc1ac..0af6e1e 100644
--- a/perf/modules/explore-sk/explore-sk.ts
+++ b/perf/modules/explore-sk/explore-sk.ts
@@ -49,6 +49,7 @@
   pivot,
   FrameResponseDisplayMode,
   ColumnHeader,
+  CIDHandlerResponse,
 } from '../json';
 import {
   PlotSimpleSk,
@@ -326,6 +327,8 @@
 
   private ingestFileLinks: IngestFileLinksSk | null = null;
 
+  private logEntry: HTMLPreElement | null = null;
+
   private paramset: ParamSetSk | null = null;
 
   private percent: HTMLSpanElement | null = null;
@@ -579,12 +582,15 @@
             </paramset-sk>
         </div>
         <div id=details>
-          <paramset-sk
-            id=simple_paramset
-            clickable_values
-            @paramset-key-value-click=${ele.paramsetKeyValueClick}
-            >
-          </paramset-sk>
+          <div id=params_and_logentry>
+            <paramset-sk
+              id=simple_paramset
+              clickable_values
+              @paramset-key-value-click=${ele.paramsetKeyValueClick}
+              >
+            </paramset-sk>
+            <code><pre id=logEntry></pre></code>
+          </div>
           <div>
             <commit-detail-panel-sk id=commits selectable></commit-detail-panel-sk>
             <ingest-file-links-sk class="hide_on_pivot_plot" id=ingest-file-links></ingest-file-links-sk>
@@ -610,6 +616,7 @@
     this.formula = this.querySelector('#formula');
     this.jsonsource = this.querySelector('#jsonsource');
     this.ingestFileLinks = this.querySelector('#ingest-file-links');
+    this.logEntry = this.querySelector('#logEntry');
     this.paramset = this.querySelector('#paramset');
     this.percent = this.querySelector('#percent');
     this.plot = this.querySelector('#plot');
@@ -950,10 +957,11 @@
       },
     })
       .then(jsonOrThrow)
-      .then((json) => {
-        this.commits!.details = json;
+      .then((json: CIDHandlerResponse) => {
+        this.commits!.details = json.commitSlice || [];
         this.commitsTab!.disabled = false;
         this.simpleParamset!.paramsets = [paramset as CommonSkParamSet];
+        this.logEntry!.textContent = json.logEntry;
         this.detailTab!.selected = COMMIT_TAB_INDEX;
         const cid = commits[0]!;
         const traceid = e.detail.name;
@@ -970,6 +978,7 @@
     // Switch back to the params tab since we are about to hide the details tab.
     this.detailTab!.selected = PARAMS_TAB_INDEX;
     this.commitsTab!.disabled = true;
+    this.logEntry!.textContent = '';
     this.plot!.highlight = [];
     this.plot!.xbar = -1;
     this.state.selected = defaultPointSelected();
diff --git a/perf/modules/json/index.ts b/perf/modules/json/index.ts
index eefe80d..56715b9 100644
--- a/perf/modules/json/index.ts
+++ b/perf/modules/json/index.ts
@@ -127,6 +127,11 @@
 	IDAsString: string;
 }
 
+export interface CIDHandlerResponse {
+	commitSlice: Commit[] | null;
+	logEntry: string;
+}
+
 export interface ClusterStartResponse {
 	id: string;
 }