cmd/relui: generate uuid for workflow and task instances

Sets workflow and tasks IDs when a workflow is created in the UI. These
IDs will be used in the future when operating on workflows and tasks via
the UI or API.

This ID will likely change to a datastore ID after datastore integration
is added.

For golang/go#40279

Co-authored-by: Carlos Amedee <carlos@golang.org>
Change-Id: Ib75c00c234d156cc1bbdab8c658281f04914165b
Reviewed-on: https://go-review.googlesource.com/c/build/+/257238
Trust: Alexander Rakoczy <alex@golang.org>
Run-TryBot: Alexander Rakoczy <alex@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
diff --git a/cmd/relui/protos/relui.pb.go b/cmd/relui/protos/relui.pb.go
index 6cca73a..27d41e5 100644
--- a/cmd/relui/protos/relui.pb.go
+++ b/cmd/relui/protos/relui.pb.go
@@ -55,10 +55,12 @@
 	// buildable_asks is a list of tasks to be performed by the workflow.
 	BuildableTasks []*BuildableTask `protobuf:"bytes,2,rep,name=buildable_tasks,json=buildableTasks,proto3" json:"buildable_tasks,omitempty"`
 	// params are parameters provided when creating a workflow.
-	Params               map[string]string `protobuf:"bytes,3,rep,name=params,proto3" json:"params,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
-	XXX_NoUnkeyedLiteral struct{}          `json:"-"`
-	XXX_unrecognized     []byte            `json:"-"`
-	XXX_sizecache        int32             `json:"-"`
+	Params map[string]string `protobuf:"bytes,3,rep,name=params,proto3" json:"params,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+	// id is a unique identifier generated by relui when a workflow is created. Do not set.
+	Id                   string   `protobuf:"bytes,4,opt,name=id,proto3" json:"id,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
 }
 
 func (m *Workflow) Reset()         { *m = Workflow{} }
@@ -107,6 +109,13 @@
 	return nil
 }
 
+func (m *Workflow) GetId() string {
+	if m != nil {
+		return m.Id
+	}
+	return ""
+}
+
 type BuildableTask struct {
 	// name is a unique name for a task, such as fetch_go_source. The name must be unique across
 	// all workflow configurations.
@@ -122,7 +131,9 @@
 	GitSource *GitSource `protobuf:"bytes,5,opt,name=git_source,json=gitSource,proto3" json:"git_source,omitempty"`
 	// task_type is a unique type for a task, such as FetchGerritSource. Types are used by task runners to identify
 	// how to execute a task.
-	TaskType             string   `protobuf:"bytes,6,opt,name=task_type,json=taskType,proto3" json:"task_type,omitempty"`
+	TaskType string `protobuf:"bytes,6,opt,name=task_type,json=taskType,proto3" json:"task_type,omitempty"`
+	// id is a unique identifier generated by relui when a buildable task is created. Do not set.
+	Id                   string   `protobuf:"bytes,7,opt,name=id,proto3" json:"id,omitempty"`
 	XXX_NoUnkeyedLiteral struct{} `json:"-"`
 	XXX_unrecognized     []byte   `json:"-"`
 	XXX_sizecache        int32    `json:"-"`
@@ -195,6 +206,13 @@
 	return ""
 }
 
+func (m *BuildableTask) GetId() string {
+	if m != nil {
+		return m.Id
+	}
+	return ""
+}
+
 // LocalStorage is the persisted data of relui. It is used in development mode for saving application state.
 type LocalStorage struct {
 	// workflows are a list of user-created workflows.
@@ -295,31 +313,32 @@
 func init() { proto.RegisterFile("relui.proto", fileDescriptor_6de8859f82adce0a) }
 
 var fileDescriptor_6de8859f82adce0a = []byte{
-	// 411 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x52, 0x4f, 0x6f, 0xd3, 0x30,
-	0x1c, 0xc5, 0xed, 0x56, 0x2d, 0xbf, 0x8c, 0x51, 0x0c, 0x08, 0x8b, 0x3f, 0x52, 0xe9, 0x29, 0xda,
-	0x21, 0xa0, 0xc2, 0x01, 0x38, 0x4c, 0x0a, 0x50, 0x71, 0x18, 0xea, 0x90, 0x93, 0xaa, 0xc7, 0xc8,
-	0x69, 0xdd, 0x2a, 0x8a, 0x17, 0x47, 0xb6, 0xc3, 0x94, 0x4f, 0xc9, 0x57, 0xe0, 0xa3, 0x20, 0xbb,
-	0x0e, 0x1b, 0xd5, 0x4e, 0x79, 0x7e, 0xef, 0xe7, 0xe7, 0xf7, 0x7b, 0x0a, 0x84, 0x8a, 0x8b, 0xb6,
-	0x8c, 0x1b, 0x25, 0x8d, 0xc4, 0x23, 0xf7, 0xd1, 0xd3, 0xdf, 0x08, 0x4e, 0x56, 0x52, 0x55, 0x5b,
-	0x21, 0x6f, 0x30, 0x86, 0xa3, 0x9a, 0x5d, 0x73, 0x82, 0x26, 0x28, 0x0a, 0xa8, 0xc3, 0xf8, 0x02,
-	0x1e, 0x15, 0x6d, 0x29, 0x36, 0xac, 0x10, 0x3c, 0x37, 0x4c, 0x57, 0x9a, 0x0c, 0x26, 0xc3, 0x28,
-	0x9c, 0x3d, 0xdb, 0x3b, 0xe9, 0xf8, 0x4b, 0x2f, 0x67, 0x4c, 0x57, 0xf4, 0xac, 0xb8, 0x7b, 0xd4,
-	0xf8, 0x03, 0x8c, 0x1a, 0xa6, 0xd8, 0xb5, 0x26, 0x43, 0x77, 0xed, 0x55, 0x7f, 0xad, 0x7f, 0x35,
-	0xfe, 0xe9, 0xe4, 0x79, 0x6d, 0x54, 0x47, 0xfd, 0xec, 0x8b, 0x4f, 0x10, 0xde, 0xa1, 0xf1, 0x18,
-	0x86, 0x15, 0xef, 0x7c, 0x2e, 0x0b, 0xf1, 0x53, 0x38, 0xfe, 0xc5, 0x44, 0xcb, 0xc9, 0xc0, 0x71,
-	0xfb, 0xc3, 0xe7, 0xc1, 0x47, 0x34, 0xfd, 0x83, 0xe0, 0xe1, 0x7f, 0x91, 0xee, 0x5d, 0xeb, 0x35,
-	0xc0, 0x86, 0x37, 0xbc, 0xde, 0xe8, 0x5c, 0xd6, 0xde, 0x24, 0xf0, 0xcc, 0x55, 0x8d, 0xcf, 0x61,
-	0xa4, 0x0d, 0x33, 0xad, 0x4d, 0x8d, 0xa2, 0xb3, 0x19, 0xee, 0x53, 0x5b, 0xc3, 0xd4, 0x29, 0xd4,
-	0x4f, 0xe0, 0x37, 0x70, 0xca, 0x94, 0x29, 0xb7, 0x6c, 0x6d, 0xf2, 0x56, 0x09, 0x72, 0xe4, 0xcc,
-	0xc2, 0x9e, 0x5b, 0x2a, 0x81, 0xdf, 0x01, 0xec, 0x4a, 0x93, 0x6b, 0xd9, 0xaa, 0x35, 0x27, 0xc7,
-	0x13, 0x14, 0x85, 0xb3, 0xc7, 0xbd, 0xe5, 0xf7, 0xd2, 0xa4, 0x4e, 0xa0, 0xc1, 0xae, 0x87, 0xf8,
-	0x25, 0x04, 0xb6, 0xec, 0xdc, 0x74, 0x0d, 0x27, 0x23, 0xe7, 0x78, 0x62, 0x89, 0xac, 0x6b, 0xf8,
-	0xf4, 0x02, 0x4e, 0x7f, 0xc8, 0x35, 0x13, 0xa9, 0x91, 0x8a, 0xed, 0x38, 0x8e, 0x21, 0xb8, 0xf1,
-	0x6d, 0x6a, 0x82, 0x5c, 0xcd, 0xe3, 0xc3, 0x9a, 0xe9, 0xed, 0xc8, 0xf4, 0x2d, 0x04, 0xff, 0x1e,
-	0xb5, 0xdd, 0xda, 0xd4, 0xbe, 0xdb, 0x56, 0x09, 0xcb, 0x28, 0xbe, 0xf5, 0xa5, 0x58, 0x78, 0xbe,
-	0x02, 0xb8, 0x5d, 0x1c, 0x3f, 0x87, 0x27, 0x59, 0x92, 0x5e, 0xe6, 0x69, 0x96, 0x64, 0xcb, 0x34,
-	0x5f, 0x2e, 0x2e, 0x17, 0x57, 0xab, 0xc5, 0xf8, 0xc1, 0xa1, 0xf0, 0x95, 0xce, 0x93, 0x6c, 0xfe,
-	0x6d, 0x8c, 0x0e, 0x85, 0x34, 0x4b, 0xa8, 0x15, 0x06, 0xc5, 0xfe, 0x37, 0x7c, 0xff, 0x37, 0x00,
-	0x00, 0xff, 0xff, 0xfa, 0x0a, 0x66, 0x8f, 0x9c, 0x02, 0x00, 0x00,
+	// 426 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x52, 0x4d, 0x8f, 0xd3, 0x30,
+	0x10, 0xc5, 0xe9, 0x6e, 0xd9, 0x4c, 0x96, 0x52, 0x0c, 0x08, 0x8b, 0x0f, 0xa9, 0xf4, 0x54, 0xed,
+	0xa1, 0xa0, 0xc2, 0x01, 0x38, 0xac, 0x54, 0xa0, 0xe2, 0xb0, 0xa8, 0x8b, 0x9c, 0x54, 0x3d, 0x46,
+	0x6e, 0xe3, 0x56, 0x51, 0xbd, 0x71, 0x64, 0x3b, 0xac, 0xf2, 0x6b, 0xf9, 0x0b, 0xfc, 0x04, 0x64,
+	0xd7, 0x66, 0x4b, 0xc5, 0x29, 0xe3, 0xf7, 0xc6, 0x2f, 0xf3, 0xde, 0x18, 0x12, 0xc5, 0x45, 0x53,
+	0x8e, 0x6b, 0x25, 0x8d, 0xc4, 0x5d, 0xf7, 0xd1, 0xc3, 0x5f, 0x08, 0xce, 0x96, 0x52, 0xed, 0x36,
+	0x42, 0xde, 0x62, 0x0c, 0x27, 0x15, 0xbb, 0xe1, 0x04, 0x0d, 0xd0, 0x28, 0xa6, 0xae, 0xc6, 0x97,
+	0xf0, 0x70, 0xd5, 0x94, 0xa2, 0x60, 0x2b, 0xc1, 0x73, 0xc3, 0xf4, 0x4e, 0x93, 0x68, 0xd0, 0x19,
+	0x25, 0x93, 0xa7, 0x7b, 0x25, 0x3d, 0xfe, 0x1c, 0xe8, 0x8c, 0xe9, 0x1d, 0xed, 0xad, 0x0e, 0x8f,
+	0x1a, 0xbf, 0x87, 0x6e, 0xcd, 0x14, 0xbb, 0xd1, 0xa4, 0xe3, 0xae, 0xbd, 0x0c, 0xd7, 0xc2, 0x5f,
+	0xc7, 0x3f, 0x1c, 0x3d, 0xab, 0x8c, 0x6a, 0xa9, 0xef, 0xc5, 0x3d, 0x88, 0xca, 0x82, 0x9c, 0xb8,
+	0x39, 0xa2, 0xb2, 0x78, 0xfe, 0x11, 0x92, 0x83, 0x36, 0xdc, 0x87, 0xce, 0x8e, 0xb7, 0x7e, 0x4e,
+	0x5b, 0xe2, 0x27, 0x70, 0xfa, 0x93, 0x89, 0x86, 0x93, 0xc8, 0x61, 0xfb, 0xc3, 0xa7, 0xe8, 0x03,
+	0x1a, 0xfe, 0x46, 0xf0, 0xe0, 0x9f, 0x11, 0xff, 0x6b, 0xf3, 0x15, 0x40, 0xc1, 0x6b, 0x5e, 0x15,
+	0x3a, 0x97, 0x95, 0x17, 0x89, 0x3d, 0x72, 0x5d, 0xe1, 0x0b, 0xe8, 0x6a, 0xc3, 0x4c, 0x63, 0x5d,
+	0xa0, 0x51, 0x6f, 0x82, 0x83, 0x0b, 0x2b, 0x98, 0x3a, 0x86, 0xfa, 0x0e, 0xfc, 0x1a, 0xce, 0x99,
+	0x32, 0xe5, 0x86, 0xad, 0x4d, 0xde, 0x28, 0xe1, 0x5d, 0x24, 0x01, 0x5b, 0x28, 0x81, 0xdf, 0x02,
+	0x6c, 0x4b, 0x93, 0x6b, 0xd9, 0xa8, 0x35, 0x27, 0xa7, 0x03, 0x34, 0x4a, 0x26, 0x8f, 0x82, 0xe4,
+	0xb7, 0xd2, 0xa4, 0x8e, 0xa0, 0xf1, 0x36, 0x94, 0xf8, 0x05, 0xc4, 0x36, 0xfc, 0xdc, 0xb4, 0x35,
+	0x27, 0x5d, 0xa7, 0x78, 0x66, 0x81, 0xac, 0xad, 0xb9, 0x4f, 0xeb, 0x7e, 0x48, 0x6b, 0x78, 0x09,
+	0xe7, 0xdf, 0xe5, 0x9a, 0x89, 0xd4, 0x48, 0xc5, 0xb6, 0x1c, 0x8f, 0x21, 0xbe, 0xf5, 0x69, 0x6b,
+	0x82, 0xdc, 0x1a, 0xfa, 0xc7, 0x6b, 0xa0, 0x77, 0x2d, 0xc3, 0x37, 0x10, 0xff, 0x1d, 0xc2, 0x66,
+	0x6d, 0x5d, 0xf8, 0xac, 0x1b, 0x25, 0x2c, 0xa2, 0xf8, 0xc6, 0x87, 0x64, 0xcb, 0x8b, 0x25, 0xc0,
+	0x5d, 0x10, 0xf8, 0x19, 0x3c, 0xce, 0xa6, 0xe9, 0x55, 0x9e, 0x66, 0xd3, 0x6c, 0x91, 0xe6, 0x8b,
+	0xf9, 0xd5, 0xfc, 0x7a, 0x39, 0xef, 0xdf, 0x3b, 0x26, 0xbe, 0xd0, 0xd9, 0x34, 0x9b, 0x7d, 0xed,
+	0xa3, 0x63, 0x22, 0xcd, 0xa6, 0xd4, 0x12, 0xd1, 0x6a, 0xff, 0x4c, 0xdf, 0xfd, 0x09, 0x00, 0x00,
+	0xff, 0xff, 0xa2, 0xed, 0x2b, 0x74, 0xbc, 0x02, 0x00, 0x00,
 }
diff --git a/cmd/relui/protos/relui.proto b/cmd/relui/protos/relui.proto
index 6060dfa..7bad5db 100644
--- a/cmd/relui/protos/relui.proto
+++ b/cmd/relui/protos/relui.proto
@@ -16,6 +16,9 @@
 
   // params are parameters provided when creating a workflow.
   map<string, string> params = 3;
+
+  // id is a unique identifier generated by relui when a workflow is created.
+  string id = 4;
 }
 
 message BuildableTask {
@@ -39,6 +42,9 @@
   // task_type is a unique type for a task, such as FetchGerritSource. Types are used by task runners to identify
   // how to execute a task.
   string task_type = 6;
+
+  // id is a unique identifier generated by relui when a buildable task is created.
+  string id = 7;
 }
 
 // LocalStorage is the persisted data of relui. It is used in development mode for saving application state.
diff --git a/cmd/relui/templates/home.html b/cmd/relui/templates/home.html
index c868830..c230dd5 100644
--- a/cmd/relui/templates/home.html
+++ b/cmd/relui/templates/home.html
@@ -19,6 +19,7 @@
         <li class="TaskList-item">
           <span class="TaskList-itemTitle">{{$task.Name}}</span>
           Status: {{$task.Status}}
+          ID: {{$task.Id}}
         </li>
         {{end}}
         <li class="TaskList-item">
diff --git a/cmd/relui/web.go b/cmd/relui/web.go
index 31cba38..4915253 100644
--- a/cmd/relui/web.go
+++ b/cmd/relui/web.go
@@ -17,6 +17,7 @@
 
 	"cloud.google.com/go/pubsub"
 	"github.com/golang/protobuf/proto"
+	"github.com/google/uuid"
 	reluipb "golang.org/x/build/cmd/relui/protos"
 )
 
@@ -108,6 +109,10 @@
 	if wf.GetParams() == nil {
 		wf.Params = map[string]string{}
 	}
+	wf.Id = uuid.New().String()
+	for _, t := range wf.GetBuildableTasks() {
+		t.Id = uuid.New().String()
+	}
 	wf.Params["GitObject"] = ref
 	if err := s.store.AddWorkflow(wf); err != nil {
 		log.Printf("Error adding workflow: s.store.AddWorkflow(%v) = %v", wf, err)
diff --git a/cmd/relui/web_test.go b/cmd/relui/web_test.go
index ab1ae59..7cab6e9 100644
--- a/cmd/relui/web_test.go
+++ b/cmd/relui/web_test.go
@@ -106,7 +106,12 @@
 }
 
 func TestServerCreateWorkflowHandler(t *testing.T) {
-	config := []*reluipb.Workflow{{Name: "test_workflow"}}
+	config := []*reluipb.Workflow{
+		{
+			Name:           "test_workflow",
+			BuildableTasks: []*reluipb.BuildableTask{{Name: "test_task"}},
+		},
+	}
 	cases := []struct {
 		desc        string
 		params      url.Values
@@ -157,6 +162,12 @@
 			if diff := cmp.Diff(c.wantParams, s.store.Workflows()[0].GetParams()); diff != "" {
 				t.Errorf("s.Store.Workflows()[0].Params() mismatch (-want, +got):\n%s", diff)
 			}
+			if s.store.Workflows()[0].GetId() == "" {
+				t.Errorf("s.Store.Workflows[0].GetId() = %q, wanted not empty", s.store.Workflows()[0].GetId())
+			}
+			if s.store.Workflows()[0].GetBuildableTasks()[0].GetId() == "" {
+				t.Errorf("s.Store.Workflows[0].GetBuildableTasks()[0].GetId() = %q, wanted not empty", s.store.Workflows()[0].GetId())
+			}
 		})
 	}
 }
diff --git a/go.mod b/go.mod
index 8674d2c..9f0aba7 100644
--- a/go.mod
+++ b/go.mod
@@ -20,6 +20,7 @@
 	github.com/google/go-cmp v0.4.0
 	github.com/google/go-github v17.0.0+incompatible
 	github.com/google/go-querystring v1.0.0 // indirect
+	github.com/google/uuid v1.1.2
 	github.com/googleapis/gax-go/v2 v2.0.5
 	github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7
 	github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1
diff --git a/go.sum b/go.sum
index aa15670..afdca40 100644
--- a/go.sum
+++ b/go.sum
@@ -112,6 +112,8 @@
 github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/gax-go/v2 v2.0.4 h1:hU4mGcQI4DaAYW+IbTun+2qEZVFxK0ySjQLTbS0VQKc=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=