[buildbucket] Add url_title_fn

Add url_title_fn parameter to schedule() and run() that allows customizing
build URL links.

Recipe-Nontrivial-Roll: build
Recipe-Nontrivial-Roll: build_limited_scripts_slave
Recipe-Nontrivial-Roll: chromiumos
Change-Id: I60c98a0730e2fddd5a002dbe2aaa078bb22ff4b6
Reviewed-on: https://chromium-review.googlesource.com/c/infra/luci/recipes-py/+/1565013
Commit-Queue: Nodir Turakulov <nodir@chromium.org>
Reviewed-by: Andrew Lamb <andrewlamb@chromium.org>
Reviewed-by: Andrii Shyshkalov <tandrii@chromium.org>
Auto-Submit: Nodir Turakulov <nodir@chromium.org>
diff --git a/README.recipes.md b/README.recipes.md
index fd86cbd..1fa2ad8 100644
--- a/README.recipes.md
+++ b/README.recipes.md
@@ -238,7 +238,7 @@
 
 A module for interacting with buildbucket.
 
-&emsp; **@property**<br>&mdash; **def [bucket\_v1](/recipe_modules/buildbucket/api.py#629)(self):**
+&emsp; **@property**<br>&mdash; **def [bucket\_v1](/recipe_modules/buildbucket/api.py#636)(self):**
 
 Returns bucket name in v1 format.
 
@@ -261,11 +261,11 @@
 the rules described in the .proto files.
 If the current build is not a buildbucket build, returned `build.id` is 0.
 
-&emsp; **@property**<br>&mdash; **def [build\_id](/recipe_modules/buildbucket/api.py#645)(self):**
+&emsp; **@property**<br>&mdash; **def [build\_id](/recipe_modules/buildbucket/api.py#652)(self):**
 
 DEPRECATED, use build.id instead.
 
-&emsp; **@property**<br>&mdash; **def [build\_input](/recipe_modules/buildbucket/api.py#650)(self):**
+&emsp; **@property**<br>&mdash; **def [build\_input](/recipe_modules/buildbucket/api.py#657)(self):**
 
 DEPRECATED, use build.input instead.
 
@@ -273,7 +273,7 @@
 
 Returns url to a build. Defaults to current build.
 
-&emsp; **@property**<br>&mdash; **def [builder\_id](/recipe_modules/buildbucket/api.py#655)(self):**
+&emsp; **@property**<br>&mdash; **def [builder\_id](/recipe_modules/buildbucket/api.py#662)(self):**
 
 Deprecated. Use build.builder instead.
 
@@ -281,9 +281,9 @@
 
 Returns builder name. Shortcut for `.build.builder.builder`.
 
-&mdash; **def [cancel\_build](/recipe_modules/buildbucket/api.py#516)(self, build_id, \*\*kwargs):**
+&mdash; **def [cancel\_build](/recipe_modules/buildbucket/api.py#523)(self, build_id, \*\*kwargs):**
 
-&mdash; **def [collect\_build](/recipe_modules/buildbucket/api.py#522)(self, build_id, mirror_status=False, \*\*kwargs):**
+&mdash; **def [collect\_build](/recipe_modules/buildbucket/api.py#529)(self, build_id, mirror_status=False, \*\*kwargs):**
 
 Shorthand for `collect_builds` below, but for a single build only.
 
@@ -295,7 +295,7 @@
   [Build](https://chromium.googlesource.com/infra/luci/luci-go/+/master/buildbucket/proto/build.proto).
   for the ended build.
 
-&mdash; **def [collect\_builds](/recipe_modules/buildbucket/api.py#542)(self, build_ids, interval=None, timeout=None, step_name=None):**
+&mdash; **def [collect\_builds](/recipe_modules/buildbucket/api.py#549)(self, build_ids, interval=None, timeout=None, step_name=None):**
 
 Waits for a set of builds to end and returns their details.
 
@@ -311,7 +311,7 @@
   [Build](https://chromium.googlesource.com/infra/luci/luci-go/+/master/buildbucket/proto/build.proto)
   for all specified builds.
 
-&mdash; **def [get\_build](/recipe_modules/buildbucket/api.py#519)(self, build_id, \*\*kwargs):**
+&mdash; **def [get\_build](/recipe_modules/buildbucket/api.py#526)(self, build_id, \*\*kwargs):**
 
 &emsp; **@property**<br>&mdash; **def [gitiles\_commit](/recipe_modules/buildbucket/api.py#135)(self):**
 
@@ -329,11 +329,11 @@
 Returns True if the build is critical. Build defaults to the current one.
     
 
-&emsp; **@property**<br>&mdash; **def [properties](/recipe_modules/buildbucket/api.py#640)(self):**
+&emsp; **@property**<br>&mdash; **def [properties](/recipe_modules/buildbucket/api.py#647)(self):**
 
 DEPRECATED, use build attribute instead.
 
-&mdash; **def [put](/recipe_modules/buildbucket/api.py#481)(self, builds, \*\*kwargs):**
+&mdash; **def [put](/recipe_modules/buildbucket/api.py#488)(self, builds, \*\*kwargs):**
 
 Puts a batch of builds.
 
@@ -357,7 +357,7 @@
   A step that as its `.stdout` property contains the response object as
   returned by buildbucket.
 
-&mdash; **def [run](/recipe_modules/buildbucket/api.py#238)(self, schedule_build_requests, collect_interval=None, timeout=None, step_name=None):**
+&mdash; **def [run](/recipe_modules/buildbucket/api.py#238)(self, schedule_build_requests, collect_interval=None, timeout=None, url_title_fn=None, step_name=None):**
 
 Runs builds and returns results.
 
@@ -369,14 +369,10 @@
   [Builds](https://chromium.googlesource.com/infra/luci/luci-go/+/master/buildbucket/proto/build.proto)
   in the same order as schedule_build_requests.
 
-&mdash; **def [schedule](/recipe_modules/buildbucket/api.py#391)(self, schedule_build_requests, step_name=None):**
+&mdash; **def [schedule](/recipe_modules/buildbucket/api.py#393)(self, schedule_build_requests, url_title_fn=None, step_name=None):**
 
 Schedules a batch of builds.
 
-`schedule_build_requests` must be a list of
-`buildbucket.v2.ScheduleBuildRequest` protobuf messages.
-Create one by calling `schedule_request` method.
-
 Example:
 ```python
     req = api.buildbucket.schedule_request(builder='linux')
@@ -388,6 +384,15 @@
     api.cq.record_triggered_builds(*api.buildbucket.schedule([req1, req2]))
 ```
 
+Args:
+
+*   schedule_build_requests: a list of `buildbucket.v2.ScheduleBuildRequest`
+    protobuf messages. Create one by calling `schedule_request` method.
+*   url_title_fn: a function (build_pb2.Build) -> (str) that returns a title
+    of build link for the step. Defaults to build id.
+    If returns None, the link is not emitted.
+*   step_name: step name/
+
 Returns:
   A list of
   [`Build`](https://chromium.googlesource.com/infra/luci/luci-go/+/master/buildbucket/proto/build.proto)
@@ -396,7 +401,7 @@
 Raises:
   `InfraFailure` if any of the requests fail.
 
-&mdash; **def [schedule\_request](/recipe_modules/buildbucket/api.py#260)(self, builder, project=None, bucket=None, properties=None, experimental=None, gitiles_commit=None, gerrit_changes=None, tags=None, inherit_buildsets=True, dimensions=None, priority=None, critical=None):**
+&mdash; **def [schedule\_request](/recipe_modules/buildbucket/api.py#262)(self, builder, project=None, bucket=None, properties=None, experimental=None, gitiles_commit=None, gerrit_changes=None, tags=None, inherit_buildsets=True, dimensions=None, priority=None, critical=None):**
 
 Creates a new `ScheduleBuildRequest` message with reasonable defaults.
 
diff --git a/recipe_modules/buildbucket/api.py b/recipe_modules/buildbucket/api.py
index f84b00b..f11140b 100644
--- a/recipe_modules/buildbucket/api.py
+++ b/recipe_modules/buildbucket/api.py
@@ -237,7 +237,7 @@
 
   def run(
       self, schedule_build_requests, collect_interval=None, timeout=None,
-      step_name=None):
+      url_title_fn=None, step_name=None):
     """Runs builds and returns results.
 
     A shortcut for schedule() and collect_builds().
@@ -249,7 +249,9 @@
       in the same order as schedule_build_requests.
     """
     with self.m.step.nest(step_name or 'buildbucket.run'):
-      builds = self.schedule(schedule_build_requests, step_name='schedule')
+      builds = self.schedule(
+          schedule_build_requests, step_name='schedule',
+           url_title_fn=url_title_fn)
       build_dict = self.collect_builds(
           [b.id for b in builds],
           interval=collect_interval,
@@ -388,13 +390,10 @@
 
     return req
 
-  def schedule(self, schedule_build_requests, step_name=None):
+  def schedule(
+      self, schedule_build_requests, url_title_fn=None, step_name=None):
     """Schedules a batch of builds.
 
-    `schedule_build_requests` must be a list of
-    `buildbucket.v2.ScheduleBuildRequest` protobuf messages.
-    Create one by calling `schedule_request` method.
-
     Example:
     ```python
         req = api.buildbucket.schedule_request(builder='linux')
@@ -406,6 +405,15 @@
         api.cq.record_triggered_builds(*api.buildbucket.schedule([req1, req2]))
     ```
 
+    Args:
+
+    *   schedule_build_requests: a list of `buildbucket.v2.ScheduleBuildRequest`
+        protobuf messages. Create one by calling `schedule_request` method.
+    *   url_title_fn: a function (build_pb2.Build) -> (str) that returns a title
+        of build link for the step. Defaults to build id.
+        If returns None, the link is not emitted.
+    *   step_name: name for this step.
+
     Returns:
       A list of
       [`Build`](https://chromium.googlesource.com/infra/luci/luci-go/+/master/buildbucket/proto/build.proto)
@@ -417,6 +425,7 @@
     assert isinstance(schedule_build_requests, list), schedule_build_requests
     for r in schedule_build_requests:
       assert isinstance(r, rpc_pb2.ScheduleBuildRequest), r
+    url_title_fn = url_title_fn or (lambda b: b.id)
 
     batch_req = rpc_pb2.BatchRequest(
         requests=[dict(schedule_build=r) for r in schedule_build_requests]
@@ -466,12 +475,10 @@
               '',  # Blank line.
           ])
         else:
-          b = r.schedule_build
-          build_url = self.build_url(build_id=b.id)
-          build_title = '%s/%s/%s/%d' % (
-              b.builder.project, b.builder.bucket, b.builder.builder,
-              b.number or b.id)
-          pres.links[build_title] = build_url
+          build_title = url_title_fn(r.schedule_build)
+          if build_title is not None:
+            pres.links[str(build_title)] = self.build_url(
+                build_id=r.schedule_build.id)
 
       pres.step_text = '<br>'.join(step_text)
 
diff --git a/recipe_modules/buildbucket/tests/schedule.expected/basic.json b/recipe_modules/buildbucket/tests/schedule.expected/basic.json
index 3008574..a06a1fd 100644
--- a/recipe_modules/buildbucket/tests/schedule.expected/basic.json
+++ b/recipe_modules/buildbucket/tests/schedule.expected/basic.json
@@ -64,7 +64,7 @@
       "@@@STEP_LOG_LINE@request@  ]@@@",
       "@@@STEP_LOG_LINE@request@}@@@",
       "@@@STEP_LOG_END@request@@@",
-      "@@@STEP_LINK@chromium/try/linux/8922054662172514000@https://cr-buildbucket.appspot.com/build/8922054662172514000@@@"
+      "@@@STEP_LINK@8922054662172514000@https://cr-buildbucket.appspot.com/build/8922054662172514000@@@"
     ]
   },
   {
@@ -137,7 +137,7 @@
       "@@@STEP_LOG_LINE@request@  ]@@@",
       "@@@STEP_LOG_LINE@request@}@@@",
       "@@@STEP_LOG_END@request@@@",
-      "@@@STEP_LINK@chromium/try/linux/8922054662172514001@https://cr-buildbucket.appspot.com/build/8922054662172514001@@@"
+      "@@@STEP_LINK@8922054662172514001@https://cr-buildbucket.appspot.com/build/8922054662172514001@@@"
     ]
   },
   {
diff --git a/recipe_modules/buildbucket/tests/schedule.expected/critical.json b/recipe_modules/buildbucket/tests/schedule.expected/critical.json
index 70df31d..0654813 100644
--- a/recipe_modules/buildbucket/tests/schedule.expected/critical.json
+++ b/recipe_modules/buildbucket/tests/schedule.expected/critical.json
@@ -65,7 +65,7 @@
       "@@@STEP_LOG_LINE@request@  ]@@@",
       "@@@STEP_LOG_LINE@request@}@@@",
       "@@@STEP_LOG_END@request@@@",
-      "@@@STEP_LINK@chromium/try/linux/8922054662172514000@https://cr-buildbucket.appspot.com/build/8922054662172514000@@@"
+      "@@@STEP_LINK@8922054662172514000@https://cr-buildbucket.appspot.com/build/8922054662172514000@@@"
     ]
   },
   {
@@ -139,7 +139,7 @@
       "@@@STEP_LOG_LINE@request@  ]@@@",
       "@@@STEP_LOG_LINE@request@}@@@",
       "@@@STEP_LOG_END@request@@@",
-      "@@@STEP_LINK@chromium/try/linux/8922054662172514001@https://cr-buildbucket.appspot.com/build/8922054662172514001@@@"
+      "@@@STEP_LINK@8922054662172514001@https://cr-buildbucket.appspot.com/build/8922054662172514001@@@"
     ]
   },
   {
diff --git a/recipe_modules/buildbucket/tests/schedule.expected/dimensions.json b/recipe_modules/buildbucket/tests/schedule.expected/dimensions.json
index 1e2f475..de83f35 100644
--- a/recipe_modules/buildbucket/tests/schedule.expected/dimensions.json
+++ b/recipe_modules/buildbucket/tests/schedule.expected/dimensions.json
@@ -70,7 +70,7 @@
       "@@@STEP_LOG_LINE@request@  ]@@@",
       "@@@STEP_LOG_LINE@request@}@@@",
       "@@@STEP_LOG_END@request@@@",
-      "@@@STEP_LINK@chromium/try/linux/8922054662172514000@https://cr-buildbucket.appspot.com/build/8922054662172514000@@@"
+      "@@@STEP_LINK@8922054662172514000@https://cr-buildbucket.appspot.com/build/8922054662172514000@@@"
     ]
   },
   {
@@ -149,7 +149,7 @@
       "@@@STEP_LOG_LINE@request@  ]@@@",
       "@@@STEP_LOG_LINE@request@}@@@",
       "@@@STEP_LOG_END@request@@@",
-      "@@@STEP_LINK@chromium/try/linux/8922054662172514001@https://cr-buildbucket.appspot.com/build/8922054662172514001@@@"
+      "@@@STEP_LINK@8922054662172514001@https://cr-buildbucket.appspot.com/build/8922054662172514001@@@"
     ]
   },
   {
diff --git a/recipe_modules/buildbucket/tests/schedule.expected/error.json b/recipe_modules/buildbucket/tests/schedule.expected/error.json
index 75c253e..ac9c102 100644
--- a/recipe_modules/buildbucket/tests/schedule.expected/error.json
+++ b/recipe_modules/buildbucket/tests/schedule.expected/error.json
@@ -133,7 +133,7 @@
       "@@@STEP_LOG_LINE@request@  ]@@@",
       "@@@STEP_LOG_LINE@request@}@@@",
       "@@@STEP_LOG_END@request@@@",
-      "@@@STEP_LINK@chromium/try/linux/8922054662172514001@https://cr-buildbucket.appspot.com/build/8922054662172514001@@@"
+      "@@@STEP_LINK@8922054662172514001@https://cr-buildbucket.appspot.com/build/8922054662172514001@@@"
     ]
   },
   {
diff --git a/recipe_modules/buildbucket/tests/schedule.expected/experimental.json b/recipe_modules/buildbucket/tests/schedule.expected/experimental.json
index b8dc6db..ecc1db2 100644
--- a/recipe_modules/buildbucket/tests/schedule.expected/experimental.json
+++ b/recipe_modules/buildbucket/tests/schedule.expected/experimental.json
@@ -65,7 +65,7 @@
       "@@@STEP_LOG_LINE@request@  ]@@@",
       "@@@STEP_LOG_LINE@request@}@@@",
       "@@@STEP_LOG_END@request@@@",
-      "@@@STEP_LINK@chromium/try/linux/8922054662172514000@https://cr-buildbucket.appspot.com/build/8922054662172514000@@@"
+      "@@@STEP_LINK@8922054662172514000@https://cr-buildbucket.appspot.com/build/8922054662172514000@@@"
     ]
   },
   {
@@ -139,7 +139,7 @@
       "@@@STEP_LOG_LINE@request@  ]@@@",
       "@@@STEP_LOG_LINE@request@}@@@",
       "@@@STEP_LOG_END@request@@@",
-      "@@@STEP_LINK@chromium/try/linux/8922054662172514001@https://cr-buildbucket.appspot.com/build/8922054662172514001@@@"
+      "@@@STEP_LINK@8922054662172514001@https://cr-buildbucket.appspot.com/build/8922054662172514001@@@"
     ]
   },
   {
diff --git a/recipe_modules/buildbucket/tests/schedule.expected/non-experimental.json b/recipe_modules/buildbucket/tests/schedule.expected/non-experimental.json
index 9faaa50..3b7a9c3 100644
--- a/recipe_modules/buildbucket/tests/schedule.expected/non-experimental.json
+++ b/recipe_modules/buildbucket/tests/schedule.expected/non-experimental.json
@@ -65,7 +65,7 @@
       "@@@STEP_LOG_LINE@request@  ]@@@",
       "@@@STEP_LOG_LINE@request@}@@@",
       "@@@STEP_LOG_END@request@@@",
-      "@@@STEP_LINK@chromium/try/linux/8922054662172514000@https://cr-buildbucket.appspot.com/build/8922054662172514000@@@"
+      "@@@STEP_LINK@8922054662172514000@https://cr-buildbucket.appspot.com/build/8922054662172514000@@@"
     ]
   },
   {
@@ -139,7 +139,7 @@
       "@@@STEP_LOG_LINE@request@  ]@@@",
       "@@@STEP_LOG_LINE@request@}@@@",
       "@@@STEP_LOG_END@request@@@",
-      "@@@STEP_LINK@chromium/try/linux/8922054662172514001@https://cr-buildbucket.appspot.com/build/8922054662172514001@@@"
+      "@@@STEP_LINK@8922054662172514001@https://cr-buildbucket.appspot.com/build/8922054662172514001@@@"
     ]
   },
   {
diff --git a/recipe_modules/buildbucket/tests/schedule.expected/properties.json b/recipe_modules/buildbucket/tests/schedule.expected/properties.json
index 98937aa..535ed61 100644
--- a/recipe_modules/buildbucket/tests/schedule.expected/properties.json
+++ b/recipe_modules/buildbucket/tests/schedule.expected/properties.json
@@ -70,7 +70,7 @@
       "@@@STEP_LOG_LINE@request@  ]@@@",
       "@@@STEP_LOG_LINE@request@}@@@",
       "@@@STEP_LOG_END@request@@@",
-      "@@@STEP_LINK@chromium/try/linux/8922054662172514000@https://cr-buildbucket.appspot.com/build/8922054662172514000@@@"
+      "@@@STEP_LINK@8922054662172514000@https://cr-buildbucket.appspot.com/build/8922054662172514000@@@"
     ]
   },
   {
@@ -149,7 +149,7 @@
       "@@@STEP_LOG_LINE@request@  ]@@@",
       "@@@STEP_LOG_LINE@request@}@@@",
       "@@@STEP_LOG_END@request@@@",
-      "@@@STEP_LINK@chromium/try/linux/8922054662172514001@https://cr-buildbucket.appspot.com/build/8922054662172514001@@@"
+      "@@@STEP_LINK@8922054662172514001@https://cr-buildbucket.appspot.com/build/8922054662172514001@@@"
     ]
   },
   {
diff --git a/recipe_modules/buildbucket/tests/schedule.expected/tags.json b/recipe_modules/buildbucket/tests/schedule.expected/tags.json
index 085d592..a1b813d 100644
--- a/recipe_modules/buildbucket/tests/schedule.expected/tags.json
+++ b/recipe_modules/buildbucket/tests/schedule.expected/tags.json
@@ -68,7 +68,7 @@
       "@@@STEP_LOG_LINE@request@  ]@@@",
       "@@@STEP_LOG_LINE@request@}@@@",
       "@@@STEP_LOG_END@request@@@",
-      "@@@STEP_LINK@chromium/try/linux/8922054662172514000@https://cr-buildbucket.appspot.com/build/8922054662172514000@@@"
+      "@@@STEP_LINK@8922054662172514000@https://cr-buildbucket.appspot.com/build/8922054662172514000@@@"
     ]
   },
   {
@@ -145,7 +145,7 @@
       "@@@STEP_LOG_LINE@request@  ]@@@",
       "@@@STEP_LOG_LINE@request@}@@@",
       "@@@STEP_LOG_END@request@@@",
-      "@@@STEP_LINK@chromium/try/linux/8922054662172514001@https://cr-buildbucket.appspot.com/build/8922054662172514001@@@"
+      "@@@STEP_LINK@8922054662172514001@https://cr-buildbucket.appspot.com/build/8922054662172514001@@@"
     ]
   },
   {