go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/ui/src/build/pages/builder_page/pending_builds_section.tsx (about)

     1  // Copyright 2023 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 { CircularProgress, Link } from '@mui/material';
    16  import { useQuery } from '@tanstack/react-query';
    17  import { DateTime } from 'luxon';
    18  
    19  import { RelativeDurationBadge } from '@/common/components/relative_duration_badge';
    20  import { usePrpcServiceClient } from '@/common/hooks/prpc_query';
    21  import { getBuildURLPathFromBuildId } from '@/common/tools/url_utils';
    22  import { BuilderID } from '@/proto/go.chromium.org/luci/buildbucket/proto/builder_common.pb';
    23  import {
    24    BuildsClientImpl,
    25    SearchBuildsRequest,
    26  } from '@/proto/go.chromium.org/luci/buildbucket/proto/builds_service.pb';
    27  import { Status } from '@/proto/go.chromium.org/luci/buildbucket/proto/common.pb';
    28  
    29  const PAGE_SIZE = 100;
    30  const FIELD_MASK = Object.freeze([
    31    'builds.*.id',
    32    'builds.*.number',
    33    'builds.*.create_time',
    34  ]);
    35  
    36  export interface PendingBuildsSectionProps {
    37    readonly builderId: BuilderID;
    38  }
    39  
    40  export function PendingBuildsSection({ builderId }: PendingBuildsSectionProps) {
    41    const client = usePrpcServiceClient({
    42      host: SETTINGS.buildbucket.host,
    43      ClientImpl: BuildsClientImpl,
    44    });
    45    const { data, error, isError, isLoading } = useQuery(
    46      client.SearchBuilds.query(
    47        SearchBuildsRequest.fromPartial({
    48          predicate: {
    49            builder: builderId,
    50            includeExperimental: true,
    51            status: Status.SCHEDULED,
    52          },
    53          pageSize: PAGE_SIZE,
    54          fields: FIELD_MASK,
    55        }),
    56      ),
    57    );
    58  
    59    if (isError) {
    60      throw error;
    61    }
    62  
    63    return (
    64      <>
    65        <h3>
    66          Scheduled Builds
    67          {!isLoading && (
    68            <>
    69              {' '}
    70              (
    71              {data.nextPageToken
    72                ? `most recent ${PAGE_SIZE} builds`
    73                : data.builds?.length || 0}
    74              )
    75            </>
    76          )}
    77        </h3>
    78        {isLoading ? (
    79          <CircularProgress />
    80        ) : (
    81          <ul css={{ maxHeight: '400px', overflow: 'auto' }}>
    82            {data.builds?.map((b) => {
    83              return (
    84                <li key={b.id}>
    85                  <Link href={getBuildURLPathFromBuildId(b.id)}>
    86                    {b.number || `b${b.id}`}
    87                  </Link>{' '}
    88                  <RelativeDurationBadge
    89                    css={{ verticalAlign: 'text-top' }}
    90                    from={DateTime.fromISO(b.createTime!)}
    91                  />{' '}
    92                  ago
    93                </li>
    94              );
    95            })}
    96          </ul>
    97        )}
    98      </>
    99    );
   100  }