167 lines
5.7 KiB
JavaScript
167 lines
5.7 KiB
JavaScript
import React, { Component, PropTypes } from 'react';
|
|
import { ResultItem } from '@jenkins-cd/design-language';
|
|
import { calculateFetchAll, calculateLogUrl } from '../util/UrlUtils';
|
|
|
|
import LogConsole from './LogConsole';
|
|
|
|
const { object, func, string, bool } = PropTypes;
|
|
|
|
export default class Node extends Component {
|
|
constructor(props) {
|
|
super(props);
|
|
const node = this.expandAnchor(props);
|
|
this.state = { isFocused: node.isFocused };
|
|
}
|
|
|
|
componentWillMount() {
|
|
const { nodesBaseUrl, fetchLog } = this.props;
|
|
const { config = {} } = this.context;
|
|
const node = this.expandAnchor(this.props);
|
|
if (node && node.isFocused) {
|
|
const fetchAll = node.fetchAll;
|
|
const mergedConfig = { ...config, node, nodesBaseUrl, fetchAll };
|
|
fetchLog(mergedConfig);
|
|
}
|
|
}
|
|
|
|
componentWillReceiveProps(nextProps) {
|
|
const { logs, nodesBaseUrl, fetchLog, followAlong } = nextProps;
|
|
// Changing state of the node we want to collapse automatic
|
|
if (nextProps.node.state !== this.props.node.state && nextProps.node.state === 'FINISHED') {
|
|
if (this.state.isFocused) {
|
|
this.setState({ isFocused: false });
|
|
}
|
|
}
|
|
const { config = {} } = this.context;
|
|
const node = this.expandAnchor(nextProps);
|
|
const fetchAll = node.fetchAll;
|
|
const mergedConfig = { ...config, node, nodesBaseUrl, fetchAll };
|
|
if (logs && logs !== this.props.logs || fetchAll) {
|
|
const key = calculateLogUrl(mergedConfig);
|
|
const log = logs ? logs[key] : null;
|
|
if (log && log !== null) {
|
|
// we may have a streaming log
|
|
const number = Number(log.newStart);
|
|
// in case we doing karaoke we want to see more logs
|
|
if (number > 0 && followAlong) {
|
|
mergedConfig.newStart = log.newStart;
|
|
// kill current timeout if any
|
|
this.clearThisTimeout();
|
|
this.timeout = setTimeout(() => fetchLog({ ...mergedConfig }), 1000);
|
|
}
|
|
} else if (!log && fetchAll) { // in case the link "full log" is clicked we need to trigger a refetch
|
|
this.clearThisTimeout();
|
|
this.timeout = setTimeout(() => fetchLog({ ...mergedConfig }), 1000);
|
|
}
|
|
}
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
this.clearThisTimeout();
|
|
}
|
|
|
|
clearThisTimeout() {
|
|
if (this.timeout) {
|
|
clearTimeout(this.timeout);
|
|
}
|
|
}
|
|
/*
|
|
* Calculate whether we need to expand the step due to linking.
|
|
* When we trigger a log-0 that means we want to see the full log
|
|
*/
|
|
expandAnchor(props) {
|
|
const { node, location: { hash: anchorName } } = props;
|
|
const isFocused = true;
|
|
const fetchAll = calculateFetchAll(props);
|
|
const general = { ...node, fetchAll };
|
|
// e.g. #step-10-log-1 or #step-10
|
|
if (anchorName) {
|
|
const stepReg = /step-([0-9]{1,})?($|-log-([0-9]{1,})$)/;
|
|
const match = stepReg.exec(anchorName);
|
|
|
|
if (match && match[1] && match[1] === node.id) {
|
|
return { ...general, isFocused };
|
|
}
|
|
} else if (this.state && this.state.isFocused) {
|
|
return { ...general, isFocused };
|
|
}
|
|
return general;
|
|
}
|
|
|
|
render() {
|
|
const { logs, nodesBaseUrl, fetchLog, followAlong } = this.props;
|
|
const node = this.expandAnchor(this.props);
|
|
const fetchAll = node.fetchAll;
|
|
// Early out
|
|
if (!node || !fetchLog) {
|
|
return null;
|
|
}
|
|
const { config = {} } = this.context;
|
|
const {
|
|
isFocused = false,
|
|
title,
|
|
durationInMillis,
|
|
result,
|
|
id,
|
|
state,
|
|
} = node;
|
|
|
|
const resultRun = result === 'UNKNOWN' || !result ? state : result;
|
|
const log = logs ? logs[calculateLogUrl({ ...config, node, nodesBaseUrl, fetchAll })] : null;
|
|
const getLogForNode = () => {
|
|
// in case we do not have logs, or the logs are have no information attached we refetch them
|
|
if (!log || !log.logArray) {
|
|
fetchLog({ ...config, node, nodesBaseUrl });
|
|
}
|
|
this.setState({ isFocused: true });
|
|
};
|
|
const runResult = resultRun.toLowerCase();
|
|
const scrollToBottom =
|
|
resultRun.toLowerCase() === 'failure'
|
|
|| (resultRun.toLowerCase() === 'running' && followAlong)
|
|
;
|
|
const logProps = {
|
|
scrollToBottom,
|
|
key: id,
|
|
prefix: `step-${id}-`,
|
|
};
|
|
if (log) {
|
|
// in follow along the Full Log button should not be shown, since you see everything already
|
|
if (followAlong) {
|
|
logProps.hasMore = false;
|
|
} else {
|
|
logProps.hasMore = log.hasMore;
|
|
}
|
|
logProps.logArray = log.logArray;
|
|
}
|
|
|
|
const logConsoleClass = `logConsole step-${id}`;
|
|
|
|
return (<div className={logConsoleClass}>
|
|
<ResultItem
|
|
key={id}
|
|
result={runResult}
|
|
expanded={isFocused}
|
|
label={title}
|
|
onExpand={getLogForNode}
|
|
durationMillis={durationInMillis}
|
|
>
|
|
{ log && <LogConsole {...logProps} /> }
|
|
|
|
{ !log && <span>
|
|
|
|
</span> }
|
|
</ResultItem>
|
|
</div>);
|
|
}
|
|
}
|
|
|
|
Node.propTypes = {
|
|
node: object.isRequired,
|
|
followAlong: bool,
|
|
logs: object,
|
|
location: object,
|
|
fetchLog: func,
|
|
nodesBaseUrl: string,
|
|
};
|