Skip to content

Commit a48cb1b

Browse files
committed
Move buildTree, convert Run to useTabHook
1 parent bae9d90 commit a48cb1b

File tree

2 files changed

+144
-185
lines changed

2 files changed

+144
-185
lines changed

frontend/src/run.js

Lines changed: 58 additions & 185 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useEffect, useCallback, useContext } from 'react';
1+
import React, { useState, useEffect, useCallback, useContext, useMemo } from 'react';
22
import { useParams, useNavigate, useLocation, Link } from 'react-router-dom';
33

44
import {
@@ -29,29 +29,23 @@ import {
2929
import {
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';
4540
import { CodeEditor, Language } from '@patternfly/react-code-editor';
4641

4742
import { HttpClient } from './services/http';
4843
import { Settings } from './settings';
4944
import {
50-
cleanPath,
5145
getSpinnerRow,
52-
processPyTestPath,
5346
resultToRow,
54-
round
47+
round,
48+
buildResultsTree
5549
} from './utilities';
5650
import EmptyObject from './components/empty-object';
5751
import FilterTable from './components/filtertable';
@@ -60,114 +54,13 @@ import TabTitle from './components/tabs';
6054
import ClassifyFailuresTable from './components/classify-failures';
6155
import ArtifactTab from './components/artifact-tab';
6256
import { 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

16560
const 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

Comments
 (0)