JENKINS-48075 add next/prev links to run details screen (#1722)
* add next/prev links to run details screen * implement PR suggestions
This commit is contained in:
parent
fdcc18f3d5
commit
16d42cd0f6
|
@ -1,12 +1,46 @@
|
|||
import React, { Component, PropTypes } from 'react';
|
||||
import { Icon } from '@jenkins-cd/design-language';
|
||||
import { UrlConfig, AppConfig, logging, ResultPageHeader, TimeManager } from '@jenkins-cd/blueocean-core-js';
|
||||
import { UrlConfig, AppConfig, logging, ResultPageHeader, TimeManager, UrlBuilder } from '@jenkins-cd/blueocean-core-js';
|
||||
import { ExpandablePath, ReadableDate, TimeDuration, CommitId } from '@jenkins-cd/design-language';
|
||||
import ChangeSetToAuthors from './ChangeSetToAuthors';
|
||||
import { Link } from 'react-router';
|
||||
import { UrlBuilder } from '@jenkins-cd/blueocean-core-js';
|
||||
import RunIdCell from './RunIdCell';
|
||||
|
||||
export class RunIdNavigation extends Component {
|
||||
render() {
|
||||
const { run, pipeline, branchName, t } = this.props;
|
||||
|
||||
const nextRunId = run._links.nextRun ? /[\/].*runs\/*([0-9]*)/g.exec(run._links.nextRun.href)[1] : '';
|
||||
const prevRunId = run._links.prevRun ? /[\/].*runs\/*([0-9]*)/g.exec(run._links.prevRun.href)[1] : '';
|
||||
|
||||
const nextRunUrl = nextRunId ? UrlBuilder.buildRunUrl(pipeline.organization, pipeline.fullName, branchName, nextRunId, 'pipeline') : '';
|
||||
const prevRunUrl = prevRunId ? UrlBuilder.buildRunUrl(pipeline.organization, pipeline.fullName, branchName, prevRunId, 'pipeline') : '';
|
||||
|
||||
return (
|
||||
<span className="run-nav-container">
|
||||
{prevRunUrl && (
|
||||
<Link to={prevRunUrl} title={t('rundetail.header.prev_run', { defaultValue: 'Previous Run' })}>
|
||||
<Icon size={24} icon="HardwareKeyboardArrowLeft" style={{ verticalAlign: 'bottom' }} />
|
||||
</Link>
|
||||
)}
|
||||
<RunIdCell run={run} />
|
||||
{nextRunUrl && (
|
||||
<Link to={nextRunUrl} title={t('rundetail.header.next_run', { defaultValue: 'Next Run' })}>
|
||||
<Icon size={24} icon="HardwareKeyboardArrowRight" style={{ verticalAlign: 'bottom' }} />
|
||||
</Link>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RunIdNavigation.propTypes = {
|
||||
run: PropTypes.object,
|
||||
pipeline: PropTypes.object,
|
||||
branchName: PropTypes.string,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
class RunDetailsHeader extends Component {
|
||||
componentWillMount() {
|
||||
this._setDuration(this.props);
|
||||
|
@ -81,9 +115,7 @@ class RunDetailsHeader extends Component {
|
|||
<Link className="path-link" to={activityUrl}>
|
||||
<ExpandablePath path={fullDisplayName} hideFirst className="dark-theme" iconSize={20} />
|
||||
</Link>
|
||||
<span>
|
||||
<RunIdCell run={run} />
|
||||
</span>
|
||||
<RunIdNavigation run={run} pipeline={pipeline} branchName={displayName} t={t} />
|
||||
</h1>
|
||||
);
|
||||
|
||||
|
|
|
@ -12,8 +12,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.RunDetailsHeader-title > span {
|
||||
.RunDetailsHeader-title .run-nav-container {
|
||||
vertical-align: middle;
|
||||
|
||||
span:first-child {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.RunDetailsHeader-sources,
|
||||
|
|
|
@ -210,6 +210,8 @@ rundetail.header.tab.artifacts=Artifacts
|
|||
rundetail.header.tab.changes=Changes
|
||||
rundetail.header.tab.pipeline=Pipeline
|
||||
rundetail.header.tab.tests=Tests
|
||||
rundetail.header.next_run=Next Run
|
||||
rundetail.header.prev_run=Previous Run
|
||||
rundetail.input.cancel=Abort
|
||||
rundetail.pipeline.description=Description
|
||||
rundetail.pipeline.logs=Logs
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
import React from 'react';
|
||||
import { assert } from 'chai';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { RunIdNavigation } from '../../main/js/components/RunDetailsHeader';
|
||||
|
||||
import { i18nTranslator } from '@jenkins-cd/blueocean-core-js';
|
||||
const t = i18nTranslator('blueocean-dashboard');
|
||||
|
||||
const mockRuns = [
|
||||
{
|
||||
'id': '1',
|
||||
'organization': 'jenkins',
|
||||
'pipeline': 'mockPipeline',
|
||||
'_links': {
|
||||
'nextRun': {
|
||||
'_class': 'io.jenkins.blueocean.rest.hal.Link',
|
||||
'href': '/blue/rest/organizations/jenkins/pipelines/mockPipeline/runs/2/'
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
'id': '2',
|
||||
'organization': 'jenkins',
|
||||
'pipeline': 'mockPipeline',
|
||||
'_links': {
|
||||
'prevRun': {
|
||||
'_class': 'io.jenkins.blueocean.rest.hal.Link',
|
||||
'href': '/blue/rest/organizations/jenkins/pipelines/mockPipeline/runs/1/'
|
||||
},
|
||||
'nextRun': {
|
||||
'_class': 'io.jenkins.blueocean.rest.hal.Link',
|
||||
'href': '/blue/rest/organizations/jenkins/pipelines/mockPipeline/runs/3/'
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
'id': '3',
|
||||
'organization': 'jenkins',
|
||||
'pipeline': 'mockPipeline',
|
||||
'_links': {
|
||||
'prevRun': {
|
||||
'_class': 'io.jenkins.blueocean.rest.hal.Link',
|
||||
'href': '/blue/rest/organizations/jenkins/pipelines/mockPipeline/runs/2/'
|
||||
},
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const mockPipeline = {
|
||||
'organization': 'jenkins',
|
||||
'fullName': 'mockPipeline',
|
||||
}
|
||||
|
||||
import { mockExtensionsForI18n } from './mock-extensions-i18n';
|
||||
mockExtensionsForI18n();
|
||||
|
||||
describe('RunDetailsIdNavigation', () => {
|
||||
beforeAll(() => mockExtensionsForI18n());
|
||||
|
||||
it('check next link on run with id = 1', () => {
|
||||
let wrapper = shallow(<RunIdNavigation run={mockRuns[0]} pipeline={mockPipeline} branchName='branchName' t={t} />);
|
||||
|
||||
//check that there is only one link for first run (only next)
|
||||
assert.equal(wrapper.find('Link').length, 1);
|
||||
|
||||
//check next link
|
||||
assert.equal(wrapper.find('Link').first().props().title, 'Next Run');
|
||||
assert.equal(wrapper.find('Link').first().props().to, '/organizations/jenkins/mockPipeline/detail/branchName/2/pipeline');
|
||||
});
|
||||
|
||||
|
||||
it('check next/prev links on run with id = 2', () => {
|
||||
let wrapper = shallow(<RunIdNavigation run={mockRuns[1]} pipeline={mockPipeline} branchName='branchName' t={t} />);
|
||||
|
||||
//check prev link
|
||||
assert.equal(wrapper.find('Link').first().props().title, 'Previous Run');
|
||||
assert.equal(wrapper.find('Link').first().props().to, '/organizations/jenkins/mockPipeline/detail/branchName/1/pipeline');
|
||||
|
||||
//check next link
|
||||
assert.equal(wrapper.find('Link').at(1).props().title, 'Next Run');
|
||||
assert.equal(wrapper.find('Link').at(1).props().to, '/organizations/jenkins/mockPipeline/detail/branchName/3/pipeline');
|
||||
});
|
||||
|
||||
it('check next link on run with id = 3', () => {
|
||||
let wrapper = shallow(<RunIdNavigation run={mockRuns[2]} pipeline={mockPipeline} branchName='branchName' t={t} />);
|
||||
|
||||
//check that there is only one link for the last run (only prev)
|
||||
assert.equal(wrapper.find('Link').length, 1);
|
||||
|
||||
//check prev link
|
||||
assert.equal(wrapper.find('Link').first().props().title, 'Previous Run');
|
||||
assert.equal(wrapper.find('Link').first().props().to, '/organizations/jenkins/mockPipeline/detail/branchName/2/pipeline');
|
||||
});
|
||||
});
|
|
@ -238,6 +238,45 @@ public class AbstractRunImplTest extends PipelineBaseTest {
|
|||
Assert.assertEquals("Waiting for next available executor", latestRun.get("causeOfBlockage"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pipelineRunIncludesNextPrevLinks() throws Exception {
|
||||
WorkflowJob p = createWorkflowJobWithJenkinsfile(getClass(),"latestRunIncludesQueued.jenkinsfile");
|
||||
|
||||
// Ensure null before first run
|
||||
Map pipeline = request().get(String.format("/organizations/jenkins/pipelines/%s/", p.getName())).build(Map.class);
|
||||
Assert.assertNull(pipeline.get("latestRun"));
|
||||
|
||||
// Run until completed
|
||||
Run r = p.scheduleBuild2(0).waitForStart();
|
||||
j.waitForCompletion(r);
|
||||
|
||||
// Make the next runs queue
|
||||
j.jenkins.setNumExecutors(1);
|
||||
|
||||
// Schedule another run so it goes in the queue
|
||||
WorkflowRun r2 = p.scheduleBuild2(0).waitForStart();
|
||||
j.waitForCompletion(r2);
|
||||
|
||||
// Schedule another run so it goes in the queue
|
||||
WorkflowRun r3 = p.scheduleBuild2(0).waitForStart();
|
||||
j.waitForCompletion(r3);
|
||||
|
||||
// Get latest run for this pipeline
|
||||
Map secondRun = request().get(String.format("/organizations/jenkins/pipelines/%s/runs/2", p.getName())).build(Map.class);
|
||||
|
||||
String prevRunUrl = ((Map)((Map)secondRun.get("_links")).get("prevRun")).get("href").toString();
|
||||
String nextRunUrl = ((Map)((Map)secondRun.get("_links")).get("nextRun")).get("href").toString();
|
||||
|
||||
//check the run id of the second run
|
||||
Assert.assertEquals("2", secondRun.get("id"));
|
||||
|
||||
//check that id in previous run url is 1
|
||||
Assert.assertEquals("1", prevRunUrl.substring(prevRunUrl.length() - 2, prevRunUrl.length() - 1));
|
||||
|
||||
//check that id in next run url is 3
|
||||
Assert.assertEquals("3", nextRunUrl.substring(nextRunUrl.length() - 2, nextRunUrl.length() - 1));
|
||||
}
|
||||
|
||||
@Issue("JENKINS-44981")
|
||||
@Test
|
||||
public void queuedSingleNode() throws Exception {
|
||||
|
|
|
@ -13,6 +13,7 @@ import io.jenkins.blueocean.commons.ServiceException;
|
|||
import io.jenkins.blueocean.rest.Reachable;
|
||||
import io.jenkins.blueocean.rest.factory.BlueTestResultFactory;
|
||||
import io.jenkins.blueocean.rest.hal.Link;
|
||||
import io.jenkins.blueocean.rest.hal.LinkResolver;
|
||||
import io.jenkins.blueocean.rest.hal.Links;
|
||||
import io.jenkins.blueocean.rest.model.BlueActionProxy;
|
||||
import io.jenkins.blueocean.rest.model.BlueArtifactContainer;
|
||||
|
@ -326,7 +327,21 @@ public abstract class AbstractRunImpl<T extends Run> extends BlueRun {
|
|||
|
||||
@Override
|
||||
public Links getLinks() {
|
||||
return super.getLinks().add("parent", parent.getLink());
|
||||
Links links = super.getLinks().add("parent", parent.getLink());
|
||||
Run nextRun = run.getNextBuild();
|
||||
Run prevRun = run.getPreviousBuild();
|
||||
|
||||
if(nextRun != null) {
|
||||
Link nextRunLink = LinkResolver.resolveLink(nextRun);
|
||||
links.add("nextRun", nextRunLink);
|
||||
}
|
||||
|
||||
if(prevRun != null) {
|
||||
Link prevRunLink = LinkResolver.resolveLink(prevRun);
|
||||
links.add("prevRun", prevRunLink);
|
||||
}
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
public static class BlueCauseImpl extends BlueCause {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.svg-icon {
|
||||
display: 'inline-block';
|
||||
vertical-align: 'middle';
|
||||
user-select: 'none';
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
user-select: none;
|
||||
fill: #ffffff;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue