go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/ui/src/test_verdict/pages/recent_regressions_page/recent_regressions.tsx (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 { Box, CircularProgress } from '@mui/material'; 16 import { useQuery } from '@tanstack/react-query'; 17 import { useCallback } from 'react'; 18 19 import { useChangepointsClient } from '@/analysis/hooks/prpc_clients'; 20 import { useSyncedSearchParams } from '@/generic_libs/hooks/synced_search_params'; 21 import { 22 ChangepointPredicate, 23 QueryChangepointGroupSummariesRequest, 24 } from '@/proto/go.chromium.org/luci/analysis/proto/v1/changepoints.pb'; 25 import { getRegressionDetailsURLPath } from '@/test_verdict/tools/url_utils'; 26 import { OutputChangepointGroupSummary } from '@/test_verdict/types'; 27 28 import { RegressionFilters } from './regression_filters'; 29 import { RegressionTable } from './regression_table'; 30 31 function getPredicate(searchParams: URLSearchParams) { 32 const predicate = searchParams.get('cp') || '{}'; 33 return ChangepointPredicate.fromJSON(JSON.parse(predicate)); 34 } 35 36 function predicateUpdater(newPredicate: ChangepointPredicate) { 37 return (params: URLSearchParams) => { 38 const searchParams = new URLSearchParams(params); 39 const predicateStr = JSON.stringify( 40 ChangepointPredicate.toJSON(newPredicate), 41 ); 42 if (predicateStr === '{}') { 43 searchParams.delete('cp'); 44 } else { 45 searchParams.set('cp', predicateStr); 46 } 47 return searchParams; 48 }; 49 } 50 51 export interface RecentRegressionsProps { 52 readonly project: string; 53 } 54 55 export function RecentRegressions({ project }: RecentRegressionsProps) { 56 const [searchParams, setSearchParams] = useSyncedSearchParams(); 57 const predicate = getPredicate(searchParams); 58 59 const client = useChangepointsClient(); 60 const { data, isLoading, isError, error } = useQuery( 61 client.QueryChangepointGroupSummaries.query( 62 QueryChangepointGroupSummariesRequest.fromPartial({ 63 project, 64 predicate, 65 }), 66 ), 67 ); 68 69 if (isError) { 70 throw error; 71 } 72 73 const getDetailsUrlPath = useCallback( 74 (group: OutputChangepointGroupSummary) => 75 getRegressionDetailsURLPath({ 76 canonicalChangepoint: group.canonicalChangepoint, 77 predicate, 78 }), 79 [predicate], 80 ); 81 82 return ( 83 <> 84 <Box 85 display="flex" 86 justifyContent="center" 87 alignItems="center" 88 sx={{ margin: '10px 20px' }} 89 > 90 <RegressionFilters 91 predicate={predicate} 92 onPredicateUpdate={(p) => setSearchParams(predicateUpdater(p))} 93 /> 94 </Box> 95 {isLoading ? ( 96 <Box display="flex" justifyContent="center" alignItems="center"> 97 <CircularProgress /> 98 </Box> 99 ) : ( 100 <RegressionTable 101 regressions={ 102 data.groupSummaries as readonly OutputChangepointGroupSummary[] 103 } 104 getDetailsUrlPath={getDetailsUrlPath} 105 /> 106 )} 107 </> 108 ); 109 }