go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/ui/src/test_verdict/legacy/invocation_page/invocation_page.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 { LinearProgress } from '@mui/material'; 16 import { observer } from 'mobx-react-lite'; 17 import { useEffect } from 'react'; 18 import { useParams } from 'react-router-dom'; 19 20 import { RecoverableErrorBoundary } from '@/common/components/error_handling'; 21 import { PageMeta } from '@/common/components/page_meta/page_meta'; 22 import { AppRoutedTab, AppRoutedTabs } from '@/common/components/routed_tabs'; 23 import { INVOCATION_STATE_DISPLAY_MAP } from '@/common/constants/legacy'; 24 import { useStore } from '@/common/store'; 25 import { 26 getBuildURLPathFromBuildId, 27 getSwarmingTaskURL, 28 } from '@/common/tools/url_utils'; 29 30 import { CountIndicator } from '../test_results_tab/count_indicator'; 31 32 import { InvLitEnvProvider } from './inv_lit_env_provider'; 33 34 // Should be checked upstream, but allowlist URLs here just to be safe. 35 const ALLOWED_SWARMING_HOSTS = [ 36 'chromium-swarm-dev.appspot.com', 37 'chromium-swarm.appspot.com', 38 'chrome-swarming.appspot.com', 39 ]; 40 41 export const InvocationPage = observer(() => { 42 const { invId } = useParams(); 43 const store = useStore(); 44 45 if (!invId) { 46 throw new Error('invariant violated: invId should be set'); 47 } 48 49 useEffect(() => { 50 store.invocationPage.setInvocationId(invId); 51 }, [invId, store]); 52 53 const inv = store.invocationPage.invocation.invocation; 54 const project = store.invocationPage.invocation.project; 55 const buildId = invId.match(/^build-(?<id>\d+)/)?.groups?.['id']; 56 const { swarmingHost, taskId } = 57 invId.match(/^task-(?<swarmingHost>.*)-(?<taskId>[0-9a-fA-F]+)$/)?.groups || 58 {}; 59 60 return ( 61 <InvLitEnvProvider> 62 <PageMeta project={project || ''} title={`inv: ${invId}`} /> 63 <div 64 css={{ 65 backgroundColor: 'var(--block-background-color)', 66 padding: '6px 16px', 67 display: 'flex', 68 }} 69 > 70 <div css={{ flex: '0 auto' }}> 71 <span css={{ color: 'var(--light-text-color)' }}>Invocation ID </span> 72 <span>{invId}</span> 73 {buildId && ( 74 <> 75 {' '} 76 ( 77 <a 78 href={getBuildURLPathFromBuildId(buildId)} 79 target="_blank" 80 rel="noreferrer" 81 > 82 build page 83 </a> 84 ) 85 </> 86 )} 87 {ALLOWED_SWARMING_HOSTS.includes(swarmingHost) && taskId && ( 88 <a 89 href={getSwarmingTaskURL(swarmingHost, taskId)} 90 target="_blank" 91 rel="noreferrer" 92 > 93 task page 94 </a> 95 )} 96 </div> 97 <div 98 css={{ 99 marginLeft: 'auto', 100 flex: '0 auto', 101 }} 102 > 103 {inv && ( 104 <> 105 <i>{INVOCATION_STATE_DISPLAY_MAP[inv.state]}</i> 106 {inv.finalizeTime ? ( 107 <> at {new Date(inv.finalizeTime).toLocaleString()}</> 108 ) : ( 109 <> since {new Date(inv.createTime).toLocaleString()}</> 110 )} 111 </> 112 )} 113 </div> 114 </div> 115 <LinearProgress 116 value={100} 117 variant={inv ? 'determinate' : 'indeterminate'} 118 /> 119 <AppRoutedTabs> 120 <AppRoutedTab 121 label="Test Results" 122 value="test-results" 123 to="test-results" 124 icon={<CountIndicator />} 125 iconPosition="end" 126 /> 127 <AppRoutedTab 128 label="Invocation Details" 129 value="invocation-details" 130 to="invocation-details" 131 /> 132 </AppRoutedTabs> 133 </InvLitEnvProvider> 134 ); 135 }); 136 137 export const element = ( 138 // See the documentation for `<LoginPage />` for why we handle error this way. 139 <RecoverableErrorBoundary key="invocation"> 140 <InvocationPage /> 141 </RecoverableErrorBoundary> 142 );