ui/push-health/details/DetailsPanel.jsx (216 lines of code) (raw):
import React from 'react';
import PropTypes from 'prop-types';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTimes } from '@fortawesome/free-solid-svg-icons';
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
import { Col, Button, Spinner } from 'reactstrap';
import { getArtifactsUrl, getLogViewerUrl } from '../../helpers/url';
import { formatArtifacts } from '../../helpers/display';
import { addAggregateFields } from '../../helpers/job';
import { getData } from '../../helpers/http';
import JobModel from '../../models/job';
import LogviewerTab from '../../shared/tabs/LogviewerTab';
import FailureSummaryTab from '../../shared/tabs/failureSummary/FailureSummaryTab';
import JobArtifacts from '../../shared/JobArtifacts';
class DetailsPanel extends React.Component {
constructor(props) {
super(props);
// used to cancel all the ajax requests triggered by selectTask
this.selectTaskController = null;
this.state = {
selectedTaskFull: null,
taskDetails: [],
taskDetailLoading: false,
tabIndex: 0,
};
}
componentDidMount() {
const { selectedTask } = this.props;
if (selectedTask) {
this.selectTask(selectedTask);
}
}
componentDidUpdate(prevProps) {
const { selectedTask } = this.props;
if (selectedTask && prevProps.selectedTask) {
const {
id: prevId,
state: prevState,
result: prevResult,
failure_classification_id: prevFci,
} = prevProps.selectedTask;
const {
id,
state,
result,
failure_classification_id: fci,
} = selectedTask;
// Check the id in case the user switched to a new task.
// But also check some of the fields of the selected task,
// in case they have changed due to polling.
if (
prevId !== id ||
prevState !== state ||
prevResult !== result ||
prevFci !== fci
) {
this.selectTask();
}
} else if (selectedTask && selectedTask !== prevProps.selectedTask) {
this.selectTask();
}
}
setTabIndex = (tabIndex) => {
this.setState({ tabIndex });
};
selectTask = async () => {
const { currentRepo, selectedTask } = this.props;
this.setState({ taskDetails: [], taskDetailLoading: true }, () => {
if (this.selectTaskController !== null) {
// Cancel the in-progress fetch requests.
this.selectTaskController.abort();
}
this.selectTaskController = new AbortController();
const taskPromise = JobModel.get(
currentRepo.name,
selectedTask.id,
this.selectTaskController.signal,
);
const artifactsParams = {
jobId: selectedTask.id,
taskId: selectedTask.task_id,
run: selectedTask.run_id,
rootUrl: currentRepo.tc_root_url,
};
const artifactsPromise = getData(
getArtifactsUrl(artifactsParams),
this.selectTaskController.signal,
);
let builtFromArtifactPromise;
if (
currentRepo.name === 'comm-central' ||
currentRepo.name === 'try-comm-central'
) {
builtFromArtifactPromise = getData(
getArtifactsUrl({
...artifactsParams,
...{ artifactPath: 'public/build/built_from.json' },
}),
);
}
Promise.all([taskPromise, artifactsPromise, builtFromArtifactPromise])
.then(
async ([taskResult, artifactsResult, builtFromArtifactResult]) => {
const selectedTaskFull = taskResult;
addAggregateFields(selectedTaskFull);
let taskDetails = artifactsResult.data.artifacts
? formatArtifacts(artifactsResult.data.artifacts, {
...artifactsParams,
})
: [];
if (
builtFromArtifactResult &&
!builtFromArtifactResult.failureStatus
) {
taskDetails = [...taskDetails, ...builtFromArtifactResult.data];
}
this.setState({
selectedTaskFull,
taskDetails,
taskDetailLoading: false,
});
},
)
.finally(() => {
this.selectTaskController = null;
});
});
};
render() {
const { currentRepo, closeDetails } = this.props;
const {
selectedTaskFull,
taskDetails,
taskDetailLoading,
tabIndex,
} = this.state;
return (
<div className="w-100">
{taskDetailLoading && <Spinner />}
{!!selectedTaskFull && !taskDetailLoading && (
<div role="region" aria-label="Task" className="d-flex ml-5">
<Tabs
selectedIndex={tabIndex}
onSelect={this.setTabIndex}
className="w-100 h-100 ml-1 mr-5 mb-2 border p-3 bg-white"
selectedTabClassName="selected-detail-tab"
>
<TabList className="pl-0 w-100 list-inline">
<span className="d-flex justify-content-between w-100">
<span>
<Tab className="font-weight-bold text-secondary list-inline-item">
Failure Summary
</Tab>
<Tab className="ml-3 font-weight-bold text-secondary list-inline-item pointable">
Log Viewer
</Tab>
<Tab className="ml-3 font-weight-bold text-secondary list-inline-item pointable">
Artifacts and Debugging Tools
</Tab>
</span>
<Button
onClick={closeDetails}
outline
className="border-0"
title="Close details view of this task"
>
<FontAwesomeIcon icon={faTimes} className="mr-1" />
</Button>
</span>
</TabList>
<div className="w-100 tab-content">
<TabPanel>
<FailureSummaryTab
selectedJob={selectedTaskFull}
jobLogUrls={selectedTaskFull.logs}
logParseStatus="unknown"
logViewerFullUrl={getLogViewerUrl(
selectedTaskFull.id,
currentRepo.name,
)}
currentRepo={currentRepo}
developerMode
/>
</TabPanel>
<TabPanel>
<LogviewerTab
selectedTaskFull={selectedTaskFull}
repoName={currentRepo.name}
/>
</TabPanel>
<TabPanel className="overflow-auto h-100">
<Col className="ml-2">
<JobArtifacts
jobDetails={taskDetails}
repoName={currentRepo.name}
selectedJob={selectedTaskFull}
/>
</Col>
</TabPanel>
</div>
</Tabs>
</div>
)}
</div>
);
}
}
DetailsPanel.propTypes = {
currentRepo: PropTypes.shape({}).isRequired,
closeDetails: PropTypes.func.isRequired,
selectedTask: PropTypes.shape({}),
};
DetailsPanel.defaultProps = {
selectedTask: null,
};
export default DetailsPanel;