Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion frontend/src/components/classify-failures.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ const resultToClassificationRow = (result, index, filterFunc) => {
'isOpen': false,
'result': result,
'cells': [
{title: <React.Fragment><Link to={`../results/${result.id}`} relative="Path">{result.test_id}</Link> {markers}</React.Fragment>},
{title: <React.Fragment><Link to={`../results/${result.id}#summary`} relative="Path">{result.test_id}</Link> {markers}</React.Fragment>},
{title: <span className={result.result}>{resultIcon} {toTitleCase(result.result)}</span>},
{title: <React.Fragment>{exceptionBadge}</React.Fragment>},
{title: <ClassificationDropdown testResult={result} />},
Expand Down Expand Up @@ -165,6 +165,7 @@ const ClassifyFailuresTable = ({ filters, run_id }) => {
hideSummary={hideSummary}
hideTestObject={hideTestObject}
testResult={rows[rowIndex].result}
skipHash={true}
/>
}
]
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/fileupload.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const FileUpload = (props) => {
let action = null;
if (data.metadata.run_id) {
const RunButton = () => (
<AlertActionLink component='a' href={'/project/' + (data.metadata.project_id || primaryObject.id) + '/runs/' + data.metadata.run_id}>
<AlertActionLink component='a' href={`/project/${(data.metadata.project_id || primaryObject.id)}/runs/${data.metadata.run_id}#summary`}>
Go to Run
</AlertActionLink>
);
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/ibutsu-header.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,7 @@ const IbutsuHeader = () => {
}, [darkTheme]);

useEffect(() => {
if (selectedProject?.id !== project_id) {

if (project_id && (selectedProject?.id !== project_id)) {
HttpClient.get([Settings.serverUrl, '/project/', project_id])
.then(response => HttpClient.handleResponse(response))
.then((data) => {
Expand All @@ -99,7 +98,8 @@ const IbutsuHeader = () => {
setFilterValue();
setInputValue(data.title);
setIsProjectSelectOpen(false);
});
})
.catch((error) => console.error(error));
}
}, [project_id, selectedProject, setDefaultDashboard, setPrimaryObject, setPrimaryType]);

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/last-passed.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const LastPassed = (props) => {
return (
<React.Fragment>
{resultData &&
<Link target="_blank" rel="noopener noreferrer" to={`../results/${resultData.id}`} relative="Path">
<Link target="_blank" rel="noopener noreferrer" to={`../results/${resultData.id}#summary`} relative="Path">
<Badge isRead>
{new Date(resultData.start_time).toLocaleString()}
</Badge>
Expand Down
97 changes: 29 additions & 68 deletions frontend/src/components/result.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useEffect, useCallback, useContext, useMemo } from 'react';
import React, { useState, useEffect, useContext, useMemo, useCallback } from 'react';
import PropTypes from 'prop-types';

import {
Expand All @@ -18,7 +18,7 @@ import {
import { InfoCircleIcon, CodeIcon, SearchIcon, FileAltIcon } from '@patternfly/react-icons';
import { CodeEditor, Language } from '@patternfly/react-code-editor';

import { Link, useNavigate, useLocation } from 'react-router-dom';
import { Link } from 'react-router-dom';
import Linkify from 'react-linkify';

import * as http from '../services/http';
Expand All @@ -30,89 +30,40 @@ import TabTitle from './tabs';
import TestHistoryTable from './test-history';
import ArtifactTab from './artifact-tab';
import { IbutsuContext } from '../services/context';
import { useTabHook } from './tabHook';

const ResultView = ({
comparisonResults,
defaultTab,
defaultTab='summary',
hideArtifact=false,
hideSummary=false,
hideTestObject=false,
hideTestHistory=false,
testResult
testResult,
skipHash=false
}) => {
const context = useContext(IbutsuContext);
const { darkTheme } = context;

// State
const [artifacts, setArtifacts] = useState([]);
const [testHistoryTable, setTestHistoryTable] = useState(null);

// Hooks
const navigate = useNavigate();
const location = useLocation();
// https://v5-archive.patternfly.org/components/tabs#tabs-linked-to-nav-elements

const getDefaultTab = () => {
if (defaultTab) {
return defaultTab;
}
else if (!hideSummary) {
return 'summary';
}
else if (!!artifactTabs.length > 0) {
return artifactTabs[0].key;
}
else {
return null;
}
};

const getTabIndex = useCallback((defaultValue) => (!!location && location.hash !== '') ? location.hash.substring(1) : defaultValue,[location]);

const getTestHistoryTable = useCallback(() => {
const testHistoryTable = useMemo(() => {
if (comparisonResults !== undefined) {
setTestHistoryTable(
return(
<TestHistoryTable
comparisonResults={comparisonResults}
testResult={testResult}
/>);
} else {
setTestHistoryTable(
return(
<TestHistoryTable testResult={testResult}/>);
}

}, [setTestHistoryTable, comparisonResults, testResult]);
}, [comparisonResults, testResult]);

const [activeTab, setActiveTab] = useState(getTabIndex(getDefaultTab()));


useEffect(() => {
if (activeTab === 'test-history') {
getTestHistoryTable();
}
}, [activeTab, getTestHistoryTable]);

const handlePopState = useCallback(() => {
// Handle browser navigation buttons click
const tabIndex = getTabIndex('summary');
setActiveTab(tabIndex);
}, [getTabIndex, setActiveTab]);

useEffect(()=>{
if (activeTab === 'test-history') {
getTestHistoryTable();
}
window.addEventListener('popstate', handlePopState);
return () => {
window.removeEventListener('popstate', handlePopState);
};
}, [activeTab, getTestHistoryTable, handlePopState]);

const onTabSelect = (_, tabIndex) => {
if (location) {
navigate(`${location.pathname}${location.search}#${tabIndex}`);
}
setActiveTab(tabIndex);
};

const artifactTabs = useMemo(() => artifacts.map((art) => (
<Tab
Expand All @@ -125,6 +76,18 @@ const ResultView = ({
)
), [artifacts]);

const artifactKeys = useCallback(() => {
if (artifactTabs && artifactTabs?.length !== 0) {return(artifactTabs.map((tab) => tab.key));}
else {return([]);}
}, [artifactTabs]);

// Tab state and navigation hooks/effects
const {activeTab, onTabSelect} = useTabHook(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider merging the artifact key extraction into the existing useMemo hook to compute all tab keys at once, avoiding an extra useCallback wrapper function and simplifying the code.

You can simplify the extraction of the artifact keys by avoiding an extra useCallback wrapper when you already have the artifact data. For example, merge the key extraction into a single useMemo that computes all tab keys. This reduces indirection while keeping functionality intact:

const tabKeys = useMemo(() => {
  // Creates an array of tab keys, including artifacts directly
  return ['summary', 'testHistory', 'testObject', ...artifacts.map(art => art.filename)];
}, [artifacts]);

const { activeTab, onTabSelect } = useTabHook(tabKeys, defaultTab, skipHash);

This change removes the extra artifactKeys callback and makes the key computation clear and directly dependent on artifacts.

['summary', 'testHistory', 'testObject', ...artifactKeys()],
defaultTab,
skipHash
);

useEffect(() => {
// Get artifacts when the test result changes
if (testResult) {
Expand All @@ -137,9 +100,6 @@ const ResultView = ({
}
}, [testResult]);

if (activeTab === null) {
setActiveTab(getDefaultTab());
}
let resultIcon = getIconForResult('pending');
let startTime = new Date();
let parameters = <div/>;
Expand All @@ -148,7 +108,7 @@ const ResultView = ({
resultIcon = getIconForResult(testResult.result);
startTime = new Date(testResult.start_time);
parameters = Object.keys(testResult.params).map((key) => <div key={key}>{key} = {testResult.params[key]}</div>);
runLink = <Link to={`../runs/${testResult.run_id}`} relative="Path">{testResult.run_id}</Link>;
runLink = <Link to={`../runs/${testResult.run_id}#summary`} relative="Path">{testResult.run_id}</Link>;
}

const testJson = useMemo(() => JSON.stringify(testResult, null, '\t'), [testResult]);
Expand Down Expand Up @@ -425,15 +385,15 @@ const ResultView = ({
</Card>
</Tab>
}
{!hideArtifact &&
artifactTabs}
{!hideTestHistory &&
<Tab key="test-history" eventKey="test-history" title={<TabTitle icon={<SearchIcon/>} text="Test History"/>}>
<Tab key="testHistory" eventKey="testHistory" title={<TabTitle icon={<SearchIcon/>} text="Test History"/>}>
{testHistoryTable}
</Tab>
}
{!hideArtifact &&
artifactTabs}
{!hideTestObject && testJson &&
<Tab key="test-object" eventKey="test-object" title={<TabTitle icon={<CodeIcon/>} text="Test Object" />}>
<Tab key="testObject" eventKey="testObject" title={<TabTitle icon={<CodeIcon/>} text="Test Object"/>}>
<Card>
<CardBody id='object-card-body'>
<CodeEditor
Expand Down Expand Up @@ -461,6 +421,7 @@ ResultView.propTypes = {
hideTestObject: PropTypes.bool,
hideTestHistory: PropTypes.bool,
testResult: PropTypes.object,
skipHash: PropTypes.bool,
};

export default ResultView;
31 changes: 31 additions & 0 deletions frontend/src/components/tabHook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useEffect, useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';

// Assisted by watsonx Code Assistant
// Code generated by WCA@IBM in this programming language is not approved for use in IBM product development.

export const useTabHook = (validTabIndicies=[], defaultTab='summary', skipHash=false) => {

const location = useLocation();
const navigate = useNavigate();
const [activeTab, setActiveTab] = useState(defaultTab);

useEffect(() => {
if (!skipHash) {
const currentHash = location.hash.slice(1);
setActiveTab(validTabIndicies.includes(currentHash) ? currentHash : defaultTab);
}
}, [defaultTab, location.hash, skipHash, validTabIndicies]);

useEffect(() => {
// catch a bad tab index and use the default
if (!skipHash && !validTabIndicies.includes(location.hash.slice(1))) {navigate(`${location.pathname}${location.search}#${defaultTab}`);}
}, [defaultTab, location, navigate, skipHash, validTabIndicies]);

const onTabSelect = (_, tabIndex) => {
if (!skipHash) {navigate(`${location.pathname}${location.search}#${tabIndex}`);};
setActiveTab(tabIndex);
};

return {activeTab, onTabSelect};
};
9 changes: 4 additions & 5 deletions frontend/src/result.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const Result = () => {
throw new Error('Failed with HTTP code ' + response.status);
}
} catch (error) {
console.log(error);
console.error(error);
}
};
fetchTestResult();
Expand All @@ -53,13 +53,12 @@ const Result = () => {
</TextContent>
</PageSection>
<PageSection>
{!testResult ? (
<Skeleton />
) : (
{testResult ? (
isResultValid ?
<ResultView testResult={testResult} /> :
<EmptyObject headingText="Result not found" returnLink="/results" returnLinkText="Return to results list"/>
)}
) :
<Skeleton />}
</PageSection>
</React.Fragment>
);
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/run-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ const runToRow = (run, filterFunc) => {
}
return {
'cells': [
{title: <React.Fragment><Link to={`${run.id}`}>{run.id}</Link> {badges}</React.Fragment>},
{title: <React.Fragment><Link to={`${run.id}#summary`}>{run.id}</Link> {badges}</React.Fragment>},
{title: round(run.duration) + 's'},
{title: <RunSummary summary={run.summary} />},
{title: created.toLocaleString()},
Expand Down Expand Up @@ -442,7 +442,7 @@ const RunList = () => {
onApplyFilter={applyFilter}
onRemoveFilter={removeFilter}
onClearFilters={clearFilters}
onApplyReport={() => navigate('/project/' + params.project_id + '/reports?' + buildParams(filters).join('&'))}
onApplyReport={() => navigate(`/project/${params.project_id}/reports?${buildParams(filters).join('&')}`)}
onSetPage={setPage}
onSetPageSize={setPageSize}
hideFilters={['project_id']}
Expand Down
Loading
Loading