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  }