[Task Driver] Add ability to add a link to a step's UI

Bug: skia:10477
Change-Id: Ib473c49e30719169455a615940e20d87a8189522
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/302897
Commit-Queue: Ravi Mistry <rmistry@google.com>
Reviewed-by: Eric Boren <borenet@google.com>
diff --git a/infra-sk/modules/task-driver-sk/task-driver-sk.js b/infra-sk/modules/task-driver-sk/task-driver-sk.js
index 7d2d797..f93ae7f 100644
--- a/infra-sk/modules/task-driver-sk/task-driver-sk.js
+++ b/infra-sk/modules/task-driver-sk/task-driver-sk.js
@@ -8,6 +8,7 @@
  *
  */
 import { define } from 'elements-sk/define'
+import { escapeAndLinkify } from '../linkify'
 import { html, render } from 'lit-html'
 import { localeTime, strDuration } from 'common-sk/modules/human'
 import { jsonOrThrow } from 'common-sk/modules/jsonOrThrow'
@@ -34,6 +35,8 @@
       return propLine("HTTP Request", d.data.url);
     case "httpResponse":
       return propLine("HTTP Response", d.data.status);
+    case "text":
+      return propLine(d.data.label, escapeAndLinkify(d.data.value));
     case "log":
       return propLine("Log (" + d.data.name + ")", html`
           <a href="${ele._logLink(s.id, d.data.id)}" target="_blank">${d.data.name}</a>
diff --git a/task_driver/go/db/db.go b/task_driver/go/db/db.go
index 210d8b1..6932068 100644
--- a/task_driver/go/db/db.go
+++ b/task_driver/go/db/db.go
@@ -15,6 +15,7 @@
 	gob.Register(map[string]interface{}{})
 	gob.Register([]interface{}{})
 	gob.Register(td.LogData{})
+	gob.Register(td.TextData{})
 	gob.Register(td.ExecData{})
 	gob.Register(td.HttpRequestData{})
 	gob.Register(td.HttpResponseData{})
diff --git a/task_driver/go/td/message.go b/task_driver/go/td/message.go
index e731c37..1542900 100644
--- a/task_driver/go/td/message.go
+++ b/task_driver/go/td/message.go
@@ -17,6 +17,7 @@
 	MSG_TYPE_STEP_EXCEPTION MessageType = "STEP_EXCEPTION"
 
 	DATA_TYPE_LOG           DataType = "log"
+	DATA_TYPE_TEXT          DataType = "text"
 	DATA_TYPE_COMMAND       DataType = "command"
 	DATA_TYPE_HTTP_REQUEST  DataType = "httpRequest"
 	DATA_TYPE_HTTP_RESPONSE DataType = "httpResponse"
@@ -115,6 +116,7 @@
 		}
 		switch m.DataType {
 		case DATA_TYPE_LOG:
+		case DATA_TYPE_TEXT:
 		case DATA_TYPE_COMMAND:
 		case DATA_TYPE_HTTP_REQUEST:
 		case DATA_TYPE_HTTP_RESPONSE:
diff --git a/task_driver/go/td/message_test.go b/task_driver/go/td/message_test.go
index 19e28e6..e75df2f 100644
--- a/task_driver/go/td/message_test.go
+++ b/task_driver/go/td/message_test.go
@@ -60,7 +60,21 @@
 			Type:      MSG_TYPE_STEP_FINISHED,
 		}
 	}
-	msgStepData := func() *Message {
+	msgTextStepData := func() *Message {
+		return &Message{
+			Index:     int(atomic.AddInt32(&msgIndex, 1)),
+			StepId:    "fake-step-id",
+			TaskId:    "fake-task-id",
+			Timestamp: now,
+			Type:      MSG_TYPE_STEP_DATA,
+			Data: &TextData{
+				Value: "http://www.google.com",
+				Label: "Google homepage",
+			},
+			DataType: DATA_TYPE_TEXT,
+		}
+	}
+	msgCommandStepData := func() *Message {
 		return &Message{
 			Index:     int(atomic.AddInt32(&msgIndex, 1)),
 			StepId:    "fake-step-id",
@@ -104,7 +118,8 @@
 	nonRootStarted.Step.Parent = STEP_ID_ROOT
 	checkValid(nonRootStarted)
 	checkValid(msgStepFinished())
-	checkValid(msgStepData())
+	checkValid(msgTextStepData())
+	checkValid(msgCommandStepData())
 	checkValid(msgStepFailed())
 	checkValid(msgStepException())
 
@@ -161,17 +176,17 @@
 		return m
 	}, fmt.Sprintf("StepId is required for %s", MSG_TYPE_STEP_FINISHED))
 	checkNotValid(func() *Message {
-		m := msgStepData()
+		m := msgCommandStepData()
 		m.StepId = ""
 		return m
 	}, fmt.Sprintf("StepId is required for %s", MSG_TYPE_STEP_DATA))
 	checkNotValid(func() *Message {
-		m := msgStepData()
+		m := msgCommandStepData()
 		m.Data = nil
 		return m
 	}, fmt.Sprintf("Data is required for %s", MSG_TYPE_STEP_DATA))
 	checkNotValid(func() *Message {
-		m := msgStepData()
+		m := msgCommandStepData()
 		m.DataType = "fake"
 		return m
 	}, "Invalid DataType \"fake\"")
diff --git a/task_driver/go/td/step.go b/task_driver/go/td/step.go
index 0903692..8b96175 100644
--- a/task_driver/go/td/step.go
+++ b/task_driver/go/td/step.go
@@ -177,6 +177,16 @@
 	getCtx(ctx).run.AddStepData(props.Id, typ, d)
 }
 
+// StepText displays the provided text with the label in the Step's UI. The
+// text will be escaped and URLs in it will be linkified.
+func StepText(ctx context.Context, label, value string) {
+	d := &TextData{
+		Label: label,
+		Value: value,
+	}
+	StepData(ctx, DATA_TYPE_TEXT, d)
+}
+
 // Do is a convenience function which runs the given function as a Step. It
 // handles creation of the sub-step and calling EndStep() for you.
 func Do(ctx context.Context, props *StepProperties, fn func(context.Context) error) error {
@@ -361,6 +371,13 @@
 	return fs.file.Name()
 }
 
+// TextData is extra Step data for displaying a text in a step. The provided
+// text will be escaped and URLs in it will be linkified.
+type TextData struct {
+	Label string `json:"label"`
+	Value string `json:"value"`
+}
+
 // ExecData is extra Step data generated when executing commands through the
 // exec package.
 type ExecData struct {