go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/ui/src/proto_utils/batched_builds_client.ts (about) 1 // Copyright 2024 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 import { BatchOption, batched } from '@/generic_libs/tools/batched_fn'; 16 import { Build } from '@/proto/go.chromium.org/luci/buildbucket/proto/build.pb'; 17 import { 18 BatchRequest, 19 BatchResponse, 20 BuildsClientImpl, 21 CancelBuildRequest, 22 GetBuildRequest, 23 GetBuildStatusRequest, 24 ScheduleBuildRequest, 25 SearchBuildsRequest, 26 SearchBuildsResponse, 27 } from '@/proto/go.chromium.org/luci/buildbucket/proto/builds_service.pb'; 28 29 import { Rpc } from './types'; 30 31 export interface BatchedBuildsClientImplOpts { 32 readonly service?: string; 33 /** 34 * Maximum number of requests in a given batch request. Defaults to 200. 35 */ 36 readonly maxBatchSize?: number; 37 } 38 39 /** 40 * The same as `BuildsClientImpl` except that eligible RPC calls are batched 41 * automatically. 42 */ 43 export class BatchedBuildsClientImpl extends BuildsClientImpl { 44 private readonly autoBatchedBatch: ( 45 opt: BatchOption, 46 req: BatchRequest, 47 ) => Promise<BatchResponse>; 48 49 constructor(rpc: Rpc, opts?: BatchedBuildsClientImplOpts) { 50 super(rpc, opts); 51 const maxBatchSize = opts?.maxBatchSize || 200; 52 53 this.autoBatchedBatch = batched({ 54 // eslint-disable-next-line new-cap 55 fn: (req: BatchRequest) => super.Batch(req), 56 combineParamSets([req1], [req2]) { 57 if (req1.requests.length + req2.requests.length > maxBatchSize) { 58 return { 59 ok: false, 60 value: null, 61 }; 62 } 63 64 return { 65 ok: true, 66 value: [ 67 { 68 requests: [...req1.requests, ...req2.requests], 69 }, 70 ], 71 }; 72 }, 73 splitReturn(paramSets, ret) { 74 let pivot = 0; 75 const splitRets: BatchResponse[] = []; 76 for (const [req] of paramSets) { 77 splitRets.push({ 78 responses: ret.responses.slice(pivot, pivot + req.requests.length), 79 }); 80 pivot += req.requests.length; 81 } 82 83 return splitRets; 84 }, 85 }); 86 } 87 88 async GetBuild( 89 request: GetBuildRequest, 90 opt: BatchOption = {}, 91 ): Promise<Build> { 92 const batchedRes = await this.autoBatchedBatch(opt, { 93 requests: [ 94 { 95 getBuild: request, 96 }, 97 ], 98 }); 99 // The responses array should always have the same length as the request 100 // array. 101 const res = batchedRes.responses[0]; 102 if (res.error) { 103 throw res.error; 104 } 105 return res.getBuild!; 106 } 107 108 async SearchBuilds( 109 request: SearchBuildsRequest, 110 opt: BatchOption = {}, 111 ): Promise<SearchBuildsResponse> { 112 const batchedRes = await this.autoBatchedBatch(opt, { 113 requests: [ 114 { 115 searchBuilds: request, 116 }, 117 ], 118 }); 119 // The responses array should always have the same length as the request 120 // array. 121 const res = batchedRes.responses[0]; 122 if (res.error) { 123 throw res.error; 124 } 125 return res.searchBuilds!; 126 } 127 128 async ScheduleBuild( 129 request: ScheduleBuildRequest, 130 opt: BatchOption = {}, 131 ): Promise<Build> { 132 const batchedRes = await this.autoBatchedBatch(opt, { 133 requests: [ 134 { 135 scheduleBuild: request, 136 }, 137 ], 138 }); 139 // The responses array should always have the same length as the request 140 // array. 141 const res = batchedRes.responses[0]; 142 if (res.error) { 143 throw res.error; 144 } 145 return res.scheduleBuild!; 146 } 147 148 async CancelBuild( 149 request: CancelBuildRequest, 150 opt: BatchOption = {}, 151 ): Promise<Build> { 152 const batchedRes = await this.autoBatchedBatch(opt, { 153 requests: [ 154 { 155 cancelBuild: request, 156 }, 157 ], 158 }); 159 // The responses array should always have the same length as the request 160 // array. 161 const res = batchedRes.responses[0]; 162 if (res.error) { 163 throw res.error; 164 } 165 return res.cancelBuild!; 166 } 167 168 async GetBuildStatus( 169 request: GetBuildStatusRequest, 170 opt: BatchOption = {}, 171 ): Promise<Build> { 172 const batchedRes = await this.autoBatchedBatch(opt, { 173 requests: [ 174 { 175 getBuildStatus: request, 176 }, 177 ], 178 }); 179 // The responses array should always have the same length as the request 180 // array. 181 const res = batchedRes.responses[0]; 182 if (res.error) { 183 throw res.error; 184 } 185 return res.getBuildStatus!; 186 } 187 188 Batch(request: BatchRequest, opt: BatchOption = {}): Promise<BatchResponse> { 189 return this.autoBatchedBatch(opt, request); 190 } 191 }