1- import React , { useState , useEffect , useCallback , useContext } from 'react' ;
1+ import React , { useState , useEffect , useCallback , useContext , useMemo } from 'react' ;
22import { useParams , useNavigate , useLocation , Link } from 'react-router-dom' ;
33
44import {
@@ -29,29 +29,23 @@ import {
2929import {
3030 CatalogIcon ,
3131 ChevronRightIcon ,
32- CheckCircleIcon ,
3332 CodeIcon ,
34- ExclamationCircleIcon ,
35- FileIcon ,
3633 FileAltIcon ,
3734 FolderIcon ,
3835 FolderOpenIcon ,
3936 InfoCircleIcon ,
4037 MessagesIcon ,
41- QuestionCircleIcon ,
42- RepositoryIcon ,
43- TimesCircleIcon
38+ RepositoryIcon
4439} from '@patternfly/react-icons' ;
4540import { CodeEditor , Language } from '@patternfly/react-code-editor' ;
4641
4742import { HttpClient } from './services/http' ;
4843import { Settings } from './settings' ;
4944import {
50- cleanPath ,
5145 getSpinnerRow ,
52- processPyTestPath ,
5346 resultToRow ,
54- round
47+ round ,
48+ buildResultsTree
5549} from './utilities' ;
5650import EmptyObject from './components/empty-object' ;
5751import FilterTable from './components/filtertable' ;
@@ -60,114 +54,13 @@ import TabTitle from './components/tabs';
6054import ClassifyFailuresTable from './components/classify-failures' ;
6155import ArtifactTab from './components/artifact-tab' ;
6256import { IbutsuContext } from './services/context' ;
57+ import { useTabHook } from './components/tabHook' ;
6358
64- // const match = (node, text) => node.name.toLowerCase().indexOf(text.toLowerCase()) !== -1;
65-
66- // const findNode = (node, text) => match(node, text) || (
67- // node.children && node.children.length && !!node.children.find(child => findNode(child, text))
68- // );
69-
70- // const searchTree = (node, text) => {
71- // if (match(node, text) || !node.children) {
72- // return node;
73- // }
74- // const filtered = node.children
75- // .filter(child => findNode(child, text))
76- // .map(child => searchTree(child, text));
77- // return Object.assign({}, node, {children: filtered});
78- // };
79-
80- const buildTree = ( results ) => {
81- const getPassPercent = ( stats ) => {
82- let percent = 'N/A' ;
83- if ( stats . count > 0 ) {
84- percent = Math . round ( ( ( stats . passed + stats . xfailed ) / stats . count * 100 ) ) ;
85- }
86- return percent ;
87- } ;
88-
89- const getBadgeClass = ( passPercent ) => {
90- let className = 'failed' ;
91- if ( passPercent > 75 ) {
92- className = 'error' ;
93- }
94- if ( passPercent > 90 ) {
95- className = 'passed' ;
96- }
97- return className ;
98- } ;
99-
100- let treeStructure = [ ] ;
101- results . forEach ( testResult => {
102- const pathParts = processPyTestPath ( cleanPath ( testResult . metadata . fspath ) ) ;
103- let children = treeStructure ;
104- pathParts . forEach ( dirName => {
105- let child = children . find ( item => item . name == dirName ) ;
106- if ( ! child ) {
107- child = {
108- name : dirName ,
109- id : dirName ,
110- children : [ ] ,
111- hasBadge : true ,
112- _stats : {
113- count : 0 ,
114- passed : 0 ,
115- failed : 0 ,
116- skipped : 0 ,
117- error : 0 ,
118- xpassed : 0 ,
119- xfailed : 0
120- } ,
121- } ;
122- if ( dirName . endsWith ( '.py' ) ) {
123- child . icon = < FileIcon /> ;
124- child . expandedIcon = < FileIcon /> ;
125- }
126- children . push ( child ) ;
127- }
128- child . _stats [ testResult . result ] += 1 ;
129- child . _stats . count += 1 ;
130- const passPercent = getPassPercent ( child . _stats ) ;
131- const className = getBadgeClass ( passPercent ) ;
132- child . customBadgeContent = `${ passPercent } %` ;
133- child . badgeProps = { className : className } ;
134- children = child . children ;
135- } ) ;
136- let icon = < QuestionCircleIcon /> ;
137- if ( testResult . result === 'passed' ) {
138- icon = < CheckCircleIcon /> ;
139- }
140- else if ( testResult . result === 'failed' ) {
141- icon = < TimesCircleIcon /> ;
142- }
143- else if ( testResult . result === 'error' ) {
144- icon = < ExclamationCircleIcon /> ;
145- }
146- else if ( testResult . result === 'skipped' ) {
147- icon = < ChevronRightIcon /> ;
148- }
149- else if ( testResult . result === 'xfailed' ) {
150- icon = < CheckCircleIcon /> ;
151- }
152- else if ( testResult . result === 'xpassed' ) {
153- icon = < TimesCircleIcon /> ;
154- }
155- children . push ( {
156- id : testResult . id ,
157- name : testResult . test_id ,
158- icon : < span className = { testResult . result } > { icon } </ span > ,
159- _testResult : testResult
160- } ) ;
161- } ) ;
162- return treeStructure ;
163- } ;
16459
16560const COLUMNS = [ 'Test' , 'Run' , 'Result' , 'Duration' , 'Started' ] ;
16661
167- const Run = ( ) => {
62+ const Run = ( defaultTab = 'summary' ) => {
16863 const { run_id } = useParams ( ) ;
169- const navigate = useNavigate ( ) ;
170- const location = useLocation ( ) ;
17164
17265 const context = useContext ( IbutsuContext ) ;
17366 const { darkTheme} = context ;
@@ -176,11 +69,6 @@ const Run = () => {
17669 const [ testResult , setTestResult ] = useState ( null ) ;
17770 const [ rows , setRows ] = useState ( [ getSpinnerRow ( 5 ) ] ) ;
17871
179- // eslint-disable-next-line no-unused-vars
180- const [ results , setResults ] = useState ( [ ] ) ; // results unused?
181-
182- const [ activeTab , setActiveTab ] = useState ( ) ;
183-
18472 const [ pageSize , setPageSize ] = useState ( 10 ) ;
18573 const [ page , setPage ] = useState ( 1 ) ;
18674 const [ totalItems , setTotalItems ] = useState ( 0 ) ;
@@ -190,13 +78,7 @@ const Run = () => {
19078 const [ resultsTree , setResultsTree ] = useState ( [ ] ) ;
19179 const [ activeItems , setActiveItems ] = useState ( [ ] ) ;
19280
193- const [ artifactTabs , setArtifactTabs ] = useState ( [ ] ) ;
194-
195- useEffect ( ( ) => {
196- if ( ! activeTab ) {
197- setActiveTab ( 'summary' ) ;
198- }
199- } , [ activeTab ] ) ;
81+ const [ artifacts , setArtifacts ] = useState ( [ ] ) ;
20082
20183 const onTreeItemSelect = ( _ , treeItem ) => {
20284 if ( treeItem && ! treeItem . children ) {
@@ -205,43 +87,6 @@ const Run = () => {
20587 }
20688 } ;
20789
208- useEffect ( ( ) => {
209- let fetchedResults = [ ] ;
210- const getResultsForTree = ( treePage ) => {
211- HttpClient . get ( [ Settings . serverUrl , 'result' ] , {
212- filter : 'run_id=' + run_id ,
213- pageSize : 500 ,
214- page : treePage
215- } )
216- . then ( response => HttpClient . handleResponse ( response ) )
217- . then ( data => {
218- fetchedResults = [ ...fetchedResults , ...data . results ] ;
219- if ( data . results . length === 500 ) {
220- // recursively fetch the next page
221- getResultsForTree ( treePage + 1 ) ;
222- }
223- else {
224- setResultsTree ( buildTree ( fetchedResults ) ) ;
225- }
226- } )
227- . catch ( ( error ) => {
228- console . error ( 'Error fetching result data:' , error ) ;
229- } ) ;
230- } ;
231-
232- if ( activeTab === 'results-tree' ) {
233- getResultsForTree ( 1 ) ;
234- }
235-
236- } , [ activeTab , run_id ] ) ;
237-
238- const onTabSelect = ( _ , tabIndex ) => {
239- if ( location ) {
240- navigate ( `${ location . pathname } #${ tabIndex } ` ) ;
241- }
242- setActiveTab ( tabIndex ) ;
243- } ;
244-
24590 useEffect ( ( ) => {
24691 if ( ! run_id ) { return ; }
24792 setIsError ( false ) ;
@@ -253,7 +98,6 @@ const Run = () => {
25398 } )
25499 . then ( response => HttpClient . handleResponse ( response ) )
255100 . then ( data => {
256- setResults ( data . results ) ;
257101 setRows ( data . results . map ( ( result ) => resultToRow ( result ) ) ) ;
258102 setPage ( data . pagination . page ) ;
259103 setPageSize ( data . pagination . pageSize ) ;
@@ -266,41 +110,70 @@ const Run = () => {
266110 } ) ;
267111 } , [ page , pageSize , run_id ] ) ;
268112
269- const handlePopState = useCallback ( ( ) => {
270- // Handle browser navigation buttons click
271- const tabIndex = ( ! ! location && location . hash !== '' ) ? location . hash . substring ( 1 ) : 'summary' ;
272- setActiveTab ( tabIndex ) ;
273- } , [ location ] ) ;
274-
275113 useEffect ( ( ) => {
276114 if ( ! run_id ) { return ; }
277115 HttpClient . get ( [ Settings . serverUrl , 'run' , run_id ] )
278116 . then ( response => HttpClient . handleResponse ( response ) )
279117 . then ( data => {
280118 setRun ( data ) ;
281- setArtifactTabs ( data . artifacts ?. map ( artifact => (
282- < Tab
283- key = { artifact . id }
284- eventKey = { artifact . id }
285- title = {
286- < TabTitle
287- icon = { < FileAltIcon /> }
288- text = { artifact . filename } />
289- } >
290- < ArtifactTab artifact = { artifact } />
291- </ Tab >
292- ) ) ) ;
119+ setArtifacts ( data . artifacts ) ;
293120 } )
294121 . catch ( error => {
295122 console . error ( error ) ;
296123 setIsRunValid ( false ) ;
297124 } ) ;
125+ } , [ run_id ] ) ;
126+
127+ const artifactTabs = useMemo ( ( ) => (
128+ artifacts ?. map ( artifact => (
129+ < Tab
130+ key = { artifact . id }
131+ eventKey = { artifact . id }
132+ title = {
133+ < TabTitle
134+ icon = { < FileAltIcon /> }
135+ text = { artifact . filename } />
136+ } >
137+ < ArtifactTab artifact = { artifact } />
138+ </ Tab > ) )
139+ ) , [ artifacts ] ) ;
140+
141+ // Tab state and navigation hooks/effects
142+ const { activeTab, onTabSelect} = useTabHook (
143+ [ 'summary' , 'results-list' , 'results-tree' , 'classify-failures' , 'run-object' ,
144+ ...artifactTabs . map ( ( tab ) => tab . key ) ] ,
145+ defaultTab
146+ ) ;
298147
299- window . addEventListener ( 'popstate' , handlePopState ) ;
300- return ( ) => {
301- window . removeEventListener ( 'popstate' , handlePopState ) ;
148+ useEffect ( ( ) => {
149+ let fetchedResults = [ ] ;
150+ const getResultsForTree = ( treePage = 1 ) => {
151+ HttpClient . get ( [ Settings . serverUrl , 'result' ] , {
152+ filter : 'run_id=' + run_id ,
153+ pageSize : 500 ,
154+ page : treePage
155+ } )
156+ . then ( response => HttpClient . handleResponse ( response ) )
157+ . then ( data => {
158+ fetchedResults = [ ...fetchedResults , ...data . results ] ;
159+ if ( data . results . length === 500 ) {
160+ // recursively fetch the next page
161+ getResultsForTree ( treePage + 1 ) ;
162+ }
163+ else {
164+ setResultsTree ( buildResultsTree ( fetchedResults ) ) ;
165+ }
166+ } )
167+ . catch ( ( error ) => {
168+ console . error ( 'Error fetching result data:' , error ) ;
169+ } ) ;
302170 } ;
303- } , [ handlePopState , run_id ] ) ;
171+
172+ if ( activeTab === 'results-tree' ) {
173+ getResultsForTree ( ) ;
174+ }
175+
176+ } , [ activeTab , run_id ] ) ;
304177
305178 let passed = 0 , failed = 0 , errors = 0 , xfailed = 0 , xpassed = 0 , skipped = 0 , not_run = 0 ;
306179 let created = 0 ;
0 commit comments