Merge pull request #321 from jenkinsci/feature/JENKINS-36286
JENKINS-36286# LinkResolver to resolve link of a jenkins model object…
This commit is contained in:
commit
b5a28196b4
|
@ -6,6 +6,7 @@ import com.google.inject.Module;
|
|||
import hudson.Extension;
|
||||
import hudson.model.RootAction;
|
||||
import io.jenkins.blueocean.BlueOceanUI;
|
||||
import io.jenkins.blueocean.rest.hal.LinkResolver;
|
||||
import org.kohsuke.stapler.StaplerProxy;
|
||||
|
||||
/**
|
||||
|
@ -48,6 +49,7 @@ public class BlueOceanRootAction implements RootAction, StaplerProxy {
|
|||
@Override
|
||||
public void configure(Binder binder) {
|
||||
binder.bind(BlueOceanUI.class).toInstance(new BlueOceanUI(URL_BASE));
|
||||
binder.bind(LinkResolver.class).to(LinkResolverImpl.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
package io.jenkins.blueocean.service.embedded;
|
||||
|
||||
import hudson.model.Item;
|
||||
import hudson.model.ItemGroup;
|
||||
import hudson.model.Job;
|
||||
import hudson.model.Queue;
|
||||
import hudson.model.Run;
|
||||
import io.jenkins.blueocean.rest.hal.Link;
|
||||
import io.jenkins.blueocean.rest.hal.LinkResolver;
|
||||
import io.jenkins.blueocean.rest.model.BluePipeline;
|
||||
import io.jenkins.blueocean.rest.model.BlueRun;
|
||||
import io.jenkins.blueocean.rest.model.Resource;
|
||||
import io.jenkins.blueocean.service.embedded.rest.BluePipelineFactory;
|
||||
import io.jenkins.blueocean.service.embedded.rest.PipelineNodeUtil;
|
||||
import org.jenkinsci.plugins.workflow.cps.nodes.StepAtomNode;
|
||||
import org.jenkinsci.plugins.workflow.graph.FlowNode;
|
||||
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Implementation of {@link LinkResolver}
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
* @author Vivek Pandey
|
||||
*/
|
||||
public class LinkResolverImpl extends LinkResolver {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LinkResolverImpl.class);
|
||||
|
||||
@Override
|
||||
public Link resolve(Object modelObject) {
|
||||
if (modelObject instanceof Job) {
|
||||
Resource resource = resolveJob((Job)modelObject);
|
||||
if(resource != null){
|
||||
return resource.getLink();
|
||||
}
|
||||
}else if(modelObject instanceof Item && modelObject instanceof ItemGroup){
|
||||
Resource resource = resolveFolder((Item) modelObject);
|
||||
if(resource!=null){
|
||||
return resource.getLink();
|
||||
}
|
||||
}else if(modelObject instanceof Run){
|
||||
Run run = (Run) modelObject;
|
||||
Resource resource = resolveRun(run);
|
||||
if(resource != null){
|
||||
return resource.getLink();
|
||||
}
|
||||
}else if(modelObject instanceof FlowNode){
|
||||
FlowNode flowNode = (FlowNode) modelObject;
|
||||
BlueRun r = resolveFlowNodeRun(flowNode);
|
||||
if(PipelineNodeUtil.isParallelBranch(flowNode) || PipelineNodeUtil.isStage(flowNode)){ // its Node
|
||||
if(r != null){
|
||||
return r.getLink().rel("nodes/"+flowNode.getId());
|
||||
}
|
||||
}else if(flowNode instanceof StepAtomNode && !PipelineNodeUtil.isStage(flowNode)) {
|
||||
if(r != null){
|
||||
return r.getLink().rel("steps/"+flowNode.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Resource resolveJob(Job job){
|
||||
return BluePipelineFactory.resolve(job);
|
||||
}
|
||||
|
||||
private Resource resolveFolder(Item folder){
|
||||
return BluePipelineFactory.resolve(folder);
|
||||
}
|
||||
|
||||
private Resource resolveRun(Run run){
|
||||
Resource resource = resolveJob(run.getParent());
|
||||
if(resource instanceof BluePipeline){
|
||||
BluePipeline pipeline = (BluePipeline) resource;
|
||||
return pipeline.getRuns().get(run.getId());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private BlueRun resolveFlowNodeRun(FlowNode flowNode){
|
||||
try {
|
||||
Queue.Executable executable = flowNode.getExecution().getOwner().getExecutable();
|
||||
if(executable!= null && executable instanceof WorkflowRun){
|
||||
WorkflowRun run = (WorkflowRun) executable;
|
||||
return (BlueRun) resolveRun(run);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
package io.jenkins.blueocean.service.embedded.rest;
|
||||
|
||||
import hudson.ExtensionList;
|
||||
import hudson.ExtensionPoint;
|
||||
import hudson.model.Item;
|
||||
import hudson.model.ItemGroup;
|
||||
import io.jenkins.blueocean.rest.Reachable;
|
||||
import io.jenkins.blueocean.rest.model.BluePipeline;
|
||||
import io.jenkins.blueocean.rest.model.Resource;
|
||||
import jenkins.model.Jenkins;
|
||||
|
||||
/**
|
||||
* Factory that gives instance of {@link BluePipeline}
|
||||
*
|
||||
* It's useful for example in cases where a plugin that has custom project and they want to serve
|
||||
* extra meta-data thru BluePipeline, would provide implementation of their BluePipeline and and implementation
|
||||
* of BluePipelineFactory.
|
||||
*
|
||||
* @author Vivek Pandey
|
||||
*/
|
||||
public abstract class BluePipelineFactory implements ExtensionPoint {
|
||||
public abstract BluePipeline getPipeline(Item item, Reachable parent);
|
||||
|
||||
/**
|
||||
* Finds a blue ocean API model object that pairs up with the given {@code target},
|
||||
* by looking at the intermediate core model object {@code context} that is an ancestor
|
||||
* of {@code target}.
|
||||
*
|
||||
* If this {@link BluePipelineFactory} understands how to map {@code context} to
|
||||
* {@link BluePipeline} (as in {@code getPipeline(item,parent)!=null}), then the resolve
|
||||
* method should also apply the same logic to map {@code context} and then recursively
|
||||
* resolve {@code target}.
|
||||
*
|
||||
* @param context
|
||||
* This is always an ancestor of target (including target==context)
|
||||
* @param parent
|
||||
* The parent object of the blue ocean API model object that pairs up with the 'context' parameter
|
||||
* @param target
|
||||
* The core model object that we are trying to map to {@link Resource}
|
||||
*
|
||||
* @return
|
||||
* null if this implementation doesn't know how to map {@code context} to a blue ocean API model object.
|
||||
* Otherwise return the BO API model object that pairs up with {@code target}
|
||||
*/
|
||||
public abstract Resource resolve(Item context, Reachable parent, Item target);
|
||||
|
||||
public static ExtensionList<BluePipelineFactory> all(){
|
||||
return ExtensionList.lookup(BluePipelineFactory.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a Job in Jenkins, map that to corresponding blue ocean API object,
|
||||
* for example so that you can get its URL.
|
||||
*/
|
||||
public static Resource resolve(Item item) {
|
||||
Item nextStep = findNextStep(Jenkins.getInstance(), item);
|
||||
|
||||
for (BluePipelineFactory f : all()) {
|
||||
Resource r = f.resolve(nextStep, OrganizationImpl.INSTANCE.getPipelines(), item);
|
||||
if (r!=null) return r;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the immediate child of 'context' that is also the ancestor of 'target'
|
||||
*/
|
||||
protected static Item findNextStep(ItemGroup context, Item target) {
|
||||
Item i = null;
|
||||
while (context!=target) {
|
||||
i = target;
|
||||
if (target.getParent() instanceof Item) {
|
||||
target = (Item) target.getParent();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return i == null ? target : i;
|
||||
}
|
||||
}
|
|
@ -7,7 +7,6 @@ import hudson.model.Job;
|
|||
import io.jenkins.blueocean.rest.Reachable;
|
||||
import io.jenkins.blueocean.rest.hal.Link;
|
||||
import io.jenkins.blueocean.rest.model.BluePipeline;
|
||||
import io.jenkins.blueocean.rest.model.BluePipelineFactory;
|
||||
import io.jenkins.blueocean.rest.model.Resource;
|
||||
import jenkins.scm.api.SCMHead;
|
||||
import jenkins.scm.api.actions.ChangeRequestAction;
|
||||
|
@ -55,6 +54,14 @@ public class BranchImpl extends PipelineImpl {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource resolve(Item context, Reachable parent, Item target) {
|
||||
if (context==target.getParent()) {
|
||||
return getPipeline(context,parent);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PullRequest extends Resource {
|
||||
|
|
|
@ -14,11 +14,11 @@ import io.jenkins.blueocean.rest.model.BlueFavoriteAction;
|
|||
import io.jenkins.blueocean.rest.model.BlueMultiBranchPipeline;
|
||||
import io.jenkins.blueocean.rest.model.BluePipeline;
|
||||
import io.jenkins.blueocean.rest.model.BluePipelineContainer;
|
||||
import io.jenkins.blueocean.rest.model.BluePipelineFactory;
|
||||
import io.jenkins.blueocean.rest.model.BlueQueueContainer;
|
||||
import io.jenkins.blueocean.rest.model.BlueQueueItem;
|
||||
import io.jenkins.blueocean.rest.model.BlueRun;
|
||||
import io.jenkins.blueocean.rest.model.BlueRunContainer;
|
||||
import io.jenkins.blueocean.rest.model.Resource;
|
||||
import io.jenkins.blueocean.service.embedded.util.FavoriteUtil;
|
||||
import jenkins.branch.MultiBranchProject;
|
||||
import jenkins.scm.api.SCMHead;
|
||||
|
@ -293,11 +293,25 @@ public class MultiBranchPipelineImpl extends BlueMultiBranchPipeline {
|
|||
public static class PipelineFactoryImpl extends BluePipelineFactory{
|
||||
|
||||
@Override
|
||||
public BluePipeline getPipeline(Item item, Reachable parent) {
|
||||
public MultiBranchPipelineImpl getPipeline(Item item, Reachable parent) {
|
||||
if (item instanceof MultiBranchProject) {
|
||||
return new MultiBranchPipelineImpl((MultiBranchProject) item);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource resolve(Item context, Reachable parent, Item target) {
|
||||
if (context instanceof MultiBranchProject) {
|
||||
if (context==target)
|
||||
return getPipeline(context,parent);
|
||||
if (context==target.getParent()) {
|
||||
// target is a branch
|
||||
return getPipeline(context,parent).getBranches().get(target.getName());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import io.jenkins.blueocean.rest.Reachable;
|
|||
import io.jenkins.blueocean.rest.hal.Link;
|
||||
import io.jenkins.blueocean.rest.model.BluePipeline;
|
||||
import io.jenkins.blueocean.rest.model.BluePipelineContainer;
|
||||
import io.jenkins.blueocean.rest.model.BluePipelineFactory;
|
||||
import jenkins.model.Jenkins;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
|
|
@ -10,8 +10,8 @@ import io.jenkins.blueocean.rest.model.BlueActionProxy;
|
|||
import io.jenkins.blueocean.rest.model.BlueFavoriteAction;
|
||||
import io.jenkins.blueocean.rest.model.BluePipeline;
|
||||
import io.jenkins.blueocean.rest.model.BluePipelineContainer;
|
||||
import io.jenkins.blueocean.rest.model.BluePipelineFactory;
|
||||
import io.jenkins.blueocean.rest.model.BluePipelineFolder;
|
||||
import io.jenkins.blueocean.rest.model.Resource;
|
||||
import io.jenkins.blueocean.service.embedded.util.FavoriteUtil;
|
||||
import org.kohsuke.stapler.json.JsonBody;
|
||||
|
||||
|
@ -104,11 +104,28 @@ public class PipelineFolderImpl extends BluePipelineFolder {
|
|||
public static class PipelineFactoryImpl extends BluePipelineFactory{
|
||||
|
||||
@Override
|
||||
public BluePipeline getPipeline(Item item, Reachable parent) {
|
||||
public PipelineFolderImpl getPipeline(Item item, Reachable parent) {
|
||||
if (item instanceof ItemGroup) {
|
||||
return new PipelineFolderImpl((ItemGroup) item, parent.getLink());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource resolve(Item context, Reachable parent, Item target) {
|
||||
PipelineFolderImpl folder = getPipeline(context, parent);
|
||||
if (folder!=null) {
|
||||
if(context == target){
|
||||
return folder;
|
||||
}
|
||||
Item nextChild = findNextStep(folder.folder,target);
|
||||
for (BluePipelineFactory f : all()) {
|
||||
Resource answer = f.resolve(nextChild, folder, target);
|
||||
if (answer!=null)
|
||||
return answer;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,10 +11,10 @@ import io.jenkins.blueocean.rest.hal.Link;
|
|||
import io.jenkins.blueocean.rest.model.BlueActionProxy;
|
||||
import io.jenkins.blueocean.rest.model.BlueFavoriteAction;
|
||||
import io.jenkins.blueocean.rest.model.BluePipeline;
|
||||
import io.jenkins.blueocean.rest.model.BluePipelineFactory;
|
||||
import io.jenkins.blueocean.rest.model.BlueQueueContainer;
|
||||
import io.jenkins.blueocean.rest.model.BlueRun;
|
||||
import io.jenkins.blueocean.rest.model.BlueRunContainer;
|
||||
import io.jenkins.blueocean.rest.model.Resource;
|
||||
import io.jenkins.blueocean.service.embedded.util.FavoriteUtil;
|
||||
import org.kohsuke.stapler.Stapler;
|
||||
import org.kohsuke.stapler.WebMethod;
|
||||
|
@ -150,6 +150,14 @@ public class PipelineImpl extends BluePipeline {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource resolve(Item context, Reachable parent, Item target) {
|
||||
if(context == target.getParent()){
|
||||
return getPipeline(target,parent);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Collection<BlueActionProxy> getActionProxies(List<? extends Action> actions, Reachable parent){
|
||||
|
|
|
@ -12,11 +12,13 @@ import hudson.Util;
|
|||
import hudson.model.Job;
|
||||
import hudson.model.Run;
|
||||
import io.jenkins.blueocean.commons.JsonConverter;
|
||||
import io.jenkins.blueocean.service.embedded.rest.PipelineNodeUtil;
|
||||
import jenkins.branch.MultiBranchProject;
|
||||
import org.jenkinsci.plugins.workflow.actions.ThreadNameAction;
|
||||
import org.jenkinsci.plugins.workflow.graph.FlowNode;
|
||||
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
|
||||
import org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject;
|
||||
import org.jenkinsci.plugins.workflow.support.visualization.table.FlowGraphTable;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
|
@ -27,7 +29,9 @@ import org.slf4j.LoggerFactory;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.LogManager;
|
||||
|
||||
|
@ -304,6 +308,29 @@ public abstract class BaseTest {
|
|||
return baseUrl + path;
|
||||
}
|
||||
|
||||
|
||||
protected List<FlowNode> getStages(FlowGraphTable nodeGraphTable){
|
||||
List<FlowNode> nodes = new ArrayList<>();
|
||||
for(FlowGraphTable.Row row: nodeGraphTable.getRows()){
|
||||
if(PipelineNodeUtil.isStage(row.getNode()) ||
|
||||
PipelineNodeUtil.isParallelBranch(row.getNode())){
|
||||
nodes.add(row.getNode());
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
protected List<FlowNode> getParallelNodes(FlowGraphTable nodeGraphTable){
|
||||
List<FlowNode> parallelNodes = new ArrayList<>();
|
||||
|
||||
for(FlowGraphTable.Row row: nodeGraphTable.getRows()){
|
||||
if(PipelineNodeUtil.isParallelBranch(row.getNode())){
|
||||
parallelNodes.add(row.getNode());
|
||||
}
|
||||
}
|
||||
return parallelNodes;
|
||||
}
|
||||
|
||||
public RequestBuilder request() {
|
||||
return new RequestBuilder(baseUrl);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
package io.jenkins.blueocean.service.embedded;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import hudson.model.FreeStyleProject;
|
||||
import hudson.model.Project;
|
||||
import hudson.model.Run;
|
||||
import io.jenkins.blueocean.rest.hal.LinkResolver;
|
||||
import io.jenkins.blueocean.service.embedded.rest.PipelineNodeGraphBuilder;
|
||||
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
|
||||
import org.jenkinsci.plugins.workflow.graph.FlowNode;
|
||||
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
|
||||
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
|
||||
import org.jenkinsci.plugins.workflow.support.visualization.table.FlowGraphTable;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.jvnet.hudson.test.MockFolder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
/**
|
||||
* @author Vivek Pandey
|
||||
*/
|
||||
public class LinkResolverTest extends BaseTest {
|
||||
@Inject
|
||||
private LinkResolver linkResolver;
|
||||
|
||||
|
||||
@Override
|
||||
public void setup() throws Exception {
|
||||
super.setup();
|
||||
j.jenkins.getInjector().injectMembers(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nestedFolderJobLinkResolveTest() throws IOException {
|
||||
Project f = j.createFreeStyleProject("fstyle1");
|
||||
MockFolder folder1 = j.createFolder("folder1");
|
||||
Project p1 = folder1.createProject(FreeStyleProject.class, "test1");
|
||||
MockFolder folder2 = folder1.createProject(MockFolder.class, "folder2");
|
||||
MockFolder folder3 = folder2.createProject(MockFolder.class, "folder3");
|
||||
Project p2 = folder2.createProject(FreeStyleProject.class, "test2");
|
||||
Project p3 = folder3.createProject(FreeStyleProject.class, "test3");
|
||||
|
||||
Assert.assertEquals("/blue/rest/organizations/jenkins/pipelines/fstyle1/",linkResolver.resolve(f).getHref());
|
||||
Assert.assertEquals("/blue/rest/organizations/jenkins/pipelines/folder1/",linkResolver.resolve(folder1).getHref());
|
||||
Assert.assertEquals("/blue/rest/organizations/jenkins/pipelines/folder1/pipelines/test1/",linkResolver.resolve(p1).getHref());
|
||||
Assert.assertEquals("/blue/rest/organizations/jenkins/pipelines/folder1/pipelines/folder2/",linkResolver.resolve(folder2).getHref());
|
||||
Assert.assertEquals("/blue/rest/organizations/jenkins/pipelines/folder1/pipelines/folder2/pipelines/test2/",linkResolver.resolve(p2).getHref());
|
||||
Assert.assertEquals("/blue/rest/organizations/jenkins/pipelines/folder1/pipelines/folder2/pipelines/folder3/",linkResolver.resolve(folder3).getHref());
|
||||
Assert.assertEquals("/blue/rest/organizations/jenkins/pipelines/folder1/pipelines/folder2/pipelines/folder3/pipelines/test3/",linkResolver.resolve(p3).getHref());
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void runLinkResolveTest() throws IOException, ExecutionException, InterruptedException {
|
||||
Project f = j.createFreeStyleProject("fstyle1");
|
||||
MockFolder folder1 = j.createFolder("folder1");
|
||||
Project p1 = folder1.createProject(FreeStyleProject.class, "test1");
|
||||
MockFolder folder2 = folder1.createProject(MockFolder.class, "folder2");
|
||||
MockFolder folder3 = folder2.createProject(MockFolder.class, "folder3");
|
||||
Project p2 = folder2.createProject(FreeStyleProject.class, "test2");
|
||||
Project p3 = folder3.createProject(FreeStyleProject.class, "test3");
|
||||
|
||||
Run r = (Run) f.scheduleBuild2(0).get();
|
||||
Assert.assertEquals("/blue/rest/organizations/jenkins/pipelines/fstyle1/runs/"+r.getId()+"/",linkResolver.resolve(r).getHref());
|
||||
|
||||
r = (Run) p1.scheduleBuild2(0).get();
|
||||
Assert.assertEquals("/blue/rest/organizations/jenkins/pipelines/folder1/pipelines/test1/runs/"+r.getId()+"/",linkResolver.resolve(r).getHref());
|
||||
|
||||
r = (Run) p2.scheduleBuild2(0).get();
|
||||
Assert.assertEquals("/blue/rest/organizations/jenkins/pipelines/folder1/pipelines/folder2/pipelines/test2/runs/"+r.getId()+"/",linkResolver.resolve(r).getHref());
|
||||
|
||||
r = (Run) p3.scheduleBuild2(0).get();
|
||||
Assert.assertEquals("/blue/rest/organizations/jenkins/pipelines/folder1/pipelines/folder2/pipelines/folder3/pipelines/test3/runs/"+r.getId()+"/",linkResolver.resolve(r).getHref());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveNodeLink() throws Exception {
|
||||
{
|
||||
WorkflowJob job1 = j.jenkins.createProject(WorkflowJob.class, "pipeline1");
|
||||
job1.setDefinition(new CpsFlowDefinition("stage \"Build\"\n" +
|
||||
" node {\n" +
|
||||
" sh \"echo here\"\n" +
|
||||
" }\n" +
|
||||
"\n" +
|
||||
"stage \"Test\"\n" +
|
||||
" parallel (\n" +
|
||||
" \"Firefox\" : {\n" +
|
||||
" node {\n" +
|
||||
" sh \"echo ffox\"\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"Chrome\" : {\n" +
|
||||
" node {\n" +
|
||||
" sh \"echo chrome\"\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" )\n" +
|
||||
"\n" +
|
||||
"stage \"CrashyMcgee\"\n" +
|
||||
" parallel (\n" +
|
||||
" \"SlowButSuccess\" : {\n" +
|
||||
" node {\n" +
|
||||
" echo 'This is time well spent.'\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"DelayThenFail\" : {\n" +
|
||||
" node {\n" +
|
||||
" echo 'Not yet.'\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" )\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"stage \"Deploy\"\n" +
|
||||
" node {\n" +
|
||||
" sh \"echo deploying\"\n" +
|
||||
" }"));
|
||||
|
||||
WorkflowRun b1 = job1.scheduleBuild2(0).get();
|
||||
j.assertBuildStatusSuccess(b1);
|
||||
|
||||
FlowGraphTable nodeGraphTable = new FlowGraphTable(b1.getExecution());
|
||||
nodeGraphTable.build();
|
||||
List<FlowNode> nodes = getStages(nodeGraphTable);
|
||||
List<FlowNode> parallelNodes = getParallelNodes(nodeGraphTable);
|
||||
|
||||
Assert.assertEquals(String.format("/blue/rest/organizations/jenkins/pipelines/pipeline1/runs/%s/nodes/%s/",
|
||||
b1.getId(),nodes.get(0).getId()),
|
||||
linkResolver.resolve(nodes.get(0)).getHref());
|
||||
|
||||
Assert.assertEquals(String.format("/blue/rest/organizations/jenkins/pipelines/pipeline1/runs/%s/nodes/%s/",
|
||||
b1.getId(),parallelNodes.get(0).getId()),
|
||||
linkResolver.resolve(parallelNodes.get(0)).getHref());
|
||||
|
||||
PipelineNodeGraphBuilder graphBuilder = new PipelineNodeGraphBuilder(b1);
|
||||
|
||||
List<FlowNode> steps = graphBuilder.getAllSteps();
|
||||
|
||||
Assert.assertEquals(String.format("/blue/rest/organizations/jenkins/pipelines/pipeline1/runs/%s/steps/%s/",
|
||||
b1.getId(),steps.get(0).getId()),
|
||||
linkResolver.resolve(steps.get(0)).getHref());
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
package io.jenkins.blueocean.service.embedded;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.inject.Inject;
|
||||
import hudson.Util;
|
||||
import hudson.model.FreeStyleProject;
|
||||
import hudson.plugins.git.util.BuildData;
|
||||
import hudson.scm.ChangeLogSet;
|
||||
import io.jenkins.blueocean.rest.hal.LinkResolver;
|
||||
import io.jenkins.blueocean.service.embedded.scm.GitSampleRepoRule;
|
||||
import jenkins.branch.BranchProperty;
|
||||
import jenkins.branch.BranchSource;
|
||||
|
@ -43,6 +45,9 @@ public class MultiBranchTest extends BaseTest{
|
|||
@Rule
|
||||
public GitSampleRepoRule sampleRepo = new GitSampleRepoRule();
|
||||
|
||||
@Inject
|
||||
private LinkResolver linkResolver;
|
||||
|
||||
|
||||
private final String[] branches={"master", "feature%2Fux-1", "feature2"};
|
||||
|
||||
|
@ -62,6 +67,29 @@ public class MultiBranchTest extends BaseTest{
|
|||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void resolveMbpLink() throws Exception {
|
||||
j.jenkins.getInjector().injectMembers(this);
|
||||
WorkflowMultiBranchProject mp = j.jenkins.createProject(WorkflowMultiBranchProject.class, "p");
|
||||
FreeStyleProject f = j.jenkins.createProject(FreeStyleProject.class, "f");
|
||||
mp.getSourcesList().add(new BranchSource(new GitSCMSource(null, sampleRepo.toString(), "", "*", "", false),
|
||||
new DefaultBranchPropertyStrategy(new BranchProperty[0])));
|
||||
for (SCMSource source : mp.getSCMSources()) {
|
||||
assertEquals(mp, source.getOwner());
|
||||
}
|
||||
|
||||
mp.scheduleBuild2(0).getFuture().get();
|
||||
|
||||
j.waitUntilNoActivity();
|
||||
|
||||
Assert.assertEquals("/blue/rest/organizations/jenkins/pipelines/p/",linkResolver.resolve(mp).getHref());
|
||||
Assert.assertEquals("/blue/rest/organizations/jenkins/pipelines/p/branches/master/",linkResolver.resolve(mp.getBranch("master")).getHref());
|
||||
Assert.assertEquals("/blue/rest/organizations/jenkins/pipelines/p/branches/feature%252Fux-1/",linkResolver.resolve(mp.getBranch("feature%2Fux-1")).getHref());
|
||||
Assert.assertEquals("/blue/rest/organizations/jenkins/pipelines/p/branches/feature2/",linkResolver.resolve(mp.getBranch("feature2")).getHref());
|
||||
Assert.assertEquals("/blue/rest/organizations/jenkins/pipelines/f/",linkResolver.resolve(f).getHref());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void getMultiBranchPipelines() throws IOException, ExecutionException, InterruptedException {
|
||||
Assume.assumeTrue(runAllTests());
|
||||
|
|
|
@ -27,7 +27,8 @@ import hudson.tasks.junit.TestResultAction;
|
|||
import io.jenkins.blueocean.rest.Reachable;
|
||||
import io.jenkins.blueocean.rest.annotation.Capability;
|
||||
import io.jenkins.blueocean.rest.model.BluePipeline;
|
||||
import io.jenkins.blueocean.rest.model.BluePipelineFactory;
|
||||
import io.jenkins.blueocean.rest.model.Resource;
|
||||
import io.jenkins.blueocean.service.embedded.rest.BluePipelineFactory;
|
||||
import io.jenkins.blueocean.service.embedded.rest.PipelineImpl;
|
||||
import jenkins.model.Jenkins;
|
||||
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
|
||||
|
@ -563,6 +564,14 @@ public class PipelineApiTest extends BaseTest {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource resolve(Item context, Reachable parent, Item target) {
|
||||
if(context == target && target instanceof Job) {
|
||||
return new TestPipelineImpl((Job) target);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Capability({"io.jenkins.blueocean.rest.annotation.test.TestPipeline", "io.jenkins.blueocean.rest.annotation.test.TestPipelineExample"})
|
||||
|
|
|
@ -11,7 +11,6 @@ import org.junit.Assert;
|
|||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
@ -1007,27 +1006,4 @@ public class PipelineNodeTest extends BaseTest {
|
|||
Assert.assertNotNull(output);
|
||||
System.out.println(output);
|
||||
}
|
||||
|
||||
private List<FlowNode> getStages(FlowGraphTable nodeGraphTable){
|
||||
List<FlowNode> nodes = new ArrayList<>();
|
||||
for(FlowGraphTable.Row row: nodeGraphTable.getRows()){
|
||||
if(PipelineNodeUtil.isStage(row.getNode()) ||
|
||||
PipelineNodeUtil.isParallelBranch(row.getNode())){
|
||||
nodes.add(row.getNode());
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
private List<FlowNode> getParallelNodes(FlowGraphTable nodeGraphTable){
|
||||
List<FlowNode> parallelNodes = new ArrayList<>();
|
||||
|
||||
for(FlowGraphTable.Row row: nodeGraphTable.getRows()){
|
||||
if(PipelineNodeUtil.isParallelBranch(row.getNode())){
|
||||
parallelNodes.add(row.getNode());
|
||||
}
|
||||
}
|
||||
return parallelNodes;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
package io.jenkins.blueocean.rest;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import hudson.Extension;
|
||||
import hudson.ExtensionList;
|
||||
import io.jenkins.blueocean.BlueOceanUI;
|
||||
import io.jenkins.blueocean.RootRoutable;
|
||||
import io.jenkins.blueocean.commons.ServiceException;
|
||||
import io.jenkins.blueocean.rest.hal.Link;
|
||||
import io.jenkins.blueocean.rest.pageable.Pageable;
|
||||
import io.jenkins.blueocean.rest.pageable.Pageables;
|
||||
import io.jenkins.blueocean.rest.pageable.PagedResponse;
|
||||
import org.kohsuke.stapler.Ancestor;
|
||||
import org.kohsuke.stapler.QueryParameter;
|
||||
import org.kohsuke.stapler.Stapler;
|
||||
import org.kohsuke.stapler.StaplerRequest;
|
||||
|
@ -26,6 +27,9 @@ import java.util.Map;
|
|||
@Extension
|
||||
public final class ApiHead implements RootRoutable, Reachable {
|
||||
|
||||
@Inject
|
||||
private BlueOceanUI blueOceanUI;
|
||||
|
||||
private final Map<String,ApiRoutable> apis = new HashMap<>();
|
||||
|
||||
public ApiHead() {
|
||||
|
@ -81,20 +85,19 @@ public final class ApiHead implements RootRoutable, Reachable {
|
|||
|
||||
@Override
|
||||
public Link getLink() {
|
||||
Ancestor apiHead = Stapler.getCurrentRequest().findAncestor(ApiHead.class);
|
||||
|
||||
String contextPath = Stapler.getCurrentRequest().getContextPath();
|
||||
String parentUrl = apiHead.getUrl();
|
||||
if(!contextPath.isEmpty() && !contextPath.equals("/")){
|
||||
int i = parentUrl.indexOf(contextPath);
|
||||
if(i>=0 && i+contextPath.length() < parentUrl.length()){
|
||||
parentUrl = parentUrl.substring(i+contextPath.length());
|
||||
}
|
||||
}
|
||||
return new Link(parentUrl);
|
||||
return new Link("/"+blueOceanUI.getUrlBase()).rel(getUrlName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives instance of ApiHead by looking in to Extensions. In some cases it might be null, such as when jenkins is
|
||||
* booting up.
|
||||
*/
|
||||
public static ApiHead INSTANCE(){
|
||||
return (ApiHead) Stapler.getCurrentRequest().findAncestor(ApiHead.class).getObject();
|
||||
ExtensionList<ApiHead> extensionList = ExtensionList.lookup(ApiHead.class);
|
||||
if(!extensionList.isEmpty()){
|
||||
return extensionList.get(0); //ApiHead is singleton
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
package io.jenkins.blueocean.rest.hal;
|
||||
|
||||
/**
|
||||
*
|
||||
* Resolves a {@link Link} for a given model object
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
* @author Vivek Pandey
|
||||
*/
|
||||
public abstract class LinkResolver {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param modelObject
|
||||
* a model object to map to corresponding BlueOcean API model object
|
||||
*
|
||||
* @return
|
||||
* {@link Link} to BlueOcean API model object. null if there is no BlueOcean API model object found that maps
|
||||
* to the given model object.
|
||||
*/
|
||||
public abstract Link resolve(Object modelObject);
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
package io.jenkins.blueocean.rest.model;
|
||||
|
||||
import hudson.ExtensionList;
|
||||
import hudson.ExtensionPoint;
|
||||
import hudson.model.Item;
|
||||
import io.jenkins.blueocean.rest.Reachable;
|
||||
|
||||
/**
|
||||
* Factory that gives instance of {@link BluePipeline}
|
||||
*
|
||||
* It's useful for example in cases where a plugin that has custom project and they want to serve
|
||||
* extra meta-data thru BluePipeline, would provide implementation of their BluePipeline and and implementation
|
||||
* of BluePipelineFactory.
|
||||
*
|
||||
* @author Vivek Pandey
|
||||
*/
|
||||
public abstract class BluePipelineFactory implements ExtensionPoint {
|
||||
public abstract BluePipeline getPipeline(Item item, Reachable parent);
|
||||
|
||||
public static ExtensionList<BluePipelineFactory> all(){
|
||||
return ExtensionList.lookup(BluePipelineFactory.class);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue