Skip to content

Commit 3f4ea70

Browse files
committed
useTabHook, restore state and add skipHash
When nested, the activeTab should not sync with the location hash. add useTabHook to jenkinsjob analysis Update link composition in filterheatmap provide hash for tabbed views use `${}` syntax Update useTabHook navigation with location include search and path as-is, search is used in a few spots
1 parent 82acace commit 3f4ea70

File tree

6 files changed

+75
-52
lines changed

6 files changed

+75
-52
lines changed

frontend/src/components/classify-failures.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ const ClassifyFailuresTable = ({ filters, run_id }) => {
165165
hideSummary={hideSummary}
166166
hideTestObject={hideTestObject}
167167
testResult={rows[rowIndex].result}
168+
skipHash={true}
168169
/>
169170
}
170171
]

frontend/src/components/result.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ const ResultView = ({
3939
hideSummary=false,
4040
hideTestObject=false,
4141
hideTestHistory=false,
42-
testResult
42+
testResult,
43+
skipHash=false
4344
}) => {
4445
const context = useContext(IbutsuContext);
4546
const { darkTheme } = context;
@@ -83,7 +84,8 @@ const ResultView = ({
8384
// Tab state and navigation hooks/effects
8485
const {activeTab, onTabSelect} = useTabHook(
8586
['summary', 'testHistory', 'testObject', ...artifactKeys()],
86-
defaultTab
87+
defaultTab,
88+
skipHash
8789
);
8890

8991
useEffect(() => {
@@ -419,6 +421,7 @@ ResultView.propTypes = {
419421
hideTestObject: PropTypes.bool,
420422
hideTestHistory: PropTypes.bool,
421423
testResult: PropTypes.object,
424+
skipHash: PropTypes.bool,
422425
};
423426

424427
export default ResultView;

frontend/src/components/tabHook.js

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,30 @@
1-
import { useState, useEffect } from 'react';
1+
import { useEffect, useState } from 'react';
22
import { useLocation, useNavigate } from 'react-router-dom';
33

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

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

99
const location = useLocation();
1010
const navigate = useNavigate();
11-
const currentHash = location.hash.slice(1);
12-
const activeTab = validTabIndicies.includes(currentHash) ? currentHash : defaultTab;
11+
const [activeTab, setActiveTab] = useState(defaultTab);
12+
13+
useEffect(() => {
14+
if (!skipHash) {
15+
const currentHash = location.hash.slice(1);
16+
setActiveTab(validTabIndicies.includes(currentHash) ? currentHash : defaultTab);
17+
}
18+
}, [defaultTab, location.hash, skipHash, validTabIndicies]);
1319

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

1925
const onTabSelect = (_, tabIndex) => {
20-
navigate(`#${tabIndex}`);
26+
if (!skipHash) {navigate(`${location.pathname}${location.search}#${tabIndex}`);};
27+
setActiveTab(tabIndex);
2128
};
2229

2330
return {activeTab, onTabSelect};

frontend/src/run.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import ClassifyFailuresTable from './components/classify-failures';
5555
import ArtifactTab from './components/artifact-tab';
5656
import { IbutsuContext } from './services/context';
5757
import { useTabHook } from './components/tabHook';
58+
import PropTypes from 'prop-types';
5859

5960

6061
const COLUMNS = ['Test', 'Run', 'Result', 'Duration', 'Started'];
@@ -238,7 +239,7 @@ const Run = ({defaultTab='summary'}) => {
238239
}
239240
{isRunValid &&
240241
<Tabs activeKey={activeTab} onSelect={onTabSelect} isBox>
241-
<Tab eventKey="summary" title={<TabTitle icon={<InfoCircleIcon />} text="Summary" />}>
242+
<Tab key= "summary" eventKey="summary" title={<TabTitle icon={<InfoCircleIcon />} text="Summary" />}>
242243
<Card>
243244
<CardBody style={{ padding: 0 }} id="run-detail">
244245
<Grid>
@@ -432,7 +433,7 @@ const Run = ({defaultTab='summary'}) => {
432433
</CardBody>
433434
</Card>
434435
</Tab>
435-
<Tab eventKey="results-list" title={<TabTitle icon={<CatalogIcon />} text="Results List" />}>
436+
<Tab key="results-list" eventKey="results-list" title={<TabTitle icon={<CatalogIcon />} text="Results List" />}>
436437
<Card className="pf-u-mt-lg">
437438
<CardHeader>
438439
<Flex style={{ width: '100%' }}>
@@ -489,7 +490,7 @@ const Run = ({defaultTab='summary'}) => {
489490
}
490491
</CardHeader>
491492
<CardBody style={{ backgroundColor: 'var(--pf-v5-c-card--BackgroundColor)', paddingTop: '1.2em' }}>
492-
<ResultView testResult={testResult} defaultTab='summary' />
493+
<ResultView testResult={testResult} skipHash={true} />
493494
</CardBody>
494495
</Card>
495496
}

frontend/src/views/jenkinsjobanalysis.js

Lines changed: 47 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useContext, useEffect, useState } from 'react';
1+
import React, { useContext, useEffect, useState, useMemo } from 'react';
22
import PropTypes from 'prop-types';
33
import {
44
Switch,
@@ -16,7 +16,8 @@ import FilterHeatmapWidget from '../widgets/filterheatmap';
1616
import { HEATMAP_MAX_BUILDS } from '../constants';
1717
import { IbutsuContext } from '../services/context';
1818
import ParamDropdown from '../components/param-dropdown';
19-
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
19+
import { useSearchParams } from 'react-router-dom';
20+
import { useTabHook } from '../components/tabHook';
2021

2122
const DEFAULT_BAR = 8;
2223

@@ -25,17 +26,14 @@ const LONG_BUILDS = [...SHORT_BUILDS, 70, 150];
2526

2627
const PF_BACK_100 = 'var(--pf-v5-global--BackgroundColor--100)';
2728

28-
const JenkinsJobAnalysisView =(props) => {
29+
const JenkinsJobAnalysisView =({view, defaultTab='heatmap'}) => {
2930
const context = useContext(IbutsuContext);
3031
const {primaryObject} = context;
31-
const {view} = props;
32-
const location = useLocation();
33-
const navigate = useNavigate();
32+
3433
const [searchParams] = useSearchParams();
3534

3635
const [isAreaChart, setIsAreaChart] = useState(false);
3736
const [isLoading, setIsLoading] = useState();
38-
const [activeTab, setActiveTab] = useState('heatmap');
3937

4038
const [barWidth, setBarWidth] = useState(DEFAULT_BAR);
4139
const [builds, setBuilds] = useState(20);
@@ -45,6 +43,12 @@ const JenkinsJobAnalysisView =(props) => {
4543
const [barchartParams, setBarchartParams] = useState({});
4644
const [linechartParams, setLinechartParams] = useState({});
4745

46+
// Tab state and navigation hooks/effects
47+
const {activeTab, onTabSelect} = useTabHook(
48+
['heatmap', 'overall-health', 'build-durations'],
49+
defaultTab
50+
);
51+
4852
useEffect(() => {
4953
// Fetch the widget parameters for heatmap, barchart and linechart
5054
if (view) {
@@ -112,10 +116,35 @@ const JenkinsJobAnalysisView =(props) => {
112116
return color;
113117
};
114118

115-
const onTabSelect = (_, tabIndex) => {
116-
navigate(`${location.pathname}${location.search}#${tabIndex}`);
117-
setActiveTab(tabIndex);
118-
};
119+
const heatmapParam = useMemo(() => {
120+
if (activeTab === 'heatmap') {
121+
return(
122+
<div style={{backgroundColor: PF_BACK_100, float: 'right', clear: 'none', marginBottom: '-2em', padding: '0.2em 1em', width: '30em'}}>
123+
<ParamDropdown
124+
dropdownItems={['Yes', 'No']}
125+
defaultValue={(countSkips ? 'Yes': 'No')}
126+
handleSelect={(value) => setCountSkips(value === 'Yes')}
127+
tooltip="Count skips as failure:"
128+
/>
129+
</div>)
130+
}
131+
}, [activeTab]);
132+
133+
const overallSwitch = useMemo(() => {
134+
if (activeTab === 'overall-health') {
135+
return(
136+
<div style={{backgroundColor: PF_BACK_100, float: 'right', clear: 'none', marginBottom: '-2em', padding: '0.5em 1em'}}>
137+
<Switch
138+
id="bar-chart-switch"
139+
labelOff="Change to Area Chart"
140+
label="Change to Bar Chart"
141+
isChecked={isAreaChart}
142+
onChange={(_, checked) => setIsAreaChart(checked)}
143+
/>
144+
</div>
145+
)
146+
}
147+
}, [activeTab]);
119148

120149
return (
121150
<React.Fragment>
@@ -127,29 +156,10 @@ const JenkinsJobAnalysisView =(props) => {
127156
tooltip="Number of builds:"
128157
/>
129158
</div>
130-
{activeTab === 'heatmap' &&
131-
<div style={{backgroundColor: PF_BACK_100, float: 'right', clear: 'none', marginBottom: '-2em', padding: '0.2em 1em', width: '30em'}}>
132-
<ParamDropdown
133-
dropdownItems={['Yes', 'No']}
134-
defaultValue={(countSkips ? 'Yes': 'No')}
135-
handleSelect={(value) => setCountSkips(value === 'Yes')}
136-
tooltip="Count skips as failure:"
137-
/>
138-
</div>
139-
}
140-
{activeTab === 'overall-health' &&
141-
<div style={{backgroundColor: PF_BACK_100, float: 'right', clear: 'none', marginBottom: '-2em', padding: '0.5em 1em'}}>
142-
<Switch
143-
id="bar-chart-switch"
144-
labelOff="Change to Area Chart"
145-
label="Change to Bar Chart"
146-
isChecked={isAreaChart}
147-
onChange={(_, checked) => setIsAreaChart(checked)}
148-
/>
149-
</div>
150-
}
159+
{heatmapParam}
160+
{overallSwitch}
151161
<Tabs activeKey={activeTab} onSelect={onTabSelect} isBox>
152-
<Tab eventKey='heatmap' title="Heatmap">
162+
<Tab key='heatmap' eventKey='heatmap' title="Heatmap">
153163
{!isLoading && activeTab === 'heatmap' &&
154164
<FilterHeatmapWidget
155165
title={heatmapParams.job_name}
@@ -160,7 +170,7 @@ const JenkinsJobAnalysisView =(props) => {
160170
/>
161171
}
162172
</Tab>
163-
<Tab eventKey='overall-health' title="Overall Health">
173+
<Tab key='overall-health' eventKey='overall-health' title="Overall Health">
164174
{!isLoading && !isAreaChart && activeTab === 'overall-health' &&
165175
<GenericBarWidget
166176
title={'Test counts for ' + barchartParams.job_name}
@@ -211,7 +221,7 @@ const JenkinsJobAnalysisView =(props) => {
211221
/>
212222
}
213223
</Tab>
214-
<Tab eventKey='build-durations' title="Build Duration">
224+
<Tab key='build-durations' eventKey='build-durations' title="Build Duration">
215225
{!isLoading && activeTab === 'build-durations' &&
216226
<GenericAreaWidget
217227
title={'Durations for ' + linechartParams.job_name}
@@ -239,7 +249,8 @@ const JenkinsJobAnalysisView =(props) => {
239249
};
240250

241251
JenkinsJobAnalysisView.propTypes = {
242-
view: PropTypes.object
252+
view: PropTypes.object,
253+
defaultTab: PropTypes.string
243254
};
244255

245256
export default JenkinsJobAnalysisView;

frontend/src/widgets/filterheatmap.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ const FilterHeatmapWidget = (props) => {
6262
const getJenkinsAnalysisLink = () => {
6363
if (includeAnalysisLink && analysisViewId !== null) {
6464
return (
65-
<Link to={`/project/${primaryObject?.id || params?.project}/view/${analysisViewId}?job_name=${params?.job_name}`}>
65+
<Link to={`/project/${primaryObject?.id || params?.project}/view/${analysisViewId}?job_name=${params?.job_name}#heatmap`}>
6666
<Button variant="secondary" title="See analysis" aria-label="See analysis" isInline><ChartLineIcon/></Button>
6767
</Link>
6868
);
@@ -177,10 +177,10 @@ const FilterHeatmapWidget = (props) => {
177177
cellTitle += item.name + ': ' + item.value + '\n';
178178
}
179179
});
180-
contents = <p title={cellTitle}><Link to={'/project/' + primaryObject.id + `/runs/${value[1]}`}>{Math.floor(value[0])}</Link></p>;
180+
contents = <p title={cellTitle}><Link to={`/project/${primaryObject.id}/runs/${value[1]}#summary`}>{Math.floor(value[0])}</Link></p>;
181181
}
182182
else {
183-
contents = <Link to={'/project/' + primaryObject.id + `/runs/${value[1]}`}>{Math.floor(value[0])}</Link>;
183+
contents = <Link to={`/project/${primaryObject.id}/runs/${value[1]}#summary`}>{Math.floor(value[0])}</Link>;
184184
}
185185
}
186186
return <div style={style}>{contents}</div>;
@@ -198,7 +198,7 @@ const FilterHeatmapWidget = (props) => {
198198
renderData.push(values);
199199
values.forEach((item) => {
200200
if (!!item && (item.length > 2) && !!item[3]) {
201-
newLabels.push(<Link to={'/project/' + (params?.project) +`/results?metadata.jenkins.build_number[eq]=${item[3]}&metadata.jenkins.job_name[eq]=` + params?.job_name} key={item[3]}>{item[3]}</Link>);
201+
newLabels.push(<Link to={`/project/${params?.project}/results?metadata.jenkins.build_number[eq]=${item[3]}&metadata.jenkins.job_name[eq]=${params?.job_name}`} key={item[3]}>{item[3]}</Link>);
202202
}
203203
});
204204
if (newLabels.length > labels.length) {

0 commit comments

Comments
 (0)