[perf] Serialize the Alert ID as a string.

Not all int64s can be expressed in JS.

Also fix email test on alerts page, there is no JSON response, only the status code.

Change-Id: I640f39dcf7b8a004ebe8423176970ac556332314
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/303517
Reviewed-by: Joe Gregorio <jcgregorio@google.com>
Commit-Queue: Joe Gregorio <jcgregorio@google.com>
diff --git a/perf/configs/flutter-flutter.json b/perf/configs/flutter-flutter.json
index acdfd29..b5a5773 100644
--- a/perf/configs/flutter-flutter.json
+++ b/perf/configs/flutter-flutter.json
@@ -18,7 +18,7 @@
   },
   "git_repo_config": {
     "url": "https://github.com/flutter/flutter",
-    "dir": "/tmp/repo",
+    "dir": "/tmp/flutter-flutter",
     "debounce_commit_url": false,
     "commit_url": "%s/commit/%s"
   }
diff --git a/perf/go/alerts/alertstest/alertstest.go b/perf/go/alerts/alertstest/alertstest.go
index 38da99e..bb76960 100644
--- a/perf/go/alerts/alertstest/alertstest.go
+++ b/perf/go/alerts/alertstest/alertstest.go
@@ -64,7 +64,7 @@
 	// Add some data to the empty config.
 	cfg.Query = "source_type=svg"
 	cfg.DisplayName = "bar"
-	cfg.ID = 12
+	cfg.IDAsString = "12"
 	err := a.Save(ctx, cfg)
 	require.NoError(t, err)
 
