ui/job-view/details/tabs/TabsPanel.jsx (300 lines of code) (raw):
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Button } from 'reactstrap';
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import {
faAngleDown,
faAngleUp,
faTimes,
} from '@fortawesome/free-solid-svg-icons';
import { thEvents } from '../../../helpers/constants';
import JobArtifacts from '../../../shared/JobArtifacts';
import JobTestGroups from '../JobTestGroups';
import { clearSelectedJob } from '../../redux/stores/selectedJob';
import { pinJob, addBug } from '../../redux/stores/pinnedJobs';
import FailureSummaryTab from '../../../shared/tabs/failureSummary/FailureSummaryTab';
import PerformanceTab from './PerformanceTab';
import AnnotationsTab from './AnnotationsTab';
import SimilarJobsTab from './SimilarJobsTab';
const showTabsFromProps = (props) => {
const { perfJobDetail } = props;
return {
showPerf: !!perfJobDetail.length,
};
};
class TabsPanel extends React.Component {
constructor(props) {
super(props);
this.state = {
tabIndex: 0,
enableTestGroupsTab: false,
};
this.handleEnableTestGroupsTab = this.handleEnableTestGroupsTab.bind(this);
}
static getDerivedStateFromProps(props, state) {
const { perfJobDetail, selectedJobFull } = props;
// This fires every time the props change. But we only want to figure out the new default
// tab when we get a new job. However, the job could change, then later, the perf details fetch
// returns. So we need to check for a change in the size of the perfJobDetail too.
if (
state.jobId !== selectedJobFull.id ||
state.perfJobDetailSize !== perfJobDetail.length
) {
const tabIndex = TabsPanel.getDefaultTabIndex(
selectedJobFull.resultStatus,
props,
);
return {
tabIndex,
// Every time we select a different job we need to let the component
// let us know if we should enable the tab
enableTestGroupsTab: false,
jobId: selectedJobFull.id,
perfJobDetailSize: perfJobDetail.length,
};
}
return {};
}
componentDidMount() {
window.addEventListener(thEvents.selectNextTab, this.onSelectNextTab);
}
componentWillUnmount() {
window.removeEventListener(thEvents.selectNextTab, this.onSelectNextTab);
}
onSelectNextTab = () => {
const { tabIndex } = this.state;
const nextIndex = tabIndex + 1;
const tabCount = TabsPanel.getTabNames(showTabsFromProps(this.props))
.length;
this.setState({ tabIndex: nextIndex < tabCount ? nextIndex : 0 });
};
static getDefaultTabIndex(status, props) {
const { showPerf } = showTabsFromProps(props);
let idx = 0;
const tabNames = TabsPanel.getTabNames({ showPerf });
const tabIndexes = tabNames.reduce(
(acc, name) => ({ ...acc, [name]: idx++ }),
{},
);
let tabIndex = showPerf ? tabIndexes.perf : tabIndexes.artifacts;
if (['busted', 'testfailed', 'exception'].includes(status)) {
tabIndex = tabIndexes.failure;
}
return tabIndex;
}
static getTabNames({ showPerf }) {
// The order in here has to match the order within the render method
return [
'artifacts',
'failure',
'annotations',
'similar',
'perf',
'test-groups',
].filter((name) => !(name === 'perf' && !showPerf));
}
handleEnableTestGroupsTab = (stateOfTab) => {
this.setState({ enableTestGroupsTab: stateOfTab });
};
setTabIndex = (tabIndex) => {
this.setState({ tabIndex });
};
render() {
const {
jobArtifactsLoading,
jobDetails,
jobLogUrls,
logParseStatus,
bugs,
perfJobDetail,
jobRevision,
classifications,
togglePinBoardVisibility,
isPinBoardVisible,
pinnedJobs,
classificationMap,
logViewerFullUrl,
clearSelectedJob,
selectedJobFull,
currentRepo,
pinJob,
addBug,
taskId,
rootUrl,
} = this.props;
const { enableTestGroupsTab, tabIndex } = this.state;
const countPinnedJobs = Object.keys(pinnedJobs).length;
const { showPerf } = showTabsFromProps(this.props);
return (
<div id="tabs-panel" role="region" aria-label="Job">
<Tabs
selectedTabClassName="selected-tab"
selectedIndex={tabIndex}
onSelect={this.setTabIndex}
>
<TabList className="tab-headers">
<span className="tab-header-tabs">
<Tab>Artifacts and Debugging Tools</Tab>
<Tab>Failure Summary</Tab>
<Tab>Annotations</Tab>
<Tab>Similar Jobs</Tab>
{showPerf && <Tab>Performance</Tab>}
{enableTestGroupsTab ? (
<Tab>Test Groups</Tab>
) : (
<Tab disabled>Test Groups</Tab>
)}
</span>
<span
id="tab-header-buttons"
className="details-panel-controls pull-right"
>
<Button
id="pinboard-btn"
className="btn pinboard-btn-text"
onClick={togglePinBoardVisibility}
title={
isPinBoardVisible ? 'Close the pinboard' : 'Open the pinboard'
}
>
PinBoard
{!!countPinnedJobs && (
<div
id="pin-count-group"
title={`You have ${countPinnedJobs} job${
countPinnedJobs > 1 ? 's' : ''
} pinned`}
className={`${
countPinnedJobs > 99 ? 'pin-count-group-3-digit' : ''
}`}
>
<div
className={`pin-count-text ${
countPinnedJobs > 99 ? 'pin-count-group-3-digit' : ''
}`}
>
{countPinnedJobs}
</div>
</div>
)}
<FontAwesomeIcon
icon={isPinBoardVisible ? faAngleDown : faAngleUp}
title={isPinBoardVisible ? 'expand' : 'collapse'}
className="ml-1"
/>
</Button>
<Button
onClick={() => clearSelectedJob(countPinnedJobs)}
className="btn details-panel-close-btn bg-transparent border-0"
aria-label="Close"
>
<FontAwesomeIcon icon={faTimes} title="Close" />
</Button>
</span>
</TabList>
<TabPanel>
<JobArtifacts
jobDetails={jobDetails}
jobArtifactsLoading={jobArtifactsLoading}
repoName={currentRepo.name}
selectedJob={selectedJobFull}
/>
</TabPanel>
<TabPanel>
<FailureSummaryTab
selectedJob={selectedJobFull}
jobLogUrls={jobLogUrls}
logParseStatus={logParseStatus}
logViewerFullUrl={logViewerFullUrl}
addBug={addBug}
pinJob={pinJob}
currentRepo={currentRepo}
fontSize="font-size-11"
/>
</TabPanel>
<TabPanel>
<AnnotationsTab
classificationMap={classificationMap}
classifications={classifications}
bugs={bugs}
selectedJobFull={selectedJobFull}
/>
</TabPanel>
<TabPanel>
<SimilarJobsTab
repoName={currentRepo.name}
classificationMap={classificationMap}
selectedJobFull={selectedJobFull}
/>
</TabPanel>
{showPerf && (
<TabPanel>
<PerformanceTab
key={selectedJobFull.id}
selectedJobFull={selectedJobFull}
currentRepo={currentRepo}
repoName={currentRepo.name}
jobDetails={jobDetails}
perfJobDetail={perfJobDetail}
revision={jobRevision}
/>
</TabPanel>
)}
{enableTestGroupsTab ? (
<TabPanel>
<JobTestGroups
taskId={taskId}
rootUrl={rootUrl}
notifyTestGroupsAvailable={this.handleEnableTestGroupsTab}
/>
</TabPanel>
) : (
<TabPanel disabled forceRender>
<JobTestGroups
taskId={taskId}
rootUrl={rootUrl}
notifyTestGroupsAvailable={this.handleEnableTestGroupsTab}
/>
</TabPanel>
)}
</Tabs>
</div>
);
}
}
TabsPanel.propTypes = {
classificationMap: PropTypes.shape({}).isRequired,
jobDetails: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
jobArtifactsLoading: PropTypes.bool,
classifications: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
togglePinBoardVisibility: PropTypes.func.isRequired,
isPinBoardVisible: PropTypes.bool.isRequired,
pinnedJobs: PropTypes.shape({}).isRequired,
bugs: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
clearSelectedJob: PropTypes.func.isRequired,
selectedJobFull: PropTypes.shape({}).isRequired,
currentRepo: PropTypes.shape({}).isRequired,
perfJobDetail: PropTypes.arrayOf(PropTypes.shape({})),
jobRevision: PropTypes.string,
jobLogUrls: PropTypes.arrayOf(PropTypes.shape({})),
logParseStatus: PropTypes.string,
logViewerFullUrl: PropTypes.string,
taskId: PropTypes.string.isRequired,
rootUrl: PropTypes.string.isRequired,
};
TabsPanel.defaultProps = {
jobArtifactsLoading: false,
jobLogUrls: [],
logParseStatus: 'pending',
perfJobDetail: [],
jobRevision: null,
logViewerFullUrl: null,
};
const mapStateToProps = ({
pinnedJobs: { pinnedJobs, isPinBoardVisible },
}) => ({ pinnedJobs, isPinBoardVisible });
export default connect(mapStateToProps, { clearSelectedJob, pinJob, addBug })(
TabsPanel,
);