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  };