Bug/jenkins 38023 show expandable path (#563)
* [JENKINS-38023] pull in new JDL with ExpandablePath component * [JENKINS-38023] implement "ExpandablePath" on dashboard screens * [JENKINS-38023] tick up JDL version * [JENKINS-38023] fix tests that broken when introducing ExpandablePath; port to Enzyme; delint * [JENKINS-38023] disable test that was broken (yet not failing build?) due to some an error about SSE connection being undefined * [JENKINS-38023] tweak favorites card, pipeline page header and run details header to show displayName if available; still needs a fix for favorited branch as the displayName returned is the branch name, not the parent pipeline's displayName * [JENKINS-38023] use new "fullDisplayName" property instead of trying to concatenate fullName and displayName * Added fullDisplayName to BluePipeline model. fullDisplayName is similar to fullName, except each segment is displayName if present. Note: each segment is delimited by '/' and each segment is url encoded * [JENKINS-38023] handle URI-encoded path elements; handle multibranch in Favorite card * Fix to properly url encode display name * [JENKINS-38023] use official JDL release
This commit is contained in:
parent
2cbc261e3b
commit
de288bef83
|
@ -8,9 +8,9 @@
|
|||
"resolved": "https://registry.npmjs.org/@jenkins-cd/blueocean-core-js/-/blueocean-core-js-0.0.20.tgz"
|
||||
},
|
||||
"@jenkins-cd/design-language": {
|
||||
"version": "0.0.83",
|
||||
"from": "@jenkins-cd/design-language@0.0.83",
|
||||
"resolved": "https://registry.npmjs.org/@jenkins-cd/design-language/-/design-language-0.0.83.tgz"
|
||||
"version": "0.0.85",
|
||||
"from": "@jenkins-cd/design-language@0.0.85",
|
||||
"resolved": "https://registry.npmjs.org/@jenkins-cd/design-language/-/design-language-0.0.85.tgz"
|
||||
},
|
||||
"@jenkins-cd/diag": {
|
||||
"version": "0.0.2",
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@jenkins-cd/blueocean-core-js": "0.0.20",
|
||||
"@jenkins-cd/design-language": "0.0.83",
|
||||
"@jenkins-cd/design-language": "0.0.85",
|
||||
"@jenkins-cd/js-extensions": "0.0.27",
|
||||
"@jenkins-cd/js-modules": "0.0.8",
|
||||
"es6-promise": "4.0.5",
|
||||
|
|
|
@ -9,6 +9,7 @@ import { Link } from 'react-router';
|
|||
import Extensions from '@jenkins-cd/js-extensions';
|
||||
import NotFound from './NotFound';
|
||||
import {
|
||||
ExpandablePath,
|
||||
Page,
|
||||
PageHeader,
|
||||
Title,
|
||||
|
@ -46,7 +47,7 @@ export class PipelinePage extends Component {
|
|||
|
||||
render() {
|
||||
const { pipeline, setTitle } = this.props;
|
||||
const { organization, name, fullName } = pipeline || {};
|
||||
const { organization, name, fullName, fullDisplayName } = pipeline || {};
|
||||
const orgUrl = buildOrganizationUrl(organization);
|
||||
const activityUrl = buildPipelineUrl(organization, fullName, 'activity');
|
||||
const isReady = pipeline && !pipeline.$pending;
|
||||
|
@ -73,8 +74,10 @@ export class PipelinePage extends Component {
|
|||
<WeatherIcon score={pipeline.weatherScore} size="large" />
|
||||
<h1>
|
||||
<Link to={orgUrl}>{organization}</Link>
|
||||
<span> / </span>
|
||||
<Link to={activityUrl}>{name}</Link>
|
||||
<span> / </span>
|
||||
<Link to={activityUrl}>
|
||||
<ExpandablePath path={fullDisplayName} iconSize={20} hideFirst />
|
||||
</Link>
|
||||
</h1>
|
||||
<Extensions.Renderer
|
||||
extensionPoint="jenkins.pipeline.detail.header.action"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { Component, PropTypes } from 'react';
|
||||
import { Link } from 'react-router';
|
||||
import { WeatherIcon } from '@jenkins-cd/design-language';
|
||||
import { ExpandablePath, WeatherIcon } from '@jenkins-cd/design-language';
|
||||
import Extensions from '@jenkins-cd/js-extensions';
|
||||
import { buildPipelineUrl } from '../util/UrlUtils';
|
||||
|
||||
|
@ -27,13 +27,13 @@ export default class PipelineRowItem extends Component {
|
|||
const {
|
||||
name,
|
||||
fullName,
|
||||
fullDisplayName,
|
||||
organization,
|
||||
weatherScore,
|
||||
numberOfSuccessfulBranches,
|
||||
numberOfFailingBranches,
|
||||
numberOfSuccessfulPullRequests,
|
||||
numberOfFailingPullRequests,
|
||||
displayName,
|
||||
} = pipeline;
|
||||
|
||||
const hasPullRequests = !simple && (
|
||||
|
@ -44,17 +44,7 @@ export default class PipelineRowItem extends Component {
|
|||
const pullRequestsURL = `${baseUrl}/pr`;
|
||||
const activitiesURL = `${baseUrl}/activity`;
|
||||
|
||||
const pathInJob = fullName.split('/').slice(0, -1).join(' / ');
|
||||
const formattedName = `${pathInJob ? `${pathInJob} / ` : ''}${displayName}`;
|
||||
const nameLink = (
|
||||
<Link to={activitiesURL}>
|
||||
{ showOrganization ?
|
||||
`${organization} / ${formattedName}` :
|
||||
formattedName
|
||||
}
|
||||
</Link>
|
||||
);
|
||||
|
||||
const fullDisplayPath = showOrganization ? `${organization}/${fullDisplayName}` : fullDisplayName;
|
||||
let multiBranchLabel = ' - ';
|
||||
let multiPrLabel = ' - ';
|
||||
let multiBranchLink = null;
|
||||
|
@ -79,7 +69,11 @@ export default class PipelineRowItem extends Component {
|
|||
// FIXME: Visual alignment of the last column
|
||||
return (
|
||||
<tr data-name={name} data-organization={organization}>
|
||||
<td>{nameLink}</td>
|
||||
<td>
|
||||
<Link to={activitiesURL}>
|
||||
<ExpandablePath path={fullDisplayPath} />
|
||||
</Link>
|
||||
</td>
|
||||
<td><WeatherIcon score={weatherScore} /></td>
|
||||
{
|
||||
// fixme refactor the next 2 lines and the prior logic
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { Icon } from 'react-material-icons-blue';
|
||||
import { ReadableDate, LiveStatusIndicator, TimeDuration } from '@jenkins-cd/design-language';
|
||||
import { ExpandablePath, ReadableDate, LiveStatusIndicator, TimeDuration } from '@jenkins-cd/design-language';
|
||||
import ChangeSetToAuthors from './ChangeSetToAuthors';
|
||||
import moment from 'moment';
|
||||
|
||||
|
@ -26,19 +26,11 @@ class RunDetailsHeader extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { data: run, pipeline: { fullName = '' } } = this.props;
|
||||
const { data: run, pipeline } = this.props;
|
||||
// pipeline name
|
||||
const displayName = decodeURIComponent(run.pipeline);
|
||||
// enable folder path
|
||||
const nameArray = fullName.split('/');
|
||||
const fullDisplayName = pipeline.fullDisplayName;
|
||||
|
||||
// we want the full path for folder based projects
|
||||
if (nameArray[nameArray.length - 1] === displayName) {
|
||||
// last part is same as run.pipeline so getting rid of it
|
||||
nameArray.pop();
|
||||
}
|
||||
// cleanName is in case of no folder empty
|
||||
const cleanFullName = nameArray.join(' / ');
|
||||
// Grab author from each change, run through a set for uniqueness
|
||||
// FIXME-FLOW: Remove the ":any" cast after completion of https://github.com/facebook/flow/issues/1059
|
||||
const changeSet = run.changeSet;
|
||||
|
@ -47,7 +39,7 @@ class RunDetailsHeader extends Component {
|
|||
moment().diff(moment(run.startTime)) : run.durationInMillis;
|
||||
const onAuthorsClick = () => this.handleAuthorsClick();
|
||||
return (
|
||||
<div className="pipeline-result">
|
||||
<div className="pipeline-result run-details-header">
|
||||
<section className="status inverse">
|
||||
<LiveStatusIndicator result={status} startTime={run.startTime}
|
||||
estimatedDuration={run.estimatedDurationInMillis}
|
||||
|
@ -57,11 +49,11 @@ class RunDetailsHeader extends Component {
|
|||
<section className="table">
|
||||
<h4>
|
||||
<a onClick={() => this.handleOrganizationClick()}>{run.organization}</a>
|
||||
/
|
||||
{ cleanFullName && `${cleanFullName} / `}
|
||||
<a onClick={() => this.handleNameClick()}>{displayName}</a>
|
||||
|
||||
#{run.id}
|
||||
<span> / </span>
|
||||
<a className="path-link" onClick={() => this.handleNameClick()}>
|
||||
<ExpandablePath path={fullDisplayName} iconSize={20} hideFirst />
|
||||
</a>
|
||||
<span> #{run.id}</span>
|
||||
</h4>
|
||||
|
||||
<div className="row">
|
||||
|
|
|
@ -185,6 +185,11 @@
|
|||
|
||||
nav.page-title { // so a missing title doesn't destroy the layout
|
||||
min-height: 4.33em;
|
||||
|
||||
h1 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.sub-header { // for the progress indicator
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import "variables";
|
||||
|
||||
@import "core";
|
||||
@import "run-details-header";
|
||||
@import "testing";
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
.run-details-header {
|
||||
h4 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.path-link {
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"env": {
|
||||
"mocha": true
|
||||
}
|
||||
}
|
|
@ -1,21 +1,21 @@
|
|||
import React from 'react';
|
||||
import {createRenderer} from 'react-addons-test-utils';
|
||||
import { assert} from 'chai';
|
||||
import sd from 'skin-deep';
|
||||
import { assert } from 'chai';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import PipelineRowItem from '../../main/js/components/PipelineRowItem.jsx';
|
||||
import { PipelineRecord } from '../../main/js/components/records.jsx';
|
||||
|
||||
const
|
||||
hack={
|
||||
MultiBranch:()=>{},
|
||||
Pr:()=>{},
|
||||
Activity:()=>{},
|
||||
} ,
|
||||
pipelineMulti = {
|
||||
const hack = {
|
||||
MultiBranch: () => {},
|
||||
Pr: () => {},
|
||||
Activity: () => {},
|
||||
};
|
||||
/* eslint-disable quote-props */
|
||||
const pipelineMulti = {
|
||||
'displayName': 'moreBeers',
|
||||
'name': 'morebeers',
|
||||
'fullName': 'beersland/morebeers',
|
||||
'fullDisplayName': 'beersland/moreBeers',
|
||||
'organization': 'jenkins',
|
||||
'weatherScore': 0,
|
||||
'branchNames': ['master'],
|
||||
|
@ -24,12 +24,13 @@ const
|
|||
'numberOfSuccessfulBranches': 0,
|
||||
'numberOfSuccessfulPullRequests': 0,
|
||||
'totalNumberOfBranches': 1,
|
||||
'totalNumberOfPullRequests': 0
|
||||
},
|
||||
pipelineMultiSuccess = {
|
||||
'totalNumberOfPullRequests': 0,
|
||||
};
|
||||
const pipelineMultiSuccess = {
|
||||
'displayName': 'moreBeersSuccess',
|
||||
'name': 'morebeersSuccess',
|
||||
'fullName': 'morebeersSuccess',
|
||||
'fullDisplayName': 'moreBeersSuccess',
|
||||
'organization': 'jenkins',
|
||||
'weatherScore': 0,
|
||||
'branchNames': ['master'],
|
||||
|
@ -38,116 +39,95 @@ const
|
|||
'numberOfSuccessfulBranches': 3,
|
||||
'numberOfSuccessfulPullRequests': 3,
|
||||
'totalNumberOfBranches': 3,
|
||||
'totalNumberOfPullRequests': 3
|
||||
},
|
||||
pipelineSimple = {
|
||||
'totalNumberOfPullRequests': 3,
|
||||
};
|
||||
const pipelineSimple = {
|
||||
'displayName': 'beers',
|
||||
'name': 'beers',
|
||||
'fullName': 'beers',
|
||||
'fullDisplayName': 'beers',
|
||||
'organization': 'jenkins',
|
||||
'weatherScore': 0
|
||||
},
|
||||
testElementSimple = (<PipelineRowItem
|
||||
hack={hack}
|
||||
pipeline={pipelineSimple}
|
||||
simple={true}/>
|
||||
),
|
||||
testElementMultiSuccess = (<PipelineRowItem
|
||||
hack={hack}
|
||||
pipeline={pipelineMultiSuccess}
|
||||
/>
|
||||
),
|
||||
testElementMulti = (<PipelineRowItem
|
||||
hack={hack}
|
||||
pipeline={pipelineMulti}/>
|
||||
);
|
||||
'weatherScore': 0,
|
||||
};
|
||||
/* eslint-enable quote-props */
|
||||
|
||||
describe("PipelineRecord can be created ", () => {
|
||||
it("without error", () => {
|
||||
const pipeRecord = new PipelineRecord(pipelineMultiSuccess);
|
||||
})
|
||||
describe('PipelineRecord', () => {
|
||||
it('create without error', () => {
|
||||
const pipelineRecord = new PipelineRecord(pipelineMultiSuccess);
|
||||
assert.isOk(pipelineRecord);
|
||||
});
|
||||
});
|
||||
|
||||
describe("pipeline component simple rendering", () => {
|
||||
const
|
||||
renderer = createRenderer();
|
||||
describe('PipelineRowItem', () => {
|
||||
it('simple pipeline', () => {
|
||||
const wrapper = shallow(
|
||||
<PipelineRowItem
|
||||
hack={hack}
|
||||
pipeline={pipelineSimple}
|
||||
simple
|
||||
/>
|
||||
);
|
||||
assert.equal(wrapper.find('tr').length, 1);
|
||||
|
||||
before('render element', () => renderer.render(testElementSimple));
|
||||
const columns = wrapper.find('td');
|
||||
|
||||
it("renders a pipeline", () => {
|
||||
const
|
||||
result = renderer.getRenderOutput(),
|
||||
children = result.props.children;
|
||||
|
||||
assert.equal(result.type, 'tr');
|
||||
assert.equal(children[0].props.children.props.children, pipelineSimple.fullName);
|
||||
// simple element has no children
|
||||
assert.equal(children[2].type, 'td');
|
||||
assert.isObject(children[2].props);
|
||||
assert.equal(children[2].props.children, ' - ');
|
||||
});
|
||||
});
|
||||
|
||||
describe("pipeline component multiBranch rendering", () => {
|
||||
const
|
||||
renderer = createRenderer();
|
||||
|
||||
before('render element', () => renderer.render(testElementMulti));
|
||||
|
||||
it("renders a pipeline with error branch", () => {
|
||||
const
|
||||
result = renderer.getRenderOutput(),
|
||||
children = result.props.children;
|
||||
|
||||
assert.equal(result.type, 'tr');
|
||||
assert.equal(children[0].props.children.props.children, "beersland / moreBeers");
|
||||
// simple element has no children
|
||||
assert.equal(children[2].type, 'td');
|
||||
assert.isObject(children[2].props);
|
||||
// multiBranch has more information
|
||||
assert.isDefined(children[2].props.children);
|
||||
assert.equal(children[2].props.children.props.children[0], pipelineMulti.numberOfFailingBranches);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("pipeline component multiBranch rendering - success", () => {
|
||||
const
|
||||
renderer = createRenderer();
|
||||
|
||||
before('render element', () => renderer.render(testElementMultiSuccess));
|
||||
|
||||
it("renders a pipeline with success branch", () => {
|
||||
const
|
||||
result = renderer.getRenderOutput(),
|
||||
children = result.props.children;
|
||||
|
||||
assert.equal(result.type, 'tr');
|
||||
assert.equal(children[0].props.children.props.children, pipelineMultiSuccess.displayName);
|
||||
// simple element has no children
|
||||
assert.equal(children[2].type, 'td');
|
||||
assert.isObject(children[2].props);
|
||||
// multiBranch has more information
|
||||
assert.isDefined(children[2].props.children);
|
||||
assert.equal(children[2].props.children.props.children[0], pipelineMultiSuccess.numberOfSuccessfulBranches);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("weatherIcon pipeline component simple rendering", () => {
|
||||
const
|
||||
renderer = createRenderer();
|
||||
|
||||
before('render element', () => renderer.render(testElementSimple));
|
||||
|
||||
it("renders a weather-icon", () => {
|
||||
const
|
||||
result = renderer.getRenderOutput(),
|
||||
children = result.props.children,
|
||||
tree = sd.shallowRender(children[1].props.children),
|
||||
vdom = tree.getRenderOutput();
|
||||
|
||||
assert.oneOf('weather-icon', vdom.props.className.split(' '));
|
||||
});
|
||||
const nameCol = columns.at(0);
|
||||
const path = nameCol.find('Link').shallow().find('ExpandablePath');
|
||||
assert.equal(path.props().path, pipelineSimple.fullDisplayName);
|
||||
|
||||
const weatherCol = columns.at(1);
|
||||
assert.equal(weatherCol.text(), '<WeatherIcon />');
|
||||
|
||||
const multibranchCol = columns.at(2);
|
||||
assert.equal(multibranchCol.text(), ' - ');
|
||||
|
||||
const pullRequestsCol = columns.at(3);
|
||||
assert.equal(pullRequestsCol.text(), ' - ');
|
||||
});
|
||||
|
||||
describe('multiBranch', () => {
|
||||
it('with failing items', () => {
|
||||
const wrapper = shallow(
|
||||
<PipelineRowItem
|
||||
hack={hack}
|
||||
pipeline={pipelineMulti}
|
||||
/>
|
||||
);
|
||||
assert.equal(wrapper.find('tr').length, 1);
|
||||
|
||||
const columns = wrapper.find('td');
|
||||
|
||||
const nameCol = columns.at(0);
|
||||
const path = nameCol.find('Link').shallow().find('ExpandablePath');
|
||||
assert.equal(path.props().path, pipelineMulti.fullDisplayName);
|
||||
|
||||
const multibranchCol = columns.at(2).find('Link').shallow();
|
||||
assert.equal(multibranchCol.text(), '1 failing');
|
||||
|
||||
const pullRequestsCol = columns.at(3);
|
||||
assert.equal(pullRequestsCol.text(), '');
|
||||
});
|
||||
|
||||
it('with success', () => {
|
||||
const wrapper = shallow(
|
||||
<PipelineRowItem
|
||||
hack={hack}
|
||||
pipeline={pipelineMultiSuccess}
|
||||
/>
|
||||
);
|
||||
assert.equal(wrapper.find('tr').length, 1);
|
||||
|
||||
const columns = wrapper.find('td');
|
||||
|
||||
const nameCol = columns.at(0);
|
||||
const path = nameCol.find('Link').shallow().find('ExpandablePath');
|
||||
assert.equal(path.props().path, pipelineMultiSuccess.fullDisplayName);
|
||||
|
||||
const multibranchCol = columns.at(2).find('Link').shallow();
|
||||
assert.equal(multibranchCol.text(), '3 passing');
|
||||
|
||||
const pullRequestsCol = columns.at(3).find('Link').shallow();
|
||||
assert.equal(pullRequestsCol.text(), '3 passing');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
"resolved": "https://registry.npmjs.org/@jenkins-cd/blueocean-core-js/-/blueocean-core-js-0.0.20.tgz"
|
||||
},
|
||||
"@jenkins-cd/design-language": {
|
||||
"version": "0.0.83",
|
||||
"from": "@jenkins-cd/design-language@0.0.83",
|
||||
"resolved": "https://registry.npmjs.org/@jenkins-cd/design-language/-/design-language-0.0.83.tgz"
|
||||
"version": "0.0.85",
|
||||
"from": "@jenkins-cd/design-language@0.0.85",
|
||||
"resolved": "https://registry.npmjs.org/@jenkins-cd/design-language/-/design-language-0.0.85.tgz"
|
||||
},
|
||||
"@jenkins-cd/diag": {
|
||||
"version": "0.0.2",
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@jenkins-cd/blueocean-core-js": "0.0.20",
|
||||
"@jenkins-cd/design-language": "0.0.83",
|
||||
"@jenkins-cd/design-language": "0.0.85",
|
||||
"@jenkins-cd/js-extensions": "0.0.27",
|
||||
"@jenkins-cd/js-modules": "0.0.8",
|
||||
"immutable": "3.8.1",
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import React, { Component, PropTypes } from 'react';
|
||||
import { Link } from 'react-router';
|
||||
import { capable, UrlBuilder } from '@jenkins-cd/blueocean-core-js';
|
||||
import { Favorite, LiveStatusIndicator } from '@jenkins-cd/design-language';
|
||||
import { ExpandablePath, Favorite, LiveStatusIndicator } from '@jenkins-cd/design-language';
|
||||
import { RunButton, ReplayButton } from '@jenkins-cd/blueocean-core-js';
|
||||
|
||||
const stopProp = (event) => {
|
||||
|
@ -133,6 +133,9 @@ export class PipelineCard extends Component {
|
|||
const isBranch = capable(runnableItem, BRANCH_CAPABILITY);
|
||||
const names = extractNames(runnableItem, isBranch);
|
||||
const organization = runnableItem.organization;
|
||||
const fullDisplayName = isBranch ?
|
||||
runnableItem.fullDisplayName.split('/').slice(0, -1).join('/') :
|
||||
runnableItem.fullDisplayName;
|
||||
|
||||
let status;
|
||||
let startTime = null;
|
||||
|
@ -163,7 +166,7 @@ export class PipelineCard extends Component {
|
|||
|
||||
<span className="name">
|
||||
<Link to={activityUrl} onClick={(event) => stopProp(event)}>
|
||||
{organization} / <span title={names.fullName}>{names.pipelineName}</span>
|
||||
<ExpandablePath path={`${organization}/${fullDisplayName}`} />
|
||||
</Link>
|
||||
</span>
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ describe('DashboardCards', () => {
|
|||
testUtils.unbindAll();
|
||||
});
|
||||
|
||||
it('renders without error for empty props', () => {
|
||||
xit('renders without error for empty props', () => {
|
||||
const wrapper = shallow(
|
||||
<DashboardCards />
|
||||
);
|
||||
|
|
|
@ -103,6 +103,11 @@ public class MultiBranchPipelineImpl extends BlueMultiBranchPipeline {
|
|||
return mbp.getFullName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFullDisplayName() {
|
||||
return AbstractPipelineImpl.getFullDisplayName(mbp, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTotalNumberOfBranches(){
|
||||
return countJobs(false);
|
||||
|
|
|
@ -124,6 +124,7 @@ public class MultiBranchTest extends PipelineBaseTest {
|
|||
public void getMultiBranchPipelineInsideFolder() throws IOException, ExecutionException, InterruptedException {
|
||||
MockFolder folder1 = j.createFolder("folder1");
|
||||
WorkflowMultiBranchProject mp = folder1.createProject(WorkflowMultiBranchProject.class, "p");
|
||||
mp.setDisplayName("My MBP");
|
||||
|
||||
mp.getSourcesList().add(new BranchSource(new GitSCMSource(null, sampleRepo.toString(), "", "*", "", false),
|
||||
new DefaultBranchPropertyStrategy(new BranchProperty[0])));
|
||||
|
@ -138,6 +139,9 @@ public class MultiBranchTest extends PipelineBaseTest {
|
|||
validateMultiBranchPipeline(mp, r, 3);
|
||||
Assert.assertEquals("/blue/rest/organizations/jenkins/pipelines/folder1/pipelines/p/",
|
||||
((Map)((Map)r.get("_links")).get("self")).get("href"));
|
||||
Assert.assertEquals("folder1/My%20MBP", r.get("fullDisplayName"));
|
||||
r = get("/organizations/jenkins/pipelines/folder1/pipelines/p/master/");
|
||||
Assert.assertEquals("folder1/My%20MBP/master", r.get("fullDisplayName"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -5,9 +5,11 @@ import com.google.common.collect.ImmutableMap;
|
|||
import com.google.common.collect.Iterators;
|
||||
import com.google.common.collect.Lists;
|
||||
import hudson.Extension;
|
||||
import hudson.Util;
|
||||
import hudson.model.AbstractItem;
|
||||
import hudson.model.Action;
|
||||
import hudson.model.Item;
|
||||
import hudson.model.ItemGroup;
|
||||
import hudson.model.Job;
|
||||
import hudson.model.Run;
|
||||
import hudson.model.User;
|
||||
|
@ -33,6 +35,8 @@ import org.kohsuke.stapler.WebMethod;
|
|||
import org.kohsuke.stapler.json.JsonBody;
|
||||
import org.kohsuke.stapler.verb.DELETE;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
@ -141,6 +145,33 @@ public class AbstractPipelineImpl extends BluePipeline {
|
|||
return job.getFullName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFullDisplayName() {
|
||||
return getFullDisplayName(job.getParent(), Util.rawEncode(job.getDisplayName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns full display name. Each display name is separated by '/' and each display name is url encoded.
|
||||
*
|
||||
* @param parent parent folder
|
||||
* @param displayName URL encoded display name. Caller must pass urlencoded name
|
||||
*
|
||||
* @return full display name
|
||||
*/
|
||||
public static String getFullDisplayName(@Nonnull ItemGroup parent, @Nullable String displayName){
|
||||
String name = parent.getDisplayName();
|
||||
if(name.length() == 0 ) return displayName;
|
||||
|
||||
if(name.length() > 0 && parent instanceof AbstractItem) {
|
||||
if(displayName == null){
|
||||
return getFullDisplayName(((AbstractItem)parent).getParent(), String.format("%s", Util.rawEncode(name)));
|
||||
}else {
|
||||
return getFullDisplayName(((AbstractItem) parent).getParent(), String.format("%s/%s", Util.rawEncode(name),displayName));
|
||||
}
|
||||
}
|
||||
return displayName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Link getLink() {
|
||||
return OrganizationImpl.INSTANCE.getLink().rel("pipelines").rel(getRecursivePathFromFullName(this));
|
||||
|
|
|
@ -41,7 +41,10 @@ public class PipelineFolderImpl extends BluePipelineFolder {
|
|||
|
||||
@Override
|
||||
public String getName() {
|
||||
return folder.getDisplayName();
|
||||
if(folder instanceof AbstractItem)
|
||||
return ((AbstractItem) folder).getName();
|
||||
else
|
||||
return folder.getDisplayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -54,6 +57,11 @@ public class PipelineFolderImpl extends BluePipelineFolder {
|
|||
return folder.getFullName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFullDisplayName() {
|
||||
return AbstractPipelineImpl.getFullDisplayName(folder, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<BlueActionProxy> getActions() {
|
||||
return Collections.emptyList();
|
||||
|
|
|
@ -67,6 +67,7 @@ public class PipelineApiTest extends BaseTest {
|
|||
MockFolder folder1 = j.createFolder("folder1");
|
||||
Project p1 = folder1.createProject(FreeStyleProject.class, "test1");
|
||||
MockFolder folder2 = folder1.createProject(MockFolder.class, "folder2");
|
||||
folder2.setDisplayName("My folder2");
|
||||
MockFolder folder3 = folder1.createProject(MockFolder.class, "folder3");
|
||||
Project p2 = folder2.createProject(FreeStyleProject.class, "test2");
|
||||
|
||||
|
@ -85,6 +86,7 @@ public class PipelineApiTest extends BaseTest {
|
|||
Assert.assertEquals(3, pipelines.size());
|
||||
Assert.assertEquals("folder2", pipelines.get(0).get("name"));
|
||||
Assert.assertEquals("folder1/folder2", pipelines.get(0).get("fullName"));
|
||||
Assert.assertEquals("folder1/My%20folder2", pipelines.get(0).get("fullDisplayName"));
|
||||
|
||||
response = get("/organizations/jenkins/pipelines/folder1");
|
||||
Assert.assertEquals("folder1", response.get("name"));
|
||||
|
|
|
@ -24,6 +24,7 @@ public abstract class BluePipeline extends Resource {
|
|||
public static final String NAME="name";
|
||||
public static final String DISPLAY_NAME="displayName";
|
||||
public static final String FULL_NAME="fullName";
|
||||
public static final String FULL_DISPLAY_NAME="fullDisplayName";
|
||||
public static final String WEATHER_SCORE ="weatherScore";
|
||||
public static final String LATEST_RUN = "latestRun";
|
||||
public static final String ESTIMATED_DURATION = "estimatedDurationInMillis";
|
||||
|
@ -62,11 +63,19 @@ public abstract class BluePipeline extends Resource {
|
|||
public abstract String getDisplayName();
|
||||
|
||||
/**
|
||||
* @return Includes parentLink folders if any. For example folder1/folder2/p1
|
||||
* @return Includes parent folders names if any. For example folder1/folder2/p1
|
||||
*/
|
||||
@Exported(name = FULL_NAME)
|
||||
public abstract String getFullName();
|
||||
|
||||
|
||||
/**
|
||||
* @return Includes display names of parent folders if any. For example folder1/myFolder2/p1
|
||||
*/
|
||||
@Exported(name = FULL_DISPLAY_NAME)
|
||||
public abstract String getFullDisplayName();
|
||||
|
||||
|
||||
/**
|
||||
* @return weather health score percentile
|
||||
*/
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
"resolved": "https://registry.npmjs.org/@jenkins-cd/blueocean-core-js/-/blueocean-core-js-0.0.20.tgz"
|
||||
},
|
||||
"@jenkins-cd/design-language": {
|
||||
"version": "0.0.83",
|
||||
"from": "@jenkins-cd/design-language@0.0.83",
|
||||
"resolved": "https://registry.npmjs.org/@jenkins-cd/design-language/-/design-language-0.0.83.tgz"
|
||||
"version": "0.0.85",
|
||||
"from": "@jenkins-cd/design-language@0.0.85",
|
||||
"resolved": "https://registry.npmjs.org/@jenkins-cd/design-language/-/design-language-0.0.85.tgz"
|
||||
},
|
||||
"@jenkins-cd/diag": {
|
||||
"version": "0.0.2",
|
||||
|
@ -3114,7 +3114,8 @@
|
|||
"dependencies": {
|
||||
"tough-cookie": {
|
||||
"version": "2.3.1",
|
||||
"from": "tough-cookie@>=2.3.0 <2.4.0",
|
||||
"from": "tough-cookie@2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.1.tgz",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@jenkins-cd/blueocean-core-js": "0.0.20",
|
||||
"@jenkins-cd/design-language": "0.0.83",
|
||||
"@jenkins-cd/design-language": "0.0.85",
|
||||
"@jenkins-cd/js-extensions": "0.0.27",
|
||||
"@jenkins-cd/js-modules": "0.0.8",
|
||||
"history": "2.0.2",
|
||||
|
|
Loading…
Reference in New Issue