dashboard/app: report bisection results to external reporting

Update #501
diff --git a/dashboard/app/bisect_test.go b/dashboard/app/bisect_test.go
index f944cd5..0f1002a 100644
--- a/dashboard/app/bisect_test.go
+++ b/dashboard/app/bisect_test.go
@@ -500,3 +500,45 @@
 			bisectLogLink, bisectCrashReportLink, bisectCrashLogLink))
 	}
 }
+
+func TestBisectCauseExternal(t *testing.T) {
+	c := NewCtx(t)
+	defer c.Close()
+
+	build := testBuild(1)
+	c.client.UploadBuild(build)
+	crash := testCrashWithRepro(build, 1)
+	c.client.ReportCrash(crash)
+	rep := c.client.pollBug()
+
+	pollResp, err := c.client.JobPoll([]string{build.Manager})
+	c.expectOK(err)
+	jobID := pollResp.ID
+	done := &dashapi.JobDoneReq{
+		ID:    jobID,
+		Build: *build,
+		Log:   []byte("bisect log"),
+		Commits: []dashapi.Commit{
+			{
+				Hash:       "111111111111111111111111",
+				Title:      "kernel: break build",
+				Author:     "hacker@kernel.org",
+				AuthorName: "Hacker Kernelov",
+				CC:         []string{"reviewer1@kernel.org", "reviewer2@kernel.org"},
+				Date:       time.Date(2000, 2, 9, 4, 5, 6, 7, time.UTC),
+			},
+		},
+	}
+	done.Build.ID = jobID
+	c.expectOK(c.client2.JobDone(done))
+
+	resp, _ := c.client.ReportingPollBugs("test")
+	c.expectEQ(len(resp.Reports), 1)
+	// Still reported because we did not ack.
+	bisect := c.client.pollBug()
+	// pollBug acks, must not be reported after that.
+	c.client.pollBugs(0)
+
+	c.expectEQ(bisect.Type, dashapi.ReportBisectCause)
+	c.expectEQ(bisect.Title, rep.Title)
+}
diff --git a/dashboard/app/reporting_external.go b/dashboard/app/reporting_external.go
index 4dd85e8..846f1f3 100644
--- a/dashboard/app/reporting_external.go
+++ b/dashboard/app/reporting_external.go
@@ -34,6 +34,11 @@
 	resp := &dashapi.PollBugsResponse{
 		Reports: reports,
 	}
+	jobs, err := pollCompletedJobs(c, req.Type)
+	if err != nil {
+		log.Errorf(c, "failed to poll jobs: %v", err)
+	}
+	resp.Reports = append(resp.Reports, jobs...)
 	return resp, nil
 }
 
@@ -70,6 +75,18 @@
 	if err := json.Unmarshal(payload, req); err != nil {
 		return nil, fmt.Errorf("failed to unmarshal request: %v", err)
 	}
+	if req.JobID != "" {
+		resp := &dashapi.BugUpdateReply{
+			OK:    true,
+			Error: false,
+		}
+		if err := jobReported(c, req.JobID); err != nil {
+			log.Errorf(c, "failed to mark job reported: %v", err)
+			resp.Text = err.Error()
+			resp.Error = true
+		}
+		return resp, nil
+	}
 	ok, reason, err := incomingCommand(c, req)
 	return &dashapi.BugUpdateReply{
 		OK:    ok,
diff --git a/dashboard/app/util_test.go b/dashboard/app/util_test.go
index d61fa0c..40ab3f1 100644
--- a/dashboard/app/util_test.go
+++ b/dashboard/app/util_test.go
@@ -302,6 +302,7 @@
 		}
 		reply, _ := client.ReportingUpdate(&dashapi.BugUpdate{
 			ID:         rep.ID,
+			JobID:      rep.JobID,
 			Status:     dashapi.BugStatusOpen,
 			ReproLevel: reproLevel,
 			CrashID:    rep.CrashID,
diff --git a/dashboard/dashapi/dashapi.go b/dashboard/dashapi/dashapi.go
index 006ed1c..24f2a87 100644
--- a/dashboard/dashapi/dashapi.go
+++ b/dashboard/dashapi/dashapi.go
@@ -332,7 +332,8 @@
 }
 
 type BugUpdate struct {
-	ID           string
+	ID           string // copied from BugReport
+	JobID        string // copied from BugReport
 	ExtID        string
 	Link         string
 	Status       BugStatus