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:
Cliff Meyers 2016-10-21 10:13:49 -04:00 committed by GitHub
parent 2cbc261e3b
commit de288bef83
22 changed files with 220 additions and 167 deletions

View File

@ -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",

View File

@ -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",

View File

@ -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>&nbsp;/&nbsp;</span>
<Link to={activityUrl}>
<ExpandablePath path={fullDisplayName} iconSize={20} hideFirst />
</Link>
</h1>
<Extensions.Renderer
extensionPoint="jenkins.pipeline.detail.header.action"

View File

@ -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

View File

@ -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>
&nbsp;/&nbsp;
{ cleanFullName && `${cleanFullName} / `}
<a onClick={() => this.handleNameClick()}>{displayName}</a>
&nbsp;
#{run.id}
<span>&nbsp;/&nbsp;</span>
<a className="path-link" onClick={() => this.handleNameClick()}>
<ExpandablePath path={fullDisplayName} iconSize={20} hideFirst />
</a>
<span>&nbsp;#{run.id}</span>
</h4>
<div className="row">

View File

@ -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

View File

@ -1,4 +1,5 @@
@import "variables";
@import "core";
@import "run-details-header";
@import "testing";

View File

@ -0,0 +1,10 @@
.run-details-header {
h4 {
display: flex;
align-items: center;
.path-link {
display: inline-flex;
}
}
}

View File

@ -0,0 +1,5 @@
{
"env": {
"mocha": true
}
}

View File

@ -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');
});
});
});

View File

@ -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",

View File

@ -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",

View File

@ -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>

View File

@ -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 />
);

View File

@ -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);

View File

@ -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

View File

@ -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));

View File

@ -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();

View File

@ -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"));

View File

@ -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
*/

View File

@ -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
}
}

View File

@ -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",