Compare commits
40 Commits
master
...
feature/JE
Author | SHA1 | Date |
---|---|---|
Cliff Meyers | de70f06aab | |
Cliff Meyers | cc429589c0 | |
Cliff Meyers | 11e065f7b8 | |
James William Dumay | 54aeee582b | |
James William Dumay | 1b2416c142 | |
Thorsten Scherler | b487981981 | |
Cliff Meyers | 466ca34cc3 | |
Cliff Meyers | 2d656ec22a | |
vivek | 5ceaaebb0a | |
Cliff Meyers | c2ee18aed0 | |
Cliff Meyers | 34ee8f2df3 | |
Thorsten Scherler | 6e60171920 | |
Cliff Meyers | e257a5ef51 | |
Vivek Pandey | e28c77e13a | |
Cliff Meyers | 7260759b0e | |
Cliff Meyers | 22a2c93676 | |
Cliff Meyers | cfd82f0613 | |
Cliff Meyers | c1d7e4ca2e | |
Cliff Meyers | 74bc89dfb0 | |
Thorsten Scherler | 2b71744a94 | |
Thorsten Scherler | 585ea88e0d | |
Thorsten Scherler | e50fb5c616 | |
Thorsten Scherler | 4757643650 | |
Thorsten Scherler | 195ed2fa17 | |
Thorsten Scherler | 6bfc400a20 | |
Thorsten Scherler | 7993c8f2e2 | |
Thorsten Scherler | 430fd3caaa | |
Thorsten Scherler | 82db85e07d | |
Thorsten Scherler | 85fdcf203c | |
Thorsten Scherler | 7d3e1188a7 | |
Thorsten Scherler | fa1bfcc8ad | |
Thorsten Scherler | 359253262b | |
Thorsten Scherler | a8fab9a2a4 | |
Thorsten Scherler | 95da276c51 | |
Thorsten Scherler | 825c06b3bc | |
Thorsten Scherler | 51ed61c95f | |
Thorsten Scherler | cb3eef2400 | |
Thorsten Scherler | 2b54b4c03c | |
Thorsten Scherler | 89b7aed2a9 | |
Thorsten Scherler | 25c00598bf |
|
@ -1,8 +1,12 @@
|
|||
Related to issue # .
|
||||
**Decription**
|
||||
|
||||
Summary of this pull request:
|
||||
.
|
||||
.
|
||||
.
|
||||
**Submitter checklist**
|
||||
- [ ] Change is code complete and matches issue description
|
||||
- [ ] Apppropriate unit or acceptance tests or explaination to why this change has no tests
|
||||
|
||||
**Reviewer checklist**
|
||||
- [ ] Run the changes and verified the change matches the issue description
|
||||
- [ ] Reviewed the code
|
||||
- [ ] Verified that the appropriate tests have been written or valid explaination given
|
||||
|
||||
@reviewbybees
|
||||
|
|
|
@ -2,6 +2,7 @@ import React, { Component, PropTypes } from 'react';
|
|||
import { CommitHash, ReadableDate } from '@jenkins-cd/design-language';
|
||||
import { LiveStatusIndicator, WeatherIcon } from '@jenkins-cd/design-language';
|
||||
import RunPipeline from './RunPipeline.jsx';
|
||||
import { buildRunDetailsUrl } from '../util/UrlUtils';
|
||||
|
||||
const { object } = PropTypes;
|
||||
|
||||
|
@ -22,6 +23,7 @@ export default class Branches extends Component {
|
|||
location,
|
||||
pipeline: {
|
||||
name: pipelineName,
|
||||
fullName,
|
||||
organization,
|
||||
},
|
||||
},
|
||||
|
@ -29,23 +31,26 @@ export default class Branches extends Component {
|
|||
const {
|
||||
latestRun: { id, result, startTime, endTime, changeSet, state, commitId, estimatedDurationInMillis },
|
||||
weatherScore,
|
||||
name,
|
||||
name: branchName,
|
||||
} = data;
|
||||
const url = `/organizations/${organization}/${pipelineName}/detail/${name}/${id}/pipeline`;
|
||||
|
||||
const cleanBranchName = decodeURIComponent(branchName);
|
||||
const url = buildRunDetailsUrl(organization, fullName, cleanBranchName, id, 'pipeline');
|
||||
|
||||
const open = () => {
|
||||
location.pathname = url;
|
||||
router.push(location);
|
||||
};
|
||||
const { msg } = changeSet[0] || {};
|
||||
|
||||
return (<tr key={name} onClick={open} id={`${name}-${id}`} >
|
||||
return (<tr key={cleanBranchName} onClick={open} id={`${cleanBranchName}-${id}`} >
|
||||
<td><WeatherIcon score={weatherScore} /></td>
|
||||
<td onClick={open}>
|
||||
<LiveStatusIndicator result={result === 'UNKNOWN' ? state : result}
|
||||
startTime={startTime} estimatedDuration={estimatedDurationInMillis}
|
||||
/>
|
||||
</td>
|
||||
<td>{decodeURIComponent(name)}</td>
|
||||
<td>{cleanBranchName}</td>
|
||||
<td><CommitHash commitId={commitId} /></td>
|
||||
<td>{msg || '-'}</td>
|
||||
<td><ReadableDate date={endTime} liveUpdate /></td>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { Component, PropTypes } from 'react';
|
||||
import { PipelineGraph } from '@jenkins-cd/design-language';
|
||||
|
||||
const { string, array, object, any } = PropTypes;
|
||||
const { string, array, any, func } = PropTypes;
|
||||
|
||||
|
||||
function badNode(jenkinsNode) {
|
||||
|
@ -149,22 +149,7 @@ export default class PipelineRunGraph extends Component {
|
|||
stages={graphNodes}
|
||||
onNodeClick={
|
||||
(name, id) => {
|
||||
const pathname = this.props.location.pathname;
|
||||
// if path ends with pipeline we simply add the node id
|
||||
if (pathname.endsWith('pipeline/')) {
|
||||
this.props.router.push(`${pathname}${id}`);
|
||||
} else if (pathname.endsWith('pipeline')) {
|
||||
this.props.router.push(`${pathname}/${id}`);
|
||||
} else {
|
||||
// remove last bit and replace it with node
|
||||
const pathArray = pathname.split('/');
|
||||
pathArray.pop();
|
||||
if (pathname.endsWith('/')) {
|
||||
pathArray.pop();
|
||||
}
|
||||
pathArray.shift();
|
||||
this.props.router.push(`${pathArray.join('/')}/${id}`);
|
||||
}
|
||||
this.props.callback(id);
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
@ -179,6 +164,5 @@ PipelineRunGraph.propTypes = {
|
|||
runId: string,
|
||||
nodes: array,
|
||||
node: any,
|
||||
router: object.isRequired, // From react-router
|
||||
location: object.isRequired, // From react-router
|
||||
callback: func,
|
||||
};
|
||||
|
|
|
@ -62,10 +62,15 @@ export default class Pipelines extends Component {
|
|||
headers={headers}
|
||||
>
|
||||
{ pipelineRecords
|
||||
.map(pipeline => <PipelineRowItem
|
||||
key={pipeline.name} pipeline={pipeline}
|
||||
showOrganization={!organization}
|
||||
/>)
|
||||
.map(pipeline => {
|
||||
const key = pipeline._links.self.href;
|
||||
return (
|
||||
<PipelineRowItem
|
||||
key={key} pipeline={pipeline}
|
||||
showOrganization={!organization}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</Table>
|
||||
</article>
|
||||
|
|
|
@ -85,8 +85,11 @@ class RunDetails extends Component {
|
|||
|
||||
const baseUrl = buildRunDetailsUrl(organization, name, branch, runId);
|
||||
|
||||
const currentRun = this.props.runs.filter(
|
||||
(run) => run.id === runId && decodeURIComponent(run.pipeline) === branch)[0];
|
||||
/* eslint-disable arrow-body-style */
|
||||
const currentRun = this.props.runs.filter((run) => {
|
||||
return run.id === runId &&
|
||||
decodeURIComponent(run.pipeline) === branch;
|
||||
})[0];
|
||||
|
||||
currentRun.name = name;
|
||||
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import React, { Component, PropTypes } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Extensions from '@jenkins-cd/js-extensions';
|
||||
import LogConsole from './LogConsole';
|
||||
import * as sse from '@jenkins-cd/sse-gateway';
|
||||
|
||||
import LogToolbar from './LogToolbar';
|
||||
import Steps from './Steps';
|
||||
import {
|
||||
steps as stepsSelector,
|
||||
|
@ -15,89 +17,164 @@ import {
|
|||
} from '../redux';
|
||||
|
||||
import { calculateStepsBaseUrl, calculateRunLogURLObject, calculateNodeBaseUrl } from '../util/UrlUtils';
|
||||
import { calculateNode } from '../util/KaraokeHelper';
|
||||
|
||||
import LogToolbar from './LogToolbar';
|
||||
|
||||
const { string, object, any, func } = PropTypes;
|
||||
|
||||
export class RunDetailsPipeline extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
// we do not want to follow any builds that are finished
|
||||
this.state = { followAlong: props && props.result && props.result.state !== 'FINISHED' };
|
||||
this.listener = {};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
const { fetchNodes, fetchLog, result, fetchSteps } = this.props;
|
||||
const mergedConfig = this.generateConfig(this.props);
|
||||
|
||||
this.mergedConfig = this.generateConfig(this.props);
|
||||
|
||||
const supportsNode = result && result._class === 'io.jenkins.blueocean.service.embedded.rest.PipelineRunImpl';
|
||||
if (supportsNode) {
|
||||
fetchNodes(mergedConfig);
|
||||
fetchNodes(this.mergedConfig);
|
||||
} else {
|
||||
// console.log('fetch the log directly')
|
||||
const logGeneral = calculateRunLogURLObject(mergedConfig);
|
||||
const logGeneral = calculateRunLogURLObject(this.mergedConfig);
|
||||
fetchLog({ ...logGeneral });
|
||||
}
|
||||
|
||||
// Listen for pipeline flow node events.
|
||||
// We filter them only for steps and the end event all other we let pass
|
||||
this.pipelineListener = sse.subscribe('pipeline', (event) => {
|
||||
const onSseEvent = (event) => {
|
||||
const jenkinsEvent = event.jenkins_event;
|
||||
// we turn on refetch so we always fetch a new Node result
|
||||
const refetch = true;
|
||||
switch (jenkinsEvent) {
|
||||
case 'pipeline_step': {
|
||||
// if the step_stage_id has changed we need to change the focus
|
||||
if (event.pipeline_step_stage_id !== mergedConfig.node) {
|
||||
mergedConfig.node = event.pipeline_step_stage_id;
|
||||
fetchNodes({ ...mergedConfig, refetch });
|
||||
} else {
|
||||
fetchSteps({ ...mergedConfig, refetch });
|
||||
// we are using try/catch to throw an early out error
|
||||
try {
|
||||
if (event.pipeline_run_id !== this.props.result.id) {
|
||||
// console.log('early out');
|
||||
throw new Error('exit');
|
||||
}
|
||||
// we turn on refetch so we always fetch a new Node result
|
||||
const refetch = true;
|
||||
switch (jenkinsEvent) {
|
||||
case 'pipeline_step':
|
||||
{
|
||||
// we are not using an early out for the events since we want to refresh the node if we finished
|
||||
if (this.state.followAlong) { // if we do it means we want karaoke
|
||||
// if the step_stage_id has changed we need to change the focus
|
||||
if (event.pipeline_step_stage_id !== this.mergedConfig.node) {
|
||||
// console.log('nodes fetching via sse triggered');
|
||||
delete this.mergedConfig.node;
|
||||
fetchNodes({ ...this.mergedConfig, refetch });
|
||||
} else {
|
||||
// console.log('only steps fetching via sse triggered');
|
||||
fetchSteps({ ...this.mergedConfig, refetch });
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'pipeline_end':
|
||||
{
|
||||
// we always want to refresh if the run has finished
|
||||
fetchNodes({ ...this.mergedConfig, refetch });
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
// //console.log(event);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// we only ignore the exit error
|
||||
if (e.message !== 'exit') {
|
||||
throw e;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'pipeline_end': {
|
||||
fetchNodes({ ...mergedConfig, refetch });
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// console.log(event);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this.listener.sse = sse.subscribe('pipeline', onSseEvent);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// determine scroll area
|
||||
const domNode = ReactDOM.findDOMNode(this.refs.scrollArea);
|
||||
// add both listemer, one to the scroll area and another to the whole document
|
||||
domNode.addEventListener('wheel', this.onScrollHandler, false);
|
||||
document.addEventListener('keydown', this._handleKeys, false);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.params.node !== this.props.params.node) {
|
||||
const config = this.generateConfig(nextProps);
|
||||
this.props.setNode(config);
|
||||
this.props.fetchSteps(config);
|
||||
}
|
||||
const followAlong = this.state.followAlong;
|
||||
this.mergedConfig = this.generateConfig({ ...nextProps, followAlong });
|
||||
|
||||
// we do not want any timeouts if we are not doing karaoke
|
||||
if (!this.state.followAlong && this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
}
|
||||
// calculate if we need to trigger any actions to get into the right state (is plain js for testing reasons)
|
||||
const nodeAction = calculateNode(this.props, nextProps, this.mergedConfig);
|
||||
if (nodeAction && nodeAction.action) {
|
||||
// use updated config
|
||||
this.mergedConfig = nodeAction.config;
|
||||
// we may need to stop following
|
||||
if (this.state.followAlong !== nodeAction.state.followAlong) {
|
||||
this.setState({ followAlong: nodeAction.state.followAlong });
|
||||
}
|
||||
// if we have actions we fire them
|
||||
this.props[nodeAction.action](this.mergedConfig);
|
||||
}
|
||||
// if we only interested in logs (in case of e.g. freestyle)
|
||||
const { logs, fetchLog } = nextProps;
|
||||
if (logs !== this.props.logs) {
|
||||
const mergedConfig = this.generateConfig(nextProps);
|
||||
const logGeneral = calculateRunLogURLObject(mergedConfig);
|
||||
const logGeneral = calculateRunLogURLObject(this.mergedConfig);
|
||||
const log = logs ? logs[logGeneral.url] : null;
|
||||
if (log && log !== null) {
|
||||
// we may have a streaming log
|
||||
const newStart = log.newStart;
|
||||
if (Number(newStart) > 0) {
|
||||
// kill current timeout if any
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = setTimeout(() => fetchLog({ ...logGeneral, newStart }), 1000);
|
||||
// in case we doing karaoke we want to see more logs
|
||||
if (this.state.followAlong) {
|
||||
// kill current timeout if any
|
||||
clearTimeout(this.timeout);
|
||||
// we need to get mpre input from the log stream
|
||||
this.timeout = setTimeout(() => fetchLog({ ...logGeneral, newStart }), 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.pipelineListener) {
|
||||
sse.unsubscribe(this.pipelineListener);
|
||||
delete this.pipelineListener;
|
||||
const domNode = ReactDOM.findDOMNode(this.refs.scrollArea);
|
||||
domNode.removeEventListener('wheel', this._onScrollHandler);
|
||||
document.removeEventListener('keydown', this._handleKeys);
|
||||
if (this.listener.sse) {
|
||||
sse.unsubscribe(this.listener.sse);
|
||||
delete this.listener.sse;
|
||||
}
|
||||
this.props.cleanNodePointer();
|
||||
clearTimeout(this.timeout);
|
||||
}
|
||||
|
||||
// need to register handler to step out of karaoke mode
|
||||
// we bail out on scroll up
|
||||
onScrollHandler(elem) {
|
||||
if (elem.deltaY < 0 && this.state.followAlong) {
|
||||
this.setState({ followAlong: false });
|
||||
}
|
||||
}
|
||||
// we bail out on arrow_up key
|
||||
_handleKeys(event) {
|
||||
if (event.keyCode === 38 && this.state.followAlong) {
|
||||
this.setState({ followAlong: false });
|
||||
}
|
||||
}
|
||||
|
||||
generateConfig(props) {
|
||||
const {
|
||||
config = {},
|
||||
} = this.context;
|
||||
const followAlong = this.state.followAlong;
|
||||
const {
|
||||
isMultiBranch,
|
||||
params: { pipeline: name, branch, runId, node: nodeParam },
|
||||
|
@ -109,7 +186,8 @@ export class RunDetailsPipeline extends Component {
|
|||
}
|
||||
// if we have a node param we do not want the calculation of the focused node
|
||||
const node = nodeParam || nodeReducer.id;
|
||||
const mergedConfig = { ...config, name, branch, runId, isMultiBranch, node, nodeReducer };
|
||||
|
||||
const mergedConfig = { ...config, name, branch, runId, isMultiBranch, node, nodeReducer, followAlong };
|
||||
return mergedConfig;
|
||||
}
|
||||
|
||||
|
@ -127,43 +205,84 @@ export class RunDetailsPipeline extends Component {
|
|||
} = this.props;
|
||||
|
||||
const {
|
||||
result,
|
||||
state,
|
||||
result,
|
||||
state,
|
||||
} = resultMeta;
|
||||
const resultRun = result === 'UNKNOWN' || !result ? state : result;
|
||||
const scrollToBottom = resultRun.toLowerCase() === 'failure' || resultRun.toLowerCase() === 'running';
|
||||
const followAlong = this.state.followAlong;
|
||||
// in certain cases we want that the log component will scroll to the end of a log
|
||||
const scrollToBottom =
|
||||
resultRun.toLowerCase() === 'failure'
|
||||
|| (resultRun.toLowerCase() === 'running' && followAlong)
|
||||
;
|
||||
|
||||
const mergedConfig = this.generateConfig(this.props);
|
||||
|
||||
const nodeKey = calculateNodeBaseUrl(mergedConfig);
|
||||
const key = calculateStepsBaseUrl(mergedConfig);
|
||||
const logGeneral = calculateRunLogURLObject(mergedConfig);
|
||||
const nodeKey = calculateNodeBaseUrl(this.mergedConfig);
|
||||
const key = calculateStepsBaseUrl(this.mergedConfig);
|
||||
const logGeneral = calculateRunLogURLObject(this.mergedConfig);
|
||||
const log = logs ? logs[logGeneral.url] : null;
|
||||
let title = mergedConfig.nodeReducer.displayName;
|
||||
let title = this.mergedConfig.nodeReducer.displayName;
|
||||
if (log) {
|
||||
title = 'Logs';
|
||||
} else if (mergedConfig.nodeReducer.id !== null) {
|
||||
} else if (this.mergedConfig.nodeReducer.id !== null && title) {
|
||||
title = `Steps - ${title}`;
|
||||
}
|
||||
const currentSteps = steps ? steps[key] : null;
|
||||
// here we decide what to do next if somebody clicks on a flowNode
|
||||
const afterClick = (id) => {
|
||||
// get some information about the node the user clicked
|
||||
const nodeInfo = nodes[nodeKey].model.filter((item) => item.id === id)[0];
|
||||
const pathname = location.pathname;
|
||||
let newPath;
|
||||
// if path ends with pipeline we simply use it
|
||||
if (pathname.endsWith('pipeline/')) {
|
||||
newPath = pathname;
|
||||
} else if (pathname.endsWith('pipeline')) {
|
||||
newPath = `${pathname}/`;
|
||||
} else {
|
||||
// remove last bits
|
||||
const pathArray = pathname.split('/');
|
||||
pathArray.pop();
|
||||
if (pathname.endsWith('/')) {
|
||||
pathArray.pop();
|
||||
}
|
||||
pathArray.shift();
|
||||
newPath = `${pathArray.join('/')}/`;
|
||||
}
|
||||
// we only want to redirect to the node if the node is finished
|
||||
if (nodeInfo.state === 'FINISHED') {
|
||||
newPath = `${newPath}${id}`;
|
||||
}
|
||||
// see whether we need to update the state
|
||||
if (nodeInfo.state === 'FINISHED' && followAlong) {
|
||||
this.setState({ followAlong: false });
|
||||
}
|
||||
if (nodeInfo.state !== 'FINISHED' && !followAlong) {
|
||||
this.setState({ followAlong: true });
|
||||
}
|
||||
router.push(newPath);
|
||||
};
|
||||
const shouldShowLogHeader = log !== null || (currentSteps && currentSteps.model && currentSteps.model.length > 0);
|
||||
return (
|
||||
<div>
|
||||
<div ref="scrollArea">
|
||||
{ nodes && nodes[nodeKey] && <Extensions.Renderer
|
||||
extensionPoint="jenkins.pipeline.run.result"
|
||||
router={router}
|
||||
location={location}
|
||||
callback={afterClick}
|
||||
nodes={nodes[nodeKey].model}
|
||||
pipelineName={name}
|
||||
branchName={isMultiBranch ? branch : undefined}
|
||||
runId={runId}
|
||||
/>
|
||||
}
|
||||
<LogToolbar
|
||||
fileName={logGeneral.fileName}
|
||||
url={logGeneral.url}
|
||||
title={title}
|
||||
/>
|
||||
{ steps && steps[key] && <Steps
|
||||
nodeInformation={steps[key]}
|
||||
{ shouldShowLogHeader &&
|
||||
<LogToolbar
|
||||
fileName={logGeneral.fileName}
|
||||
url={logGeneral.url}
|
||||
title={title}
|
||||
/>
|
||||
}
|
||||
{ currentSteps && <Steps
|
||||
nodeInformation={currentSteps}
|
||||
followAlong={followAlong}
|
||||
{...this.props}
|
||||
/>
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { calculateLogUrl } from '../util/UrlUtils';
|
|||
|
||||
import LogConsole from './LogConsole';
|
||||
|
||||
const { object, func, string } = PropTypes;
|
||||
const { object, func, string, bool } = PropTypes;
|
||||
|
||||
export default class Node extends Component {
|
||||
componentWillMount() {
|
||||
|
@ -17,30 +17,38 @@ export default class Node extends Component {
|
|||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const { node, logs, nodesBaseUrl, fetchLog } = nextProps;
|
||||
const { node, logs, nodesBaseUrl, fetchLog, followAlong } = nextProps;
|
||||
const { config = {} } = this.context;
|
||||
const mergedConfig = { ...config, node, nodesBaseUrl };
|
||||
if (logs !== this.props.logs) {
|
||||
if (logs && logs !== this.props.logs) {
|
||||
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);
|
||||
if (number > 0) {
|
||||
// in case we doing karaoke we want to see more logs
|
||||
if (number > 0 && followAlong) {
|
||||
mergedConfig.newStart = log.newStart;
|
||||
// kill current timeout if any
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = setTimeout(() => fetchLog(mergedConfig), 1000);
|
||||
this.clearThisTimeout();
|
||||
this.timeout = setTimeout(() => fetchLog({ ...mergedConfig }), 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this.timeout);
|
||||
this.clearThisTimeout();
|
||||
}
|
||||
|
||||
clearThisTimeout() {
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { node, logs, nodesBaseUrl, fetchLog } = this.props;
|
||||
const { node, logs, nodesBaseUrl, fetchLog, followAlong } = this.props;
|
||||
// Early out
|
||||
if (!node || !fetchLog) {
|
||||
return null;
|
||||
|
@ -58,13 +66,16 @@ export default class Node extends Component {
|
|||
const resultRun = result === 'UNKNOWN' || !result ? state : result;
|
||||
const log = logs ? logs[calculateLogUrl({ ...config, node, nodesBaseUrl })] : null;
|
||||
const getLogForNode = () => {
|
||||
if (!log) {
|
||||
// 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 });
|
||||
}
|
||||
};
|
||||
const runResult = resultRun.toLowerCase();
|
||||
const scrollToBottom = runResult === 'failure' || runResult === 'running';
|
||||
|
||||
const scrollToBottom =
|
||||
resultRun.toLowerCase() === 'failure'
|
||||
|| (resultRun.toLowerCase() === 'running' && followAlong)
|
||||
;
|
||||
return (<div>
|
||||
<ResultItem
|
||||
key={id}
|
||||
|
@ -82,6 +93,7 @@ export default class Node extends Component {
|
|||
|
||||
Node.propTypes = {
|
||||
node: object.isRequired,
|
||||
followAlong: bool,
|
||||
logs: object,
|
||||
fetchLog: func,
|
||||
nodesBaseUrl: string,
|
||||
|
|
|
@ -12,7 +12,6 @@ export default class Nodes extends Component {
|
|||
model,
|
||||
nodesBaseUrl,
|
||||
} = nodeInformation;
|
||||
|
||||
return (<div>
|
||||
{
|
||||
model.map((item, index) =>
|
||||
|
|
|
@ -5,6 +5,7 @@ since we would return a function, */
|
|||
const { Record } = Immutable;
|
||||
export class PipelineRecord extends Record({
|
||||
_class: null,
|
||||
_links: null,
|
||||
branchNames: null,
|
||||
displayName: '',
|
||||
estimatedDurationInMillis: 0,
|
||||
|
|
|
@ -61,7 +61,7 @@ export const actionHandlers = {
|
|||
return state.set('currentRuns', payload);
|
||||
},
|
||||
[ACTION_TYPES.SET_NODE](state, { payload }): State {
|
||||
return state.set('node', payload);
|
||||
return state.set('node', { ...payload });
|
||||
},
|
||||
[ACTION_TYPES.SET_NODES](state, { payload }): State {
|
||||
const nodes = { ...state.nodes } || {};
|
||||
|
@ -157,7 +157,7 @@ function parseMoreDataHeader(response) {
|
|||
* @param onError
|
||||
*/
|
||||
exports.fetchJson = function fetchJson(url, onSuccess, onError) {
|
||||
fetch(url, fetchOptions)
|
||||
return fetch(url, fetchOptions)
|
||||
.then(checkStatus)
|
||||
.then(parseJSON)
|
||||
.then(onSuccess)
|
||||
|
@ -188,7 +188,7 @@ exports.fetchLogsInjectStart = function fetchJson(url, start, onSuccess, onError
|
|||
} else {
|
||||
refetchUrl = `${url}?start=${start}`;
|
||||
}
|
||||
fetch(refetchUrl, fetchOptions)
|
||||
return fetch(refetchUrl, fetchOptions)
|
||||
.then(checkStatus)
|
||||
.then(parseMoreDataHeader)
|
||||
.then(onSuccess)
|
||||
|
@ -684,6 +684,7 @@ export const actions = {
|
|||
nodeModel = information.model.filter((item) => item.id === config.node)[0];
|
||||
node = config.node;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: ACTION_TYPES.SET_NODE,
|
||||
payload: nodeModel,
|
||||
|
@ -770,7 +771,7 @@ export const actions = {
|
|||
if (
|
||||
!data || !data[logUrl] ||
|
||||
config.newStart > 0 ||
|
||||
(data && data[logUrl] && data[logUrl].newStart > 0)
|
||||
(data && data[logUrl] && data[logUrl].newStart > 0 || !data[logUrl].logArray)
|
||||
) {
|
||||
return exports.fetchLogsInjectStart(
|
||||
logUrl,
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Calculate whether to fetch a node
|
||||
* @param props
|
||||
* @param nextProps
|
||||
* @param mergedConfig
|
||||
*/
|
||||
|
||||
export function calculateNode(props, nextProps, mergedConfig) {
|
||||
const refetch = nextProps.result.state === 'RUNNING';
|
||||
// case the param is different from the one we currently in
|
||||
if (nextProps.params.node !== props.params.node) {
|
||||
// clone config
|
||||
const clonedConfig = { ...mergedConfig };
|
||||
clonedConfig.node = nextProps.params.node;
|
||||
const answer = { state: { followAlong: mergedConfig.followAlong } };
|
||||
answer.config = { ...clonedConfig, refetch };
|
||||
answer.action = 'fetchNodes';
|
||||
return answer;
|
||||
}
|
||||
return null;
|
||||
}
|
|
@ -47,7 +47,7 @@ export const uriString = (input) => encodeURIComponent(input).replace(/%2F/g, '%
|
|||
export const calculateLogUrl = (config) => {
|
||||
if (config.node) {
|
||||
const { nodesBaseUrl, node } = config;
|
||||
return `${nodesBaseUrl}/${node.id}/log`;
|
||||
return `${nodesBaseUrl}/${node.id}/log/`;
|
||||
}
|
||||
return config.url;
|
||||
};
|
||||
|
@ -80,14 +80,13 @@ export function calculateStepsBaseUrl(config) {
|
|||
`${_appURLBase}/rest/organizations/jenkins/` +
|
||||
`pipelines/${name}`;
|
||||
if (isMultiBranch) {
|
||||
baseUrl = `${baseUrl}/branches/${branch}`;
|
||||
baseUrl = `${baseUrl}/branches/${uriString(branch)}`;
|
||||
}
|
||||
if (node && node !== null) {
|
||||
return `${baseUrl}/runs/${runId}/nodes/${node}/steps`;
|
||||
return `${baseUrl}/runs/${runId}/nodes/${node}/steps/`;
|
||||
}
|
||||
return `${baseUrl}/runs/${runId}/steps/`;
|
||||
}
|
||||
|
||||
/*
|
||||
* helper to calculate general log url, includes filename.
|
||||
* If we have multibranch we generate a slightly different url
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
import { assert } from 'chai';
|
||||
|
||||
import {calculateNode } from '../../main/js/util/KaraokeHelper';
|
||||
|
||||
|
||||
describe('KaraokeHelper', () => {
|
||||
describe('KaraokeHelper calculateNode', () => {
|
||||
const props = { params: {} };
|
||||
const nextProps = { params: { node: 21}, result:{} };
|
||||
const mergedConfig = {
|
||||
node: 32,
|
||||
nodeReducer: {
|
||||
id:32,
|
||||
},
|
||||
};
|
||||
it('should return an answer if our node param is different (case if some one clicks a flownode)', () => {
|
||||
const answer = calculateNode(props, nextProps, mergedConfig);
|
||||
assert.notEqual(answer, null);
|
||||
});
|
||||
});
|
||||
describe('calculateNode real life', () => {
|
||||
|
||||
})
|
||||
});
|
|
@ -98,7 +98,7 @@ describe('UrlUtils', () => {
|
|||
}
|
||||
);
|
||||
|
||||
assert.equal(url, `${testUrl}/1/log`);
|
||||
assert.equal(url, `${testUrl}/1/log/`);
|
||||
});
|
||||
});
|
||||
describe('calculate calculateNodeBaseUrl', () => {
|
||||
|
@ -133,7 +133,7 @@ describe('UrlUtils', () => {
|
|||
const node = 15;
|
||||
const url = calculateStepsBaseUrl({...testData, node});
|
||||
assert.equal(url, `${testData._appURLBase}/rest/organizations/jenkins/` +
|
||||
`pipelines/${testData.name}/runs/${testData.runId}/nodes/${node}/steps`);
|
||||
`pipelines/${testData.name}/runs/${testData.runId}/nodes/${node}/steps/`);
|
||||
});
|
||||
it('should build the url with mutibranch and no node', () => {
|
||||
const isMultiBranch = true;
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/* eslint-disable quotes,quote-props,comma-dangle */
|
||||
export const pipelines = [
|
||||
{
|
||||
'_links': {
|
||||
'self': {
|
||||
'_class': 'io.jenkins.blueocean.rest.hal.Link',
|
||||
'href': '/blue/rest/organizations/jenkins/pipelines/morebeers/'
|
||||
},
|
||||
},
|
||||
'displayName': 'moreBeers',
|
||||
'name': 'morebeers',
|
||||
'organization': 'jenkins',
|
||||
'weatherScore': 0,
|
||||
'branchNames': ['master'],
|
||||
'numberOfFailingBranches': 1,
|
||||
'numberOfFailingPullRequests': 0,
|
||||
'numberOfSuccessfulBranches': 0,
|
||||
'numberOfSuccessfulPullRequests': 0,
|
||||
'totalNumberOfBranches': 1,
|
||||
'totalNumberOfPullRequests': 0
|
||||
},
|
||||
{
|
||||
'_links': {
|
||||
'self': {
|
||||
'_class': 'io.jenkins.blueocean.rest.hal.Link',
|
||||
'href': '/blue/rest/organizations/jenkins/pipelines/beers/'
|
||||
},
|
||||
},
|
||||
'displayName': 'beers',
|
||||
'name': 'beers',
|
||||
'organization': 'jenkins',
|
||||
'weatherScore': 0
|
||||
}
|
||||
];
|
|
@ -0,0 +1,85 @@
|
|||
/* eslint-disable quotes,quote-props,comma-dangle */
|
||||
export const pipelinesDupName = [
|
||||
{
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.MultiBranchPipelineImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/blueocean-pr-testing/"
|
||||
},
|
||||
"branches": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/blueocean-pr-testing/branches/"
|
||||
},
|
||||
"actions": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/blueocean-pr-testing/actions/"
|
||||
},
|
||||
"runs": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/blueocean-pr-testing/runs/"
|
||||
},
|
||||
"queue": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/blueocean-pr-testing/queue/"
|
||||
}
|
||||
},
|
||||
"actions": [],
|
||||
"displayName": "blueocean-pr-testing",
|
||||
"fullName": "blueocean-pr-testing",
|
||||
"name": "blueocean-pr-testing",
|
||||
"organization": "jenkins",
|
||||
"estimatedDurationInMillis": 1152,
|
||||
"numberOfFolders": 0,
|
||||
"numberOfPipelines": 8,
|
||||
"weatherScore": 100,
|
||||
"branchNames": ["develop", "master", "cliff-60s", "feature%2Fxxx", "feature%2Fcliff-2", "cliff-120s", "feature%2Fcliff-1", "feature%2Fyyy"],
|
||||
"numberOfFailingBranches": 0,
|
||||
"numberOfFailingPullRequests": 0,
|
||||
"numberOfSuccessfulBranches": 8,
|
||||
"numberOfSuccessfulPullRequests": 0,
|
||||
"totalNumberOfBranches": 8,
|
||||
"totalNumberOfPullRequests": 0
|
||||
},
|
||||
{
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.MultiBranchPipelineImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/folder1/pipelines/blueocean-pr-testing/"
|
||||
},
|
||||
"branches": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/folder1/pipelines/blueocean-pr-testing/branches/"
|
||||
},
|
||||
"actions": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/folder1/pipelines/blueocean-pr-testing/actions/"
|
||||
},
|
||||
"runs": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/folder1/pipelines/blueocean-pr-testing/runs/"
|
||||
},
|
||||
"queue": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/folder1/pipelines/blueocean-pr-testing/queue/"
|
||||
}
|
||||
},
|
||||
"actions": [],
|
||||
"displayName": "blueocean-pr-testing",
|
||||
"fullName": "folder1/blueocean-pr-testing",
|
||||
"name": "blueocean-pr-testing",
|
||||
"organization": "jenkins",
|
||||
"estimatedDurationInMillis": 1206,
|
||||
"numberOfFolders": 0,
|
||||
"numberOfPipelines": 8,
|
||||
"weatherScore": 100,
|
||||
"branchNames": ["feature%2Fcliff-1", "develop", "feature%2Fyyy", "feature%2Fcliff-2", "feature%2Fxxx", "cliff-120s", "cliff-60s", "master"],
|
||||
"numberOfFailingBranches": 0,
|
||||
"numberOfFailingPullRequests": 0,
|
||||
"numberOfSuccessfulBranches": 8,
|
||||
"numberOfSuccessfulPullRequests": 0,
|
||||
"totalNumberOfBranches": 8,
|
||||
"totalNumberOfPullRequests": 0
|
||||
}
|
||||
];
|
|
@ -0,0 +1,424 @@
|
|||
export const nodes = [{
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineNodeImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/5/"
|
||||
},
|
||||
"steps": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/5/steps/"
|
||||
}
|
||||
},
|
||||
"displayName": "deploy",
|
||||
"durationInMillis": 30779,
|
||||
"id": "5",
|
||||
"result": "SUCCESS",
|
||||
"startTime": "2016-07-01T14:02:38.829+0200",
|
||||
"state": "FINISHED",
|
||||
"edges": [{"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineNodeImpl$EdgeImpl", "id": "23"}]
|
||||
}, {
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineNodeImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/23/"
|
||||
},
|
||||
"steps": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/23/steps/"
|
||||
}
|
||||
},
|
||||
"displayName": "testing",
|
||||
"durationInMillis": 20235,
|
||||
"id": "23",
|
||||
"result": "SUCCESS",
|
||||
"startTime": "2016-07-01T14:03:09.608+0200",
|
||||
"state": "FINISHED",
|
||||
"edges": [{
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineNodeImpl$EdgeImpl",
|
||||
"id": "26"
|
||||
}, {"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineNodeImpl$EdgeImpl", "id": "27"}]
|
||||
}, {
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineNodeImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/26/"
|
||||
},
|
||||
"steps": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/26/steps/"
|
||||
}
|
||||
},
|
||||
"displayName": "firstBranch",
|
||||
"durationInMillis": 20176,
|
||||
"id": "26",
|
||||
"result": "SUCCESS",
|
||||
"startTime": "2016-07-01T14:03:09.609+0200",
|
||||
"state": "FINISHED",
|
||||
"edges": [{"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineNodeImpl$EdgeImpl", "id": "45"}]
|
||||
}, {
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineNodeImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/27/"
|
||||
},
|
||||
"steps": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/27/steps/"
|
||||
}
|
||||
},
|
||||
"displayName": "secondBranch",
|
||||
"durationInMillis": 15652,
|
||||
"id": "27",
|
||||
"result": "SUCCESS",
|
||||
"startTime": "2016-07-01T14:03:09.609+0200",
|
||||
"state": "FINISHED",
|
||||
"edges": [{"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineNodeImpl$EdgeImpl", "id": "45"}]
|
||||
}, {
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineNodeImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/45/"
|
||||
},
|
||||
"steps": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/45/steps/"
|
||||
}
|
||||
},
|
||||
"displayName": "fin",
|
||||
"durationInMillis": 26951,
|
||||
"id": "45",
|
||||
"result": "SUCCESS",
|
||||
"startTime": "2016-07-01T14:03:29.843+0200",
|
||||
"state": "FINISHED",
|
||||
"edges": []
|
||||
}];
|
||||
export const stepsNode5 = [{
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineStepImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/5/steps/6/"
|
||||
}
|
||||
},
|
||||
"displayName": "Shell Script",
|
||||
"durationInMillis": 4389,
|
||||
"id": "6",
|
||||
"result": "SUCCESS",
|
||||
"startTime": "2016-07-01T14:02:38.830+0200",
|
||||
"state": "FINISHED"
|
||||
}, {
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineStepImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/5/steps/7/"
|
||||
}
|
||||
},
|
||||
"displayName": "Print Message",
|
||||
"durationInMillis": 2,
|
||||
"id": "7",
|
||||
"result": "SUCCESS",
|
||||
"startTime": "2016-07-01T14:02:43.219+0200",
|
||||
"state": "FINISHED"
|
||||
}, {
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineStepImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/5/steps/8/"
|
||||
}
|
||||
},
|
||||
"displayName": "Shell Script",
|
||||
"durationInMillis": 4390,
|
||||
"id": "8",
|
||||
"result": "SUCCESS",
|
||||
"startTime": "2016-07-01T14:02:43.221+0200",
|
||||
"state": "FINISHED"
|
||||
}, {
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineStepImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/5/steps/9/"
|
||||
}
|
||||
},
|
||||
"displayName": "Print Message",
|
||||
"durationInMillis": 1,
|
||||
"id": "9",
|
||||
"result": "SUCCESS",
|
||||
"startTime": "2016-07-01T14:02:47.611+0200",
|
||||
"state": "FINISHED"
|
||||
}, {
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineStepImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/5/steps/10/"
|
||||
}
|
||||
},
|
||||
"displayName": "Shell Script",
|
||||
"durationInMillis": 4391,
|
||||
"id": "10",
|
||||
"result": "SUCCESS",
|
||||
"startTime": "2016-07-01T14:02:47.612+0200",
|
||||
"state": "FINISHED"
|
||||
}, {
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineStepImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/5/steps/11/"
|
||||
}
|
||||
},
|
||||
"displayName": "Print Message",
|
||||
"durationInMillis": 2,
|
||||
"id": "11",
|
||||
"result": "SUCCESS",
|
||||
"startTime": "2016-07-01T14:02:52.003+0200",
|
||||
"state": "FINISHED"
|
||||
}, {
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineStepImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/5/steps/12/"
|
||||
}
|
||||
},
|
||||
"displayName": "Shell Script",
|
||||
"durationInMillis": 4389,
|
||||
"id": "12",
|
||||
"result": "SUCCESS",
|
||||
"startTime": "2016-07-01T14:02:52.005+0200",
|
||||
"state": "FINISHED"
|
||||
}, {
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineStepImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/5/steps/13/"
|
||||
}
|
||||
},
|
||||
"displayName": "Print Message",
|
||||
"durationInMillis": 2,
|
||||
"id": "13",
|
||||
"result": "SUCCESS",
|
||||
"startTime": "2016-07-01T14:02:56.394+0200",
|
||||
"state": "FINISHED"
|
||||
}, {
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineStepImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/5/steps/14/"
|
||||
}
|
||||
},
|
||||
"displayName": "Shell Script",
|
||||
"durationInMillis": 4386,
|
||||
"id": "14",
|
||||
"result": "SUCCESS",
|
||||
"startTime": "2016-07-01T14:02:56.396+0200",
|
||||
"state": "FINISHED"
|
||||
}, {
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineStepImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/5/steps/15/"
|
||||
}
|
||||
},
|
||||
"displayName": "Print Message",
|
||||
"durationInMillis": 2,
|
||||
"id": "15",
|
||||
"result": "SUCCESS",
|
||||
"startTime": "2016-07-01T14:03:00.782+0200",
|
||||
"state": "FINISHED"
|
||||
}, {
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineStepImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/5/steps/16/"
|
||||
}
|
||||
},
|
||||
"displayName": "Shell Script",
|
||||
"durationInMillis": 4384,
|
||||
"id": "16",
|
||||
"result": "SUCCESS",
|
||||
"startTime": "2016-07-01T14:03:00.784+0200",
|
||||
"state": "FINISHED"
|
||||
}, {
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineStepImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/5/steps/17/"
|
||||
}
|
||||
},
|
||||
"displayName": "Print Message",
|
||||
"durationInMillis": 1,
|
||||
"id": "17",
|
||||
"result": "SUCCESS",
|
||||
"startTime": "2016-07-01T14:03:05.168+0200",
|
||||
"state": "FINISHED"
|
||||
}, {
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineStepImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/5/steps/18/"
|
||||
}
|
||||
},
|
||||
"displayName": "Shell Script",
|
||||
"durationInMillis": 4413,
|
||||
"id": "18",
|
||||
"result": "SUCCESS",
|
||||
"startTime": "2016-07-01T14:03:05.169+0200",
|
||||
"state": "FINISHED"
|
||||
}];
|
||||
|
||||
export const stepsNode45 = [{
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineStepImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/45/steps/46/"
|
||||
}
|
||||
},
|
||||
"displayName": "Shell Script",
|
||||
"durationInMillis": 6727,
|
||||
"id": "46",
|
||||
"result": "SUCCESS",
|
||||
"startTime": "2016-07-01T14:03:29.844+0200",
|
||||
"state": "FINISHED"
|
||||
}, {
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineStepImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/45/steps/47/"
|
||||
}
|
||||
},
|
||||
"displayName": "Print Message",
|
||||
"durationInMillis": 1,
|
||||
"id": "47",
|
||||
"result": "SUCCESS",
|
||||
"startTime": "2016-07-01T14:03:36.571+0200",
|
||||
"state": "FINISHED"
|
||||
}, {
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineStepImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/45/steps/48/"
|
||||
}
|
||||
},
|
||||
"displayName": "Shell Script",
|
||||
"durationInMillis": 6726,
|
||||
"id": "48",
|
||||
"result": "SUCCESS",
|
||||
"startTime": "2016-07-01T14:03:36.572+0200",
|
||||
"state": "FINISHED"
|
||||
}, {
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineStepImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/45/steps/49/"
|
||||
}
|
||||
},
|
||||
"displayName": "Print Message",
|
||||
"durationInMillis": 1,
|
||||
"id": "49",
|
||||
"result": "SUCCESS",
|
||||
"startTime": "2016-07-01T14:03:43.298+0200",
|
||||
"state": "FINISHED"
|
||||
}, {
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineStepImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/45/steps/50/"
|
||||
}
|
||||
},
|
||||
"displayName": "Print Message",
|
||||
"durationInMillis": 1,
|
||||
"id": "50",
|
||||
"result": "SUCCESS",
|
||||
"startTime": "2016-07-01T14:03:43.299+0200",
|
||||
"state": "FINISHED"
|
||||
}, {
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineStepImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/45/steps/51/"
|
||||
}
|
||||
},
|
||||
"displayName": "Shell Script",
|
||||
"durationInMillis": 6737,
|
||||
"id": "51",
|
||||
"result": "SUCCESS",
|
||||
"startTime": "2016-07-01T14:03:43.300+0200",
|
||||
"state": "FINISHED"
|
||||
}, {
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineStepImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/45/steps/52/"
|
||||
}
|
||||
},
|
||||
"displayName": "Print Message",
|
||||
"durationInMillis": 0,
|
||||
"id": "52",
|
||||
"result": "SUCCESS",
|
||||
"startTime": "2016-07-01T14:03:50.037+0200",
|
||||
"state": "FINISHED"
|
||||
}, {
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineStepImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/45/steps/53/"
|
||||
}
|
||||
},
|
||||
"displayName": "Print Message",
|
||||
"durationInMillis": 1,
|
||||
"id": "53",
|
||||
"result": "SUCCESS",
|
||||
"startTime": "2016-07-01T14:03:50.037+0200",
|
||||
"state": "FINISHED"
|
||||
}, {
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineStepImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/45/steps/54/"
|
||||
}
|
||||
},
|
||||
"displayName": "Print Message",
|
||||
"durationInMillis": 1,
|
||||
"id": "54",
|
||||
"result": "SUCCESS",
|
||||
"startTime": "2016-07-01T14:03:50.038+0200",
|
||||
"state": "FINISHED"
|
||||
}, {
|
||||
"_class": "io.jenkins.blueocean.service.embedded.rest.PipelineStepImpl",
|
||||
"_links": {
|
||||
"self": {
|
||||
"_class": "io.jenkins.blueocean.rest.hal.Link",
|
||||
"href": "/blue/rest/organizations/jenkins/pipelines/steps/runs/16/nodes/45/steps/55/"
|
||||
}
|
||||
},
|
||||
"displayName": "Shell Script",
|
||||
"durationInMillis": 6749,
|
||||
"id": "55",
|
||||
"result": "SUCCESS",
|
||||
"startTime": "2016-07-01T14:03:50.039+0200",
|
||||
"state": "FINISHED"
|
||||
}];
|
|
@ -1,43 +1,65 @@
|
|||
import { prepareMount } from './util/EnzymeUtils';
|
||||
prepareMount();
|
||||
|
||||
import React from 'react';
|
||||
import { assert} from 'chai';
|
||||
import sd from 'skin-deep';
|
||||
import Immutable from 'immutable';
|
||||
import { assert } from 'chai';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
|
||||
import Pipelines from '../../main/js/components/Pipelines.jsx';
|
||||
import { pipelines } from './pipelines';
|
||||
import { pipelines } from './data/pipelines/pipelinesSingle';
|
||||
import { pipelinesDupName } from './data/pipelines/pipelinesTwoJobsSameName';
|
||||
|
||||
const
|
||||
resultArrayHeaders = ['Name', 'Status', 'Branches', 'Pull Requests', '']
|
||||
;
|
||||
|
||||
describe("pipelines", () => {
|
||||
let tree;
|
||||
const resultArrayHeaders = ['Name', 'Status', 'Branches', 'Pull Requests', ''];
|
||||
|
||||
describe('Pipelines', () => {
|
||||
const config = {
|
||||
getRootURL: () => '/',
|
||||
};
|
||||
|
||||
const params = {};
|
||||
|
||||
beforeEach(() => {
|
||||
tree = sd.shallowRender(
|
||||
() => React.createElement(Pipelines), // For some reason using a fn turns on context
|
||||
{
|
||||
pipelines: Immutable.fromJS(pipelines),
|
||||
describe('basic table rendering', () => {
|
||||
let wrapper;
|
||||
let context;
|
||||
|
||||
beforeEach(() => {
|
||||
context = {
|
||||
pipelines,
|
||||
params,
|
||||
config,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
wrapper = shallow(
|
||||
<Pipelines />,
|
||||
{
|
||||
context,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('check header to be as expected', () => {
|
||||
assert.equal(wrapper.find('Table').props().headers.length, resultArrayHeaders.length);
|
||||
});
|
||||
|
||||
it('check rows number to be as expected', () => {
|
||||
assert.equal(wrapper.find('PipelineRowItem').length, 2);
|
||||
});
|
||||
});
|
||||
|
||||
it("renders pipelines - check header to be as expected", () => {
|
||||
const header = tree.subTree('Table').getRenderOutput();
|
||||
assert.equal(header.props.headers.length, resultArrayHeaders.length);
|
||||
});
|
||||
describe('duplicate job names', () => {
|
||||
it('should render two rows when job names are duplicated across folders', () => {
|
||||
const context = {
|
||||
config,
|
||||
params,
|
||||
pipelines: pipelinesDupName,
|
||||
};
|
||||
|
||||
it("renders pipelines - check rows number to be as expected", () => {
|
||||
const row = tree.everySubTree('PipelineRowItem');
|
||||
assert.equal(row.length, 2);
|
||||
});
|
||||
const wrapper = mount(
|
||||
<Pipelines />,
|
||||
{ context },
|
||||
);
|
||||
|
||||
assert.equal(wrapper.find('PipelineRowItem').length, 2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
export const pipelines = [{
|
||||
'displayName': 'moreBeers',
|
||||
'name': 'morebeers',
|
||||
'organization': 'jenkins',
|
||||
'weatherScore': 0,
|
||||
'branchNames': ['master'],
|
||||
'numberOfFailingBranches': 1,
|
||||
'numberOfFailingPullRequests': 0,
|
||||
'numberOfSuccessfulBranches': 0,
|
||||
'numberOfSuccessfulPullRequests': 0,
|
||||
'totalNumberOfBranches': 1,
|
||||
'totalNumberOfPullRequests': 0
|
||||
}, {
|
||||
'displayName': 'beers',
|
||||
'name': 'beers',
|
||||
'organization': 'jenkins',
|
||||
'weatherScore': 0
|
||||
}];
|
|
@ -259,4 +259,4 @@ describe("push events - started run tests", () => {
|
|||
assert.equal(runs[0].enQueueTime, undefined);
|
||||
assert.equal(runs[0].state, 'RUNNING');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
import React from 'react';
|
||||
import {assert} from 'chai';
|
||||
import configureMockStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import nock from 'nock';
|
||||
|
||||
import {getNodesInformation} from './../../main/js/util/logDisplayHelper';
|
||||
import {
|
||||
actions,
|
||||
} from '../../main/js/redux';
|
||||
import {nodes, stepsNode45} from './nodes';
|
||||
|
||||
import {
|
||||
calculateRunLogURLObject, calculateStepsBaseUrl, calculateNodeBaseUrl,
|
||||
} from '../../main/js/util/UrlUtils';
|
||||
|
||||
|
||||
const middlewares = [thunk];
|
||||
const mockStore = configureMockStore(middlewares);
|
||||
|
||||
describe("Store should work", () => {
|
||||
describe("Log Store should work", () => {
|
||||
afterEach(() => {
|
||||
nock.cleanAll()
|
||||
});
|
||||
it("create store with run data", () => {
|
||||
const name = 'testName',
|
||||
runId = 4,
|
||||
branch = 'testing',
|
||||
_appURLBase = '',
|
||||
isMultiBranch = false;
|
||||
const mergedConfig = {name, runId, branch, _appURLBase, isMultiBranch};
|
||||
const logGeneral = calculateRunLogURLObject(mergedConfig);
|
||||
nock('http://example.com')
|
||||
.get(logGeneral.url)
|
||||
.reply(200, `Hello World 2 parallel
|
||||
[workspace] Running shell script
|
||||
+ date
|
||||
Tue May 24 13:42:18 CEST 2016
|
||||
+ sleep 20
|
||||
+ date
|
||||
Tue May 24 13:42:38 CEST 2016
|
||||
`);
|
||||
const store = mockStore({adminStore: {logs: {}}});
|
||||
logGeneral.url = `http://example.com${logGeneral.url}`;
|
||||
return store.dispatch(
|
||||
actions.fetchLog({...logGeneral}))
|
||||
.then(() => { // return of async actions
|
||||
assert.equal(store.getActions()[0].type, 'SET_LOGS');
|
||||
assert.equal(store.getActions()[0].payload.logUrl, logGeneral.url);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Store should work with steps", () => {
|
||||
afterEach(() => {
|
||||
nock.cleanAll()
|
||||
});
|
||||
|
||||
it("create store with step data", () => {
|
||||
const baseUrl = 'http://127.0.0.1';
|
||||
const name = 'steps',
|
||||
runId = 16,
|
||||
branch = 'testing',
|
||||
_appURLBase = '',
|
||||
isMultiBranch = false;
|
||||
const mergedConfig = {name, runId, branch, _appURLBase, isMultiBranch};
|
||||
const node = 45;
|
||||
const stepsUrl = calculateStepsBaseUrl({...mergedConfig, node});
|
||||
const stepsNock = nock(baseUrl)
|
||||
.get(stepsUrl)
|
||||
.reply(200, stepsNode45)
|
||||
;
|
||||
const store = mockStore({adminStore: {}});
|
||||
mergedConfig._appURLBase = `${baseUrl}:80`;
|
||||
|
||||
store.dispatch(
|
||||
actions.fetchSteps({...mergedConfig, node}))
|
||||
.then(() => { // return of async actions
|
||||
assert.equal(store.getActions()[0].type, 'SET_STEPS');
|
||||
assert.equal(store.getActions()[0].payload.isFinished, true);
|
||||
assert.equal(store.getActions()[0].payload.isError, false);
|
||||
assert.equal(store.getActions()[0].payload.nodesBaseUrl, `${baseUrl}${stepsUrl}`);
|
||||
assert.equal(store.getActions()[0].payload.model.length, 10);
|
||||
});
|
||||
assert.equal(stepsNock.isDone(), true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Store should work with nodes", () => {
|
||||
afterEach(() => {
|
||||
nock.cleanAll()
|
||||
});
|
||||
|
||||
it("create store with node data", () => {
|
||||
const baseUrl = 'http://127.0.0.1';
|
||||
const name = 'steps',
|
||||
runId = 16,
|
||||
branch = 'testing',
|
||||
_appURLBase = '',
|
||||
isMultiBranch = false;
|
||||
const mergedConfig = {name, runId, branch, _appURLBase, isMultiBranch};
|
||||
const nodesBaseUrl = calculateNodeBaseUrl(mergedConfig);
|
||||
const node = 45;
|
||||
const stepsUrl = calculateStepsBaseUrl({...mergedConfig, node});
|
||||
const nodeNock = nock(baseUrl)
|
||||
.get(nodesBaseUrl)
|
||||
.reply(200, nodes)
|
||||
;
|
||||
mergedConfig._appURLBase = `${baseUrl}:80`;
|
||||
const steps = {};
|
||||
steps[`${baseUrl}:80${stepsUrl}`] = getNodesInformation(stepsNode45);
|
||||
const otherStore = mockStore({adminStore: {steps}});
|
||||
otherStore.dispatch(actions.fetchNodes(mergedConfig));
|
||||
assert.equal(nodeNock.isDone(), true);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -10,7 +10,7 @@ import {
|
|||
pipelines as pipelinesSelector,
|
||||
currentRuns as currentRunsSelector,
|
||||
} from '../../main/js/redux';
|
||||
import { pipelines } from './pipelines';
|
||||
import { pipelines } from './data/pipelines/pipelinesSingle';
|
||||
import { latestRuns } from './data/runs/latestRuns';
|
||||
import job_crud_created_multibranch from './data/sse/job_crud_created_multibranch';
|
||||
import fetchedBranches from './data/branches/latestBranches';
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import { store as ExtensionStore } from '@jenkins-cd/js-extensions';
|
||||
const jsdom = require('jsdom').jsdom;
|
||||
|
||||
/**
|
||||
* Prepares Enzyme's "mount" function for use by binding it to JSDOM.
|
||||
* Also takes care of a little bootstrapping of @js-extensions/ExtensionStore to avoid errors.
|
||||
* Place this snippet at the very top of your test file, before importing React: *
|
||||
* import { prepareMount } from './EnzymeUtils';
|
||||
* prepareMount();
|
||||
*
|
||||
* Created by cmeyers on 7/12/16.
|
||||
*/
|
||||
export const prepareMount = () => {
|
||||
// code to boostrap mount with JSDOM
|
||||
// see: https://github.com/airbnb/enzyme/blob/master/docs/guides/jsdom.md
|
||||
const exposedProperties = ['window', 'navigator', 'document'];
|
||||
global.document = jsdom('');
|
||||
global.window = document.defaultView;
|
||||
Object.keys(document.defaultView).forEach((property) => {
|
||||
if (typeof global[property] === 'undefined') {
|
||||
exposedProperties.push(property);
|
||||
global[property] = document.defaultView[property];
|
||||
}
|
||||
});
|
||||
|
||||
global.navigator = {
|
||||
userAgent: 'node.js',
|
||||
};
|
||||
|
||||
// Extensions.Renderer will fail during Enzyme.mount without an
|
||||
// 'extensionDataProvider' function being provided
|
||||
// FIXME: there's probably a much cleaner way to do this.
|
||||
ExtensionStore.init({
|
||||
extensionDataProvider: () => undefined,
|
||||
});
|
||||
};
|
|
@ -27,6 +27,7 @@ import hudson.Extension;
|
|||
import hudson.model.Item;
|
||||
import hudson.model.ItemGroup;
|
||||
import io.jenkins.blueocean.rest.hal.Link;
|
||||
import io.jenkins.blueocean.rest.hal.LinkResolver;
|
||||
import io.jenkins.blueocean.service.embedded.rest.OrganizationImpl;
|
||||
import jenkins.model.ParameterizedJobMixIn;
|
||||
import org.jenkins.pubsub.EventProps;
|
||||
|
@ -38,31 +39,34 @@ import org.jenkinsci.plugins.workflow.job.WorkflowJob;
|
|||
import org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:tom.fennelly@gmail.com">tom.fennelly@gmail.com</a>
|
||||
*/
|
||||
@Extension
|
||||
public class BlueMessageEnricher extends MessageEnricher {
|
||||
|
||||
@Inject
|
||||
private LinkResolver linkResolver;
|
||||
|
||||
enum BlueEventProps {
|
||||
blueocean_job_rest_url,
|
||||
blueocean_job_pipeline_name,
|
||||
blueocean_job_branch_name,
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void enrich(@Nonnull Message message) {
|
||||
|
||||
// TODO: Replace once https://issues.jenkins-ci.org/browse/JENKINS-36286 is done
|
||||
|
||||
// TODO: Get organization name in generic way once multi-organization support is implemented in API
|
||||
message.set(EventProps.Jenkins.jenkins_org, OrganizationImpl.INSTANCE.getName());
|
||||
|
||||
|
||||
String channelName = message.getChannelName();
|
||||
if (channelName.equals(Events.JobChannel.NAME)) {
|
||||
JobChannelMessage jobChannelMessage = (JobChannelMessage) message;
|
||||
ParameterizedJobMixIn.ParameterizedJob job = jobChannelMessage.getJob();
|
||||
Link jobUrl = getLink(job);
|
||||
|
||||
Link jobUrl = linkResolver.resolve(job);
|
||||
|
||||
jobChannelMessage.set(BlueEventProps.blueocean_job_rest_url, jobUrl.getHref());
|
||||
jobChannelMessage.set(BlueEventProps.blueocean_job_pipeline_name, job.getName());
|
||||
if (job instanceof WorkflowJob) {
|
||||
|
@ -76,19 +80,4 @@ public class BlueMessageEnricher extends MessageEnricher {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Replace once https://issues.jenkins-ci.org/browse/JENKINS-36286 is done
|
||||
private static @Nonnull Link getLink(@Nonnull ParameterizedJobMixIn.ParameterizedJob job) {
|
||||
Link orgLink = new Link("/rest/organizations/" + OrganizationImpl.INSTANCE.getName());
|
||||
|
||||
if (job instanceof WorkflowJob) {
|
||||
ItemGroup<? extends Item> parent = job.getParent();
|
||||
if (parent instanceof WorkflowMultiBranchProject) {
|
||||
String multiBranchProjectName = ((WorkflowMultiBranchProject) parent).getName();
|
||||
return orgLink.rel("pipelines").rel(multiBranchProjectName).rel("branches").rel(job.getName());
|
||||
}
|
||||
}
|
||||
|
||||
return orgLink.rel("pipelines").rel(job.getName());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -133,24 +133,39 @@ public class MultiBranchPipelineImpl extends BlueMultiBranchPipeline {
|
|||
/**
|
||||
* TODO: this code need cleanup once MultiBranchProject exposes default branch. At present
|
||||
*
|
||||
* At present we look for master as primary branch, if not found we find the latest build and return
|
||||
* its score.
|
||||
* At present we look for master as primary branch, if not found we find the latest build across all branches and
|
||||
* return its score.
|
||||
*
|
||||
* If there are no builds taken place 0 score is returned.
|
||||
*/
|
||||
|
||||
Job j = mbp.getBranch("master");
|
||||
Job j = mbp.getItem("master");
|
||||
if(j == null) {
|
||||
j = mbp.getBranch("production");
|
||||
if(j == null){ //get latest
|
||||
j = mbp.getItem("production");
|
||||
/**
|
||||
* If there are no master or production branch then we return weather score of
|
||||
*
|
||||
* - Sort latest build of all branches in ascending order
|
||||
* - Return the latest
|
||||
*
|
||||
*/
|
||||
if(j == null){
|
||||
Collection<Job> jbs = mbp.getAllJobs();
|
||||
if(jbs.size() > 0){
|
||||
Job[] jobs = jbs.toArray(new Job[jbs.size()]);
|
||||
Arrays.sort(jobs, new Comparator<Job>() {
|
||||
@Override
|
||||
public int compare(Job o1, Job o2) {
|
||||
long t1 = o1.getLastBuild().getTimeInMillis() + o1.getLastBuild().getDuration();
|
||||
long t2 = o2.getLastBuild().getTimeInMillis() + o2.getLastBuild().getDuration();
|
||||
long t1 = 0;
|
||||
if(o1.getLastBuild() != null){
|
||||
t1 = o1.getLastBuild().getTimeInMillis() + o1.getLastBuild().getDuration();
|
||||
}
|
||||
|
||||
long t2 = 0;
|
||||
if(o2.getLastBuild() != null){
|
||||
t2 = o2.getLastBuild().getTimeInMillis() + o2.getLastBuild().getDuration();
|
||||
}
|
||||
|
||||
if(t1<2){
|
||||
return -1;
|
||||
}else if(t1 > t2){
|
||||
|
|
Loading…
Reference in New Issue