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 }