diff --git a/perf/go/alerts/config.go b/perf/go/alerts/config.go
index 42818a2..0fc057f 100644
--- a/perf/go/alerts/config.go
+++ b/perf/go/alerts/config.go
@@ -75,7 +75,11 @@
 
 // Alert represents the configuration for one alert.
 type Alert struct {
+	// We need to keep the int64 version of the ID around to support Cloud
+	// Datastore. Once everyone migrates to SQL backed datastores it can be
+	// removed.
 	ID             int64                             `json:"id"               datastore:",noindex"`
+	IDAsString     string                            `json:"id_as_string"     datastore:",noindex"`
 	DisplayName    string                            `json:"display_name"     datastore:",noindex"`
 	Query          string                            `json:"query"            datastore:",noindex"` // The query to perform on the trace store to select the traces to alert on.
 	Alert          string                            `json:"alert"            datastore:",noindex"` // Email address to send alerts to.
@@ -306,6 +310,7 @@
 func NewConfig() *Alert {
 	return &Alert{
 		ID:                BadAlertID,
+		IDAsString:        fmt.Sprintf("%d", BadAlertID),
 		Algo:              types.KMeansGrouping,
 		StateAsString:     ACTIVE,
 		Sparse:            DefaultSparse,
diff --git a/perf/go/alerts/dsalertstore/dsalertstore.go b/perf/go/alerts/dsalertstore/dsalertstore.go
index a025963..d3656de 100644
--- a/perf/go/alerts/dsalertstore/dsalertstore.go
+++ b/perf/go/alerts/dsalertstore/dsalertstore.go
@@ -26,6 +26,7 @@
 
 // Save implements the alerts.Store interface.
 func (s *DSAlertStore) Save(ctx context.Context, cfg *alerts.Alert) error {
+	cfg.SetIDFromString(cfg.IDAsString)
 	if err := cfg.Validate(); err != nil {
 		return fmt.Errorf("Failed to save invalid Config: %s", err)
 	}
@@ -96,6 +97,7 @@
 			return nil, fmt.Errorf("Failed retrieving alert list: %s", err)
 		}
 		cfg.ID = k.ID
+		cfg.IDAsString = fmt.Sprintf("%d", k.ID)
 		if err := cfg.Validate(); err != nil {
 			sklog.Errorf("Found an invalid alert %v: %s", *cfg, err)
 		}
diff --git a/perf/go/alerts/sqlalertstore/sqlalertstore.go b/perf/go/alerts/sqlalertstore/sqlalertstore.go
index 9ce26d4..498b47c 100644
--- a/perf/go/alerts/sqlalertstore/sqlalertstore.go
+++ b/perf/go/alerts/sqlalertstore/sqlalertstore.go
@@ -7,6 +7,7 @@
 	"context"
 	"database/sql"
 	"encoding/json"
+	"fmt"
 	"time"
 
 	"go.skia.org/infra/go/skerr"
@@ -133,6 +134,7 @@
 
 // Save implements the alerts.Store interface.
 func (s *SQLAlertStore) Save(ctx context.Context, cfg *alerts.Alert) error {
+	cfg.SetIDFromString(cfg.IDAsString)
 	b, err := json.Marshal(cfg)
 	if err != nil {
 		return skerr.Wrapf(err, "Failed to serialize Alert for saving with ID=%d", cfg.ID)
@@ -177,12 +179,13 @@
 		if err := rows.Scan(&id, &serializedAlert); err != nil {
 			return nil, err
 		}
-		var a alerts.Alert
-		if err := json.Unmarshal([]byte(serializedAlert), &a); err != nil {
+		a := &alerts.Alert{}
+		if err := json.Unmarshal([]byte(serializedAlert), a); err != nil {
 			return nil, skerr.Wrapf(err, "Failed to deserialize JSON Alert.")
 		}
 		a.ID = id
-		ret = append(ret, &a)
+		a.IDAsString = fmt.Sprintf("%d", id)
+		ret = append(ret, a)
 	}
 	return ret, nil
 }
diff --git a/perf/modules/alert-config-sk/alert-config-sk-demo.ts b/perf/modules/alert-config-sk/alert-config-sk-demo.ts
index a776159..0ac6aee 100644
--- a/perf/modules/alert-config-sk/alert-config-sk-demo.ts
+++ b/perf/modules/alert-config-sk/alert-config-sk-demo.ts
@@ -27,6 +27,7 @@
 
 const config: Alert = {
   id: 1,
+  id_as_string: '1',
   sparse: false,
   step_up_only: false,
   display_name: 'A name',
diff --git a/perf/modules/alert-config-sk/alert-config-sk.ts b/perf/modules/alert-config-sk/alert-config-sk.ts
index 1948374..8111ee6 100644
--- a/perf/modules/alert-config-sk/alert-config-sk.ts
+++ b/perf/modules/alert-config-sk/alert-config-sk.ts
@@ -334,6 +334,7 @@
     this.paramkeys = [];
     this._config = {
       id: -1,
+      id_as_string: '-1',
       display_name: 'Name',
       query: '',
       alert: '',
@@ -402,7 +403,6 @@
         'Content-Type': 'application/json',
       },
     })
-      .then(jsonOrThrow)
       .then(() => {
         this.alertSpinner!.active = false;
       })
diff --git a/perf/modules/alerts-page-sk/alerts-page-sk.ts b/perf/modules/alerts-page-sk/alerts-page-sk.ts
index 97548a4..305e4ce 100644
--- a/perf/modules/alerts-page-sk/alerts-page-sk.ts
+++ b/perf/modules/alerts-page-sk/alerts-page-sk.ts
@@ -173,8 +173,10 @@
     if (window.location.search.length === 0) {
       return;
     }
-    const id = +window.location.search.slice(1);
-    const matchingAlert = this.alerts.find((alert) => id === alert.id);
+    const id = window.location.search.slice(1);
+    const matchingAlert = this.alerts.find(
+      (alert) => id === alert.id_as_string
+    );
     if (matchingAlert) {
       this.startEditing(matchingAlert);
     }
@@ -231,9 +233,12 @@
   }
 
   private delete(e: MouseEvent) {
-    fetch(`/_/alert/delete/${((e.target! as any).__config as Alert).id}`, {
-      method: 'POST',
-    })
+    fetch(
+      `/_/alert/delete/${((e.target! as any).__config as Alert).id_as_string}`,
+      {
+        method: 'POST',
+      }
+    )
       .then(okOrThrow)
       .then(() => {
         this.list();
diff --git a/perf/modules/cluster-lastn-page-sk/cluster-lastn-page-sk-demo.ts b/perf/modules/cluster-lastn-page-sk/cluster-lastn-page-sk-demo.ts
index c909894..ca89b3f 100644
--- a/perf/modules/cluster-lastn-page-sk/cluster-lastn-page-sk-demo.ts
+++ b/perf/modules/cluster-lastn-page-sk/cluster-lastn-page-sk-demo.ts
@@ -58,6 +58,7 @@
 
 const alert: Alert = {
   id: -1,
+  id_as_string: '-1',
   sparse: false,
   step_up_only: false,
   display_name: 'A name',
diff --git a/perf/modules/cluster-page-sk/cluster-page-sk.ts b/perf/modules/cluster-page-sk/cluster-page-sk.ts
index b75732f..6d01511 100644
--- a/perf/modules/cluster-page-sk/cluster-page-sk.ts
+++ b/perf/modules/cluster-page-sk/cluster-page-sk.ts
@@ -428,6 +428,7 @@
       total_queries: 0,
       alert: {
         id: -1,
+        id_as_string: '-1',
         display_name: '',
         radius: +this.state.radius,
         query: this.state.query,
diff --git a/perf/modules/json/index.ts b/perf/modules/json/index.ts
index dfa686d..611a5b5 100644
--- a/perf/modules/json/index.ts
+++ b/perf/modules/json/index.ts
@@ -2,6 +2,7 @@
 
 export interface Alert {
 	id: number;
+	id_as_string: string;
 	display_name: string;
 	query: string;
 	alert: string;
diff --git a/perf/modules/triage-page-sk/triage-page-sk.ts b/perf/modules/triage-page-sk/triage-page-sk.ts
index 40cb6aa..372ce5a 100644
--- a/perf/modules/triage-page-sk/triage-page-sk.ts
+++ b/perf/modules/triage-page-sk/triage-page-sk.ts
@@ -309,7 +309,9 @@
       // The colspan=2 is important since we will have two columns under each
       // header, one for high and one for low.
       return html`
-        <th colspan="2"><a href="/a/?${item!.id}">${displayName}</a></th>
+        <th colspan="2">
+          <a href="/a/?${item!.id_as_string}">${displayName}</a>
+        </th>
       `;
     });
 
@@ -320,7 +322,9 @@
           <tr>
             <th>Alert</th>
             <td>
-              <a href="/a/?${item.alert!.id}">${item.alert!.display_name}</a>
+              <a href="/a/?${item.alert!.id_as_string}">
+                ${item.alert!.display_name}
+              </a>
             </td>
           </tr>
           <tr>