go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/analysis/frontend/ui/src/hooks/use_fetch_clusters.ts (about) 1 // Copyright 2022 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 dayjs from 'dayjs'; 16 import { 17 useQuery, 18 UseQueryResult, 19 } from 'react-query'; 20 21 import { 22 ClusterSummaryView, 23 QueryClusterSummariesRequest, 24 QueryClusterSummariesResponse, 25 } from '@/proto/go.chromium.org/luci/analysis/proto/v1/clusters.pb'; 26 import { ProjectMetric } from '@/proto/go.chromium.org/luci/analysis/proto/v1/metrics.pb'; 27 import { getClustersService } from '@/services/services'; 28 import { prpcRetrier } from '@/tools/prpc_retrier'; 29 import { MetricId } from '@/types/metric_id'; 30 31 export interface ClustersFetchOptions { 32 project: string; 33 failureFilter: string; 34 orderBy?: OrderBy; 35 metrics: ProjectMetric[]; 36 interval?: TimeInterval; 37 } 38 39 export interface OrderBy { 40 metric: MetricId; 41 isAscending: boolean; 42 } 43 44 export interface TimeInterval { 45 id: string; // ID for the time interval, e.g. '3d' 46 label: string; // Human-readable name for the time interval, e.g. '3 days' 47 duration: number; // Duration of the time interval in hours 48 } 49 50 const intervalDuration = (interval?: TimeInterval): number => { 51 if (!interval) { 52 return 0; 53 } 54 return interval.duration; 55 }; 56 57 // orderByClause returns the AIP-132 order by clause needed 58 // to sort by the given metric. 59 const orderByClause = (orderBy?: OrderBy): string => { 60 if (!orderBy) { 61 return ''; 62 } 63 return `metrics.\`${orderBy.metric}\`.value${orderBy.isAscending ? '' : ' desc'}`; 64 }; 65 66 // metricsKey returns a unique key to represent the given 67 // set of metrics. 68 export const metricsKey = (metrics: ProjectMetric[]): string => { 69 const metricNames = metrics.map((m) => m.name); 70 // Sort to ensure we treat the input as a set instead 71 // of a list. 72 metricNames.sort(); 73 // Metric IDs only contain characters in [a-z0-9-] 74 // so it is safe to concatenate them with other characters 75 // while still guaranteeing the returned keys is unique 76 // for each combination of metrics. 77 return metricNames.join(':'); 78 }; 79 80 export const useFetchClusterSummaries = ( 81 { project, failureFilter, orderBy, interval, metrics }: ClustersFetchOptions, 82 view: ClusterSummaryView, 83 ): UseQueryResult<QueryClusterSummariesResponse, Error> => { 84 const clustersService = getClustersService(); 85 return useQuery( 86 ['clusters', view, project, failureFilter, orderByClause(orderBy), intervalDuration(interval), metricsKey(metrics)], 87 async () => { 88 const latestTime = dayjs(); 89 const request: QueryClusterSummariesRequest = { 90 project: project, 91 timeRange: { 92 earliest: latestTime.subtract(intervalDuration(interval), 'hours').toISOString(), 93 latest: latestTime.toISOString(), 94 }, 95 failureFilter: failureFilter, 96 orderBy: orderByClause(orderBy), 97 metrics: metrics.map((m) => m.name), 98 view: view, 99 }; 100 return await clustersService.queryClusterSummaries(request); 101 }, 102 { 103 retry: prpcRetrier, 104 enabled: ( 105 orderBy !== undefined && 106 orderBy.metric !== '' && 107 metrics.length > 0 && 108 interval !== undefined && 109 (view !== ClusterSummaryView.FULL || (ClusterSummaryView.FULL && interval.duration > 24)) 110 ), 111 }, 112 ); 113 };