Show info about flags doesn't break Bazel owned/co-owned projects (#939)

* Show info about flags doesn't break Bazel owned/co-owned projects

1. Print a summary of flags that doesn't break Bazel team owned/co-owned
projects

2. Mark Bazel owned/co-owned pipelines as red

Change-Id: Ie0225e5cc654604ff3bf82d1de88470a769bd753

* Address reviewer comments

Change-Id: Id23c77ff7fb600366c58eb11d26fdc466fd9241d

* refactor

Change-Id: I7a04ed0fa88ebbb48aeaaad84250c038efa718b1
diff --git a/buildkite/aggregate_incompatible_flags_test_result.py b/buildkite/aggregate_incompatible_flags_test_result.py
index bb55d8b..5640673 100755
--- a/buildkite/aggregate_incompatible_flags_test_result.py
+++ b/buildkite/aggregate_incompatible_flags_test_result.py
@@ -195,15 +195,38 @@
     return f'<a href="{link}" target="_blank">{content}</a>'
 
 
+# Check if any of the given jobs needs to be migrated by the Bazel team
+def needs_bazel_team_migrate(jobs):
+    for job in jobs:
+        pipeline, _ = get_pipeline_and_platform(job)
+        if bazelci.DOWNSTREAM_PROJECTS[pipeline].get("owned_by_bazel"):
+            return True
+    return False
+
+
 def print_flags_ready_to_flip(failed_jobs_per_flag, details_per_flag):
-    info_text = ["#### The following flags didn't break any passing jobs"]
+    info_text1 = ["#### The following flags didn't break any passing projects"]
     for flag in sorted(list(details_per_flag.keys())):
         if flag not in failed_jobs_per_flag:
-            github_url = details_per_flag[flag].issue_url
-            info_text.append(f"* **{flag}** " + get_html_link_text(":github:", github_url))
-    if len(info_text) == 1:
-        return
-    print_info("flags_ready_to_flip", "success", info_text)
+            html_link_text = get_html_link_text(":github:", details_per_flag[flag].issue_url)
+            info_text1.append(f"* **{flag}** {html_link_text}")
+
+    if len(info_text1) == 1:
+        info_text1 = []
+
+    info_text2.append("#### The following flags didn't break any passing Bazel team owned/co-owned projects"):
+    for flag, jobs in failed_jobs_per_flag.items():
+        if not needs_bazel_team_migrate(jobs):
+            failed_cnt = len(jobs)
+            s1 = "" if failed_cnt == 1 else "s"
+            s2 = "s" if failed_cnt == 1 else ""
+            html_link_text = get_html_link_text(":github:", details_per_flag[flag].issue_url)
+            info_text2.append(f"* **{flag}** {html_link_text}  ({failed_cnt} other job{s1} need{s2} migration)")
+
+    if len(info_text2) == 1:
+        info_text2 = []
+
+    print_info("flags_ready_to_flip", "success", info_text1 + info_text2)
 
 
 def print_already_fail_jobs(already_failing_jobs):
@@ -264,16 +287,31 @@
         if jobs:
             github_url = details_per_flag[flag].issue_url
             info_text = [f"* **{flag}** " + get_html_link_text(":github:", github_url)]
-            info_text += merge_and_format_jobs(jobs.values(), "  - **{}**: {}")
+            jobs_per_pipeline = merge_jobs(jobs.values())
+            for pipeline, platforms in jobs_per_pipeline.items():
+                color = "red" if bazelci.DOWNSTREAM_PROJECTS[pipeline].get("owned_by_bazel") else "black"
+                platforms_text = ", ".join(platforms)
+                info_text.append(f"  <li><strong style=\"color: {color}\">{pipeline}</strong>: {platforms_text}</li>")
             # Use flag as the context so that each flag gets a different info box.
             print_info(flag, "error", info_text)
             printed_flag_boxes = True
     if not printed_flag_boxes:
         return
-    info_text = ["#### Downstream projects need to migrate for the following flags:"]
+    info_text = [
+        "#### Downstream projects need to migrate for the following flags:",
+        "    Projects with <strong style=\"color: red;\">red title</strong> need to be migrated by the Bazel team.",
+    ]
     print_info("flags_need_to_migrate", "error", info_text)
 
 
+def merge_jobs(jobs):
+    jobs_per_pipeline = collections.defaultdict(list)
+    for job in sorted(jobs, key=lambda s: s["name"].lower()):
+        pipeline, platform = get_pipeline_and_platform(job)
+        jobs_per_pipeline[pipeline].append(get_html_link_text(platform, job["web_url"]))
+    return jobs_per_pipeline
+
+
 def merge_and_format_jobs(jobs, line_pattern):
     # Merges all jobs for a single pipeline into one line.
     # Example:
@@ -282,13 +320,7 @@
     #   pipeline (platform3)
     # with line_pattern ">> {}: {}" becomes
     #   >> pipeline: platform1, platform2, platform3
-    jobs = list(jobs)
-    jobs.sort(key=lambda s: s["name"].lower())
-    jobs_per_pipeline = collections.defaultdict(list)
-    for job in jobs:
-        pipeline, platform = get_pipeline_and_platform(job)
-        jobs_per_pipeline[pipeline].append(get_html_link_text(platform, job["web_url"]))
-
+    jobs_per_pipeline = merge_jobs(jobs)
     return [
         line_pattern.format(pipeline, ", ".join(platforms))
         for pipeline, platforms in jobs_per_pipeline.items()
diff --git a/buildkite/bazelci.py b/buildkite/bazelci.py
index 98fb253..1eade27 100755
--- a/buildkite/bazelci.py
+++ b/buildkite/bazelci.py
@@ -128,6 +128,7 @@
         "git_repository": "https://github.com/bazelbuild/bazel-skylib.git",
         "http_config": "https://raw.githubusercontent.com/bazelbuild/bazel-skylib/master/.bazelci/presubmit.yml",
         "pipeline_slug": "bazel-skylib",
+        "owned_by_bazel": True,
     },
     "Bazel toolchains": {
         "git_repository": "https://github.com/bazelbuild/bazel-toolchains.git",
@@ -213,16 +214,19 @@
         "git_repository": "https://github.com/google/protobuf.git",
         "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/protobuf-postsubmit.yml",
         "pipeline_slug": "protobuf",
+        "owned_by_bazel": True,
     },
     "Skydoc": {
         "git_repository": "https://github.com/bazelbuild/skydoc.git",
         "http_config": "https://raw.githubusercontent.com/bazelbuild/skydoc/master/.bazelci/presubmit.yml",
         "pipeline_slug": "skydoc",
+        "owned_by_bazel": True,
     },
     "Subpar": {
         "git_repository": "https://github.com/google/subpar.git",
         "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/subpar-postsubmit.yml",
         "pipeline_slug": "subpar",
+        "owned_by_bazel": True,
     },
     "TensorFlow": {
         "git_repository": "https://github.com/tensorflow/tensorflow.git",
@@ -258,11 +262,13 @@
         "git_repository": "https://github.com/bazelbuild/rules_cc.git",
         "http_config": "https://raw.githubusercontent.com/bazelbuild/rules_cc/master/.bazelci/presubmit.yml",
         "pipeline_slug": "rules-cc",
+        "owned_by_bazel": True,
     },
     "rules_closure": {
         "git_repository": "https://github.com/bazelbuild/rules_closure.git",
         "http_config": "https://raw.githubusercontent.com/bazelbuild/rules_closure/master/.bazelci/presubmit.yml",
         "pipeline_slug": "rules-closure-closure-compiler",
+        "owned_by_bazel": True,
     },
     "rules_d": {
         "git_repository": "https://github.com/bazelbuild/rules_d.git",
@@ -278,6 +284,7 @@
         "git_repository": "https://github.com/bazelbuild/rules_foreign_cc.git",
         "http_config": "https://raw.githubusercontent.com/bazelbuild/rules_foreign_cc/master/.bazelci/config.yaml",
         "pipeline_slug": "rules-foreign-cc",
+        "owned_by_bazel": True,
     },
     "rules_go": {
         "git_repository": "https://github.com/bazelbuild/rules_go.git",
@@ -308,11 +315,13 @@
         "git_repository": "https://github.com/bazelbuild/rules_jvm_external.git",
         "http_config": "https://raw.githubusercontent.com/bazelbuild/rules_jvm_external/master/.bazelci/presubmit.yml",
         "pipeline_slug": "rules-jvm-external",
+        "owned_by_bazel": True,
     },
     "rules_jvm_external - examples": {
         "git_repository": "https://github.com/bazelbuild/rules_jvm_external.git",
         "http_config": "https://raw.githubusercontent.com/bazelbuild/rules_jvm_external/master/.bazelci/examples.yml",
         "pipeline_slug": "rules-jvm-external-examples",
+        "owned_by_bazel": True,
     },
     "rules_k8s": {
         "git_repository": "https://github.com/bazelbuild/rules_k8s.git",
@@ -338,11 +347,13 @@
         "git_repository": "https://github.com/bazelbuild/rules_proto.git",
         "http_config": "https://raw.githubusercontent.com/bazelbuild/rules_proto/master/.bazelci/presubmit.yml",
         "pipeline_slug": "rules-proto",
+        "owned_by_bazel": True,
     },
     "rules_python": {
         "git_repository": "https://github.com/bazelbuild/rules_python.git",
         "http_config": "https://raw.githubusercontent.com/bazelbuild/rules_python/master/.bazelci/presubmit.yml",
         "pipeline_slug": "rules-python-python",
+        "owned_by_bazel": True,
     },
     "rules_rust": {
         "git_repository": "https://github.com/bazelbuild/rules_rust.git",