storage: report a view URL in /upload responses if configured

Change-Id: I5e10082adc66e51898a71e1a67da7833f287af9d
Reviewed-on: https://go-review.googlesource.com/35067
Reviewed-by: Russ Cox <rsc@golang.org>
diff --git a/storage/app/app.go b/storage/app/app.go
index fea66b0..3db5b7f 100644
--- a/storage/app/app.go
+++ b/storage/app/app.go
@@ -25,6 +25,11 @@
 	// If necessary, it can write its own response (e.g. a
 	// redirect) and return ErrResponseWritten.
 	Auth func(http.ResponseWriter, *http.Request) (string, error)
+
+	// ViewURLBase will be used to construct a URL to return as
+	// "viewurl" in the response from /upload. If it is non-empty,
+	// the upload ID will be appended to ViewURLBase.
+	ViewURLBase string
 }
 
 // ErrResponseWritten can be returned by App.Auth to abort the normal /upload handling.
diff --git a/storage/app/upload.go b/storage/app/upload.go
index 60467df..68a378f 100644
--- a/storage/app/upload.go
+++ b/storage/app/upload.go
@@ -11,6 +11,7 @@
 	"io"
 	"mime/multipart"
 	"net/http"
+	"net/url"
 	"sort"
 
 	"golang.org/x/net/context"
@@ -71,6 +72,8 @@
 	UploadID string `json:"uploadid"`
 	// FileIDs is the list of file IDs assigned to the files in the upload.
 	FileIDs []string `json:"fileids"`
+	// ViewURL is a URL that can be used to interactively view the upload.
+	ViewURL string `json:"viewurl,omitempty"`
 }
 
 // processUpload takes one or more files from a multipart.Reader,
@@ -118,7 +121,12 @@
 		fileids = append(fileids, meta["fileid"])
 	}
 
-	return &uploadStatus{upload.ID, fileids}, nil
+	status := &uploadStatus{UploadID: upload.ID, FileIDs: fileids}
+	if a.ViewURLBase != "" {
+		status.ViewURL = a.ViewURLBase + url.QueryEscape(upload.ID)
+	}
+
+	return status, nil
 }
 
 func (a *App) indexFile(ctx context.Context, upload *db.Upload, p io.Reader, meta map[string]string) (err error) {
diff --git a/storage/app/upload_test.go b/storage/app/upload_test.go
index 7f976e0..5560c0e 100644
--- a/storage/app/upload_test.go
+++ b/storage/app/upload_test.go
@@ -12,6 +12,7 @@
 	"mime/multipart"
 	"net/http"
 	"net/http/httptest"
+	"reflect"
 	"testing"
 
 	"golang.org/x/perf/storage/db"
@@ -44,9 +45,10 @@
 	fs := fs.NewMemFS()
 
 	app := &App{
-		DB:   db,
-		FS:   fs,
-		Auth: func(http.ResponseWriter, *http.Request) (string, error) { return "user", nil },
+		DB:          db,
+		FS:          fs,
+		Auth:        func(http.ResponseWriter, *http.Request) (string, error) { return "user", nil },
+		ViewURLBase: "view:",
 	}
 
 	mux := http.NewServeMux()
@@ -94,7 +96,7 @@
 	app := createTestApp(t)
 	defer app.Close()
 
-	app.uploadFiles(t, func(mpw *multipart.Writer) {
+	status := app.uploadFiles(t, func(mpw *multipart.Writer) {
 		w, err := mpw.CreateFormFile("file", "1.txt")
 		if err != nil {
 			t.Errorf("CreateFormFile: %v", err)
@@ -102,6 +104,16 @@
 		fmt.Fprintf(w, "key: value\nBenchmarkOne 5 ns/op\nkey:value2\nBenchmarkTwo 10 ns/op\n")
 	})
 
+	if status.UploadID != "1" {
+		t.Errorf("uploadid = %q, want %q", status.UploadID, "1")
+	}
+	if have, want := status.FileIDs, []string{"1/0"}; !reflect.DeepEqual(have, want) {
+		t.Errorf("fileids = %v, want %v", have, want)
+	}
+	if status.ViewURL != "view:1" {
+		t.Errorf("viewurl = %q, want %q", status.ViewURL, "view:1")
+	}
+
 	if len(app.fs.Files()) != 1 {
 		t.Errorf("/upload wrote %d files, want 1", len(app.fs.Files()))
 	}
diff --git a/storage/appengine/app.go b/storage/appengine/app.go
index 498e812..d948f81 100644
--- a/storage/appengine/app.go
+++ b/storage/appengine/app.go
@@ -65,7 +65,8 @@
 // It creates a new App instance using the appengine Context and then
 // dispatches the request to the App. The environment variable
 // GCS_BUCKET must be set in app.yaml with the name of the bucket to
-// write to.
+// write to. PERFDATA_VIEW_URL_BASE may be set to the URL that should
+// be supplied in /upload responses.
 func appHandler(w http.ResponseWriter, r *http.Request) {
 	ctx := appengine.NewContext(r)
 	// GCS clients need to be constructed with an AppEngine
@@ -88,7 +89,7 @@
 		return
 	}
 	mux := http.NewServeMux()
-	app := &app.App{DB: db, FS: fs, Auth: auth}
+	app := &app.App{DB: db, FS: fs, Auth: auth, ViewURLBase: os.Getenv("PERFDATA_VIEW_URL_BASE")}
 	app.RegisterOnMux(mux)
 	mux.ServeHTTP(w, r)
 }
diff --git a/storage/appengine/app.yaml b/storage/appengine/app.yaml
index 757ceb8..05e4621 100644
--- a/storage/appengine/app.yaml
+++ b/storage/appengine/app.yaml
@@ -21,3 +21,4 @@
   CLOUDSQL_PASSWORD: ''
   CLOUDSQL_DATABASE: perfdata
   GCS_BUCKET: golang-perfdata
+  PERFDATA_VIEW_URL_BASE: https://perf.golang.org/search?q=uploadid: