go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/analysis/frontend/ui/src/hooks/use_fetch_exonerated_test_variant_branches.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 { useQuery } from 'react-query'; 16 17 import { getClustersService, getTestVariantsService } from '@/services/services'; 18 import { prpcRetrier } from '@/tools/prpc_retrier'; 19 import { 20 QueryTestVariantStabilityRequest, 21 QueryTestVariantStabilityRequest_TestVariantPosition, 22 TestStabilityCriteria, 23 TestVariantStabilityAnalysis, 24 TestVariantStabilityAnalysis_FailureRate, 25 TestVariantStabilityAnalysis_FlakeRate, 26 } from '@/proto/go.chromium.org/luci/analysis/proto/v1/test_variants.pb'; 27 import { GitilesCommit, SourceRef } from '@/proto/go.chromium.org/luci/analysis/proto/v1/sources.pb'; 28 import { Variant } from '@/proto/go.chromium.org/luci/analysis/proto/v1/common.pb'; 29 import { ClusterExoneratedTestVariantBranch } from '@/proto/go.chromium.org/luci/analysis/proto/v1/clusters.pb'; 30 31 const useFetchExoneratedTestVariantBranches = (project: string, clusterAlgorithm: string, clusterId: string) => { 32 return useQuery(['exoneratedTestVariantBranches', project, clusterAlgorithm, clusterId], async () => { 33 const clusterService = getClustersService(); 34 const clusterResponse = await clusterService.queryExoneratedTestVariantBranches({ 35 parent: `projects/${project}/clusters/${clusterAlgorithm}/${clusterId}/exoneratedTestVariantBranches`, 36 }); 37 const clusterExoneratedTestVariantBranches = clusterResponse.testVariantBranches; 38 if (clusterExoneratedTestVariantBranches.length === 0) { 39 // The criteria is irrelevant as there are no branches to display. 40 // Return a filler criteria. 41 const emptyCriteria : TestStabilityCriteria = { 42 failureRate: { 43 failureThreshold: 0, 44 consecutiveFailureThreshold: 0, 45 }, 46 flakeRate: { 47 flakeRateThreshold: 0.0, 48 flakeThreshold: 0, 49 minWindow: 0, 50 }, 51 }; 52 const result: ExoneratedTestVariantBranches = { 53 testVariantBranches: [], 54 criteria: emptyCriteria, 55 }; 56 return result; 57 } 58 const tvRequest: QueryTestVariantStabilityRequest = { 59 project: project, 60 testVariants: clusterExoneratedTestVariantBranches.map((tvb) => { 61 let gitilesCommit: GitilesCommit | undefined; 62 if (tvb.sourceRef?.gitiles) { 63 gitilesCommit = { 64 host: tvb.sourceRef.gitiles.host, 65 ref: tvb.sourceRef.gitiles.ref, 66 project: tvb.sourceRef.gitiles.project, 67 // Query for the most recent commit position. 68 commitHash: 'ff'.repeat(20), 69 position: '999999999999', 70 }; 71 } 72 return QueryTestVariantStabilityRequest_TestVariantPosition.create({ 73 testId: tvb.testId, 74 variant: tvb.variant, 75 sources: { 76 gitilesCommit: gitilesCommit, 77 }, 78 }); 79 }), 80 }; 81 const tvService = getTestVariantsService(); 82 const tvResponse = await tvService.queryStability(tvRequest); 83 84 const exoneratedTVBs = tvResponse.testVariants?.map((analyzedTV, i) => { 85 // QueryFailureRate returns test variants in the same order 86 // that they are requested. 87 const exoneratedTV = clusterExoneratedTestVariantBranches[i]; 88 return testVariantBranchFromAnalysis(exoneratedTV, analyzedTV); 89 }) || []; 90 91 const result: ExoneratedTestVariantBranches = { 92 testVariantBranches: exoneratedTVBs, 93 // Criteria will always be set if the RPC succeeds. 94 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 95 criteria: tvResponse.criteria!, 96 }; 97 return result; 98 }, { 99 retry: prpcRetrier, 100 }); 101 }; 102 103 export default useFetchExoneratedTestVariantBranches; 104 105 function testVariantBranchFromAnalysis(ctvb: ClusterExoneratedTestVariantBranch, analysis: TestVariantStabilityAnalysis): ExoneratedTestVariantBranch { 106 return { 107 testId: ctvb.testId, 108 variant: ctvb.variant, 109 // Source ref will always be set. 110 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 111 sourceRef: ctvb.sourceRef!, 112 // Last exoneration will always be set. 113 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 114 lastExoneration: ctvb.lastExoneration!, 115 criticalFailuresExonerated: ctvb.criticalFailuresExonerated, 116 // It is a guarantee of the RPC that both failure rate and flake rate will be set. 117 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 118 failureRate: analysis.failureRate!, 119 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 120 flakeRate: analysis.flakeRate!, 121 }; 122 } 123 124 export interface ExoneratedTestVariantBranches { 125 testVariantBranches: ExoneratedTestVariantBranch[]; 126 criteria: TestStabilityCriteria; 127 } 128 129 export interface ExoneratedTestVariantBranch { 130 testId: string; 131 variant: Variant | undefined; 132 sourceRef: SourceRef; 133 // RFC 3339 encoded date/time of the last exoneration. 134 lastExoneration: string; 135 // The number of critical failures exonerated in the last week. 136 criticalFailuresExonerated: number; 137 138 failureRate: TestVariantStabilityAnalysis_FailureRate; 139 flakeRate: TestVariantStabilityAnalysis_FlakeRate; 140 }