From b8c038abac92322d4801d773c94e314de392e8f1 Mon Sep 17 00:00:00 2001 From: vivek Date: Wed, 27 Jul 2016 07:47:39 -0700 Subject: [PATCH 1/5] JENKINS-36967# Favorite object self link fix (#377) --- .../pipeline/MultiBranchPipelineImpl.java | 14 +-- .../rest/impl/pipeline/MultiBranchTest.java | 75 +++++++++-- .../rest/impl/pipeline/PipelineBaseTest.java | 7 ++ .../service/embedded/util/FavoriteUtil.java | 28 +---- .../blueocean/service/embedded/BaseTest.java | 57 ++------- .../service/embedded/ProfileApiTest.java | 116 +++++++++++++++++- blueocean-rest/README.md | 25 +++- 7 files changed, 230 insertions(+), 92 deletions(-) diff --git a/blueocean-pipeline-api-impl/src/main/java/io/jenkins/blueocean/rest/impl/pipeline/MultiBranchPipelineImpl.java b/blueocean-pipeline-api-impl/src/main/java/io/jenkins/blueocean/rest/impl/pipeline/MultiBranchPipelineImpl.java index d40d0e85..6647121a 100644 --- a/blueocean-pipeline-api-impl/src/main/java/io/jenkins/blueocean/rest/impl/pipeline/MultiBranchPipelineImpl.java +++ b/blueocean-pipeline-api-impl/src/main/java/io/jenkins/blueocean/rest/impl/pipeline/MultiBranchPipelineImpl.java @@ -9,6 +9,7 @@ import io.jenkins.blueocean.commons.ServiceException; import io.jenkins.blueocean.rest.Navigable; import io.jenkins.blueocean.rest.Reachable; import io.jenkins.blueocean.rest.hal.Link; +import io.jenkins.blueocean.rest.hal.LinkResolver; import io.jenkins.blueocean.rest.model.BlueActionProxy; import io.jenkins.blueocean.rest.model.BlueFavorite; import io.jenkins.blueocean.rest.model.BlueFavoriteAction; @@ -69,12 +70,8 @@ public class MultiBranchPipelineImpl extends BlueMultiBranchPipeline { } FavoriteUtil.favoriteJob(mbp.getFullName(), favoriteAction.isFavorite()); - return FavoriteUtil.getFavorite(mbp, new Reachable() { - @Override - public Link getLink() { - return getLink().rel("branches"); - } - }); + + return new FavoriteImpl(new BranchImpl(job,getLink().rel("branches")), getLink().rel("favorite")); } @Override @@ -351,7 +348,10 @@ public class MultiBranchPipelineImpl extends BlueMultiBranchPipeline { Job job = project.getItem("master"); if(job != null){ Resource resource = BluePipelineFactory.resolve(job); - return new FavoriteImpl(resource, FavoriteUtil.getFavoriteLink(item.getFullName())); + Link l = LinkResolver.resolveLink(project); + if(l != null) { + return new FavoriteImpl(resource, l.rel("favorite")); + } } } return null; diff --git a/blueocean-pipeline-api-impl/src/test/java/io/jenkins/blueocean/rest/impl/pipeline/MultiBranchTest.java b/blueocean-pipeline-api-impl/src/test/java/io/jenkins/blueocean/rest/impl/pipeline/MultiBranchTest.java index 4e39e0ac..a60d1ae2 100644 --- a/blueocean-pipeline-api-impl/src/test/java/io/jenkins/blueocean/rest/impl/pipeline/MultiBranchTest.java +++ b/blueocean-pipeline-api-impl/src/test/java/io/jenkins/blueocean/rest/impl/pipeline/MultiBranchTest.java @@ -446,7 +446,7 @@ public class MultiBranchTest extends PipelineBaseTest { .auth("alice","alice") .build(List.class); - Assert.assertEquals(l.size(), 1); + Assert.assertEquals(1,l.size()); branch = (Map)((Map)l.get(0)).get("item"); validatePipeline(p, branch); @@ -454,6 +454,30 @@ public class MultiBranchTest extends PipelineBaseTest { c = (String) branch.get("_class"); Assert.assertEquals(BranchImpl.class.getName(), c); + Assert.assertEquals("/blue/rest/organizations/jenkins/pipelines/p/favorite/", getHrefFromLinks((Map)l.get(0), "self")); + + String ref = getHrefFromLinks((Map)l.get(0), "self"); + + m = new RequestBuilder(baseUrl) + .put(getUrlFromHref(ref)) + .auth("alice", "alice") + .data(ImmutableMap.of("favorite", false)) + .build(Map.class); + + branch = (Map) m.get("item"); + validatePipeline(p, branch); + c = (String) branch.get("_class"); + Assert.assertEquals(BranchImpl.class.getName(), c); + + + l = new RequestBuilder(baseUrl) + .get("/users/"+user.getId()+"/favorites/") + .auth("alice","alice") + .build(List.class); + + Assert.assertEquals(0,l.size()); + + new RequestBuilder(baseUrl) .get("/users/"+user.getId()+"/favorites/") .auth("bob","bob") @@ -488,20 +512,44 @@ public class MultiBranchTest extends PipelineBaseTest { validatePipeline(p1, (Map) map.get("item")); + Assert.assertEquals("/blue/rest/organizations/jenkins/pipelines/p/branches/feature2/favorite/", getHrefFromLinks(map, "self")); + List l = new RequestBuilder(baseUrl) .get("/users/"+user.getId()+"/favorites/") .auth("alice","alice") .build(List.class); - Assert.assertEquals(l.size(), 1); + Assert.assertEquals(1, l.size()); Map branch = (Map)((Map)l.get(0)).get("item"); + Assert.assertEquals("/blue/rest/organizations/jenkins/pipelines/p/branches/feature2/favorite/", getHrefFromLinks((Map)l.get(0), "self")); + validatePipeline(p1, branch); String c = (String) branch.get("_class"); Assert.assertEquals(BranchImpl.class.getName(), c); + + map = new RequestBuilder(baseUrl) + .put(getUrlFromHref(getHrefFromLinks((Map)l.get(0), "self"))) + .auth("alice", "alice") + .data(ImmutableMap.of("favorite", false)) + .build(Map.class); + + + validatePipeline(p1, (Map) map.get("item")); + + Assert.assertEquals("/blue/rest/organizations/jenkins/pipelines/p/branches/feature2/favorite/", getHrefFromLinks(map, "self")); + + l = new RequestBuilder(baseUrl) + .get("/users/"+user.getId()+"/favorites/") + .auth("alice","alice") + .build(List.class); + + Assert.assertEquals(0, l.size()); + + new RequestBuilder(baseUrl) .get("/users/"+user.getId()+"/favorites/") .auth("bob","bob") @@ -548,15 +596,28 @@ public class MultiBranchTest extends PipelineBaseTest { String c = (String) branch.get("_class"); Assert.assertEquals(BranchImpl.class.getName(), c); - String ref = getHrefFromLinks((Map)l.get(0), "self"); + String href = getHrefFromLinks((Map)l.get(0), "self"); + Assert.assertEquals("/blue/rest/organizations/jenkins/pipelines/p/favorite/", href); - Map r = new RequestBuilder(baseUrl) - .get(ref.substring("/blue/rest".length())) - .auth("alice","alice") + + + Map m = new RequestBuilder(baseUrl) + .put(getUrlFromHref(getUrlFromHref(href))) + .auth("alice", "alice") + .data(ImmutableMap.of("favorite", false)) .build(Map.class); - validatePipeline(p, (Map)r.get("item")); + branch = (Map) m.get("item"); + validatePipeline(p, branch); + c = (String) branch.get("_class"); + Assert.assertEquals(BranchImpl.class.getName(), c); + l = new RequestBuilder(baseUrl) + .get("/users/"+user.getId()+"/favorites/") + .auth("alice","alice") + .build(List.class); + + Assert.assertEquals(0,l.size()); } @Test diff --git a/blueocean-pipeline-api-impl/src/test/java/io/jenkins/blueocean/rest/impl/pipeline/PipelineBaseTest.java b/blueocean-pipeline-api-impl/src/test/java/io/jenkins/blueocean/rest/impl/pipeline/PipelineBaseTest.java index 738cdeb2..55c820ca 100644 --- a/blueocean-pipeline-api-impl/src/test/java/io/jenkins/blueocean/rest/impl/pipeline/PipelineBaseTest.java +++ b/blueocean-pipeline-api-impl/src/test/java/io/jenkins/blueocean/rest/impl/pipeline/PipelineBaseTest.java @@ -332,6 +332,13 @@ public abstract class PipelineBaseTest{ return (String) l.get("href"); } + protected String getUrlFromHref(String href){ + if(href.startsWith("/blue/rest")){ + return href.substring("/blue/rest".length()); + } + return href; + } + protected List getParallelNodes(FlowGraphTable nodeGraphTable){ List parallelNodes = new ArrayList<>(); diff --git a/blueocean-rest-impl/src/main/java/io/jenkins/blueocean/service/embedded/util/FavoriteUtil.java b/blueocean-rest-impl/src/main/java/io/jenkins/blueocean/service/embedded/util/FavoriteUtil.java index 6d9b6b01..8b27acc8 100644 --- a/blueocean-rest-impl/src/main/java/io/jenkins/blueocean/service/embedded/util/FavoriteUtil.java +++ b/blueocean-rest-impl/src/main/java/io/jenkins/blueocean/service/embedded/util/FavoriteUtil.java @@ -1,9 +1,6 @@ package io.jenkins.blueocean.service.embedded.util; -import hudson.Util; import hudson.model.Item; -import hudson.model.ItemGroup; -import hudson.model.Job; import hudson.model.User; import hudson.plugins.favorite.FavoritePlugin; import hudson.plugins.favorite.user.FavoriteUserProperty; @@ -16,7 +13,6 @@ import io.jenkins.blueocean.rest.model.BluePipeline; import io.jenkins.blueocean.service.embedded.rest.BlueFavoriteResolver; import io.jenkins.blueocean.service.embedded.rest.BluePipelineFactory; import io.jenkins.blueocean.service.embedded.rest.FavoriteImpl; -import io.jenkins.blueocean.service.embedded.rest.UserImpl; import jenkins.model.Jenkins; import org.kohsuke.stapler.Stapler; @@ -53,22 +49,6 @@ public class FavoriteUtil { } } - public static Link getFavoriteLink(String fullName){ - User user = User.current(); - if(user != null) { - return new UserImpl(user).getLink().rel("favorites/"+ FavoriteUtil.encodeFullName(fullName)); - } - return null; - } - - public static boolean isFavorableItem(Item i){ - return i!= null && (i instanceof Job || i instanceof ItemGroup); - } - - public static String encodeFullName(String name){ - return Util.rawEncode(Util.rawEncode(name)); - } - public static String decodeFullName(String name){ try { return URLDecoder.decode(URLDecoder.decode(name, "UTF-8"), "UTF-8"); @@ -119,15 +99,9 @@ public class FavoriteUtil { } } - //otherwise, default - Link favouriteLink = getFavoriteLink(item.getFullName()); - if(favouriteLink == null){ - return null; - } - BluePipeline pipeline = BluePipelineFactory.getPipelineInstance(item, parent); if(pipeline != null){ - return new FavoriteImpl(pipeline,favouriteLink); + return new FavoriteImpl(pipeline,pipeline.getLink().rel("favorite")); } return null; diff --git a/blueocean-rest-impl/src/test/java/io/jenkins/blueocean/service/embedded/BaseTest.java b/blueocean-rest-impl/src/test/java/io/jenkins/blueocean/service/embedded/BaseTest.java index d05f2f63..1a3916de 100644 --- a/blueocean-rest-impl/src/test/java/io/jenkins/blueocean/service/embedded/BaseTest.java +++ b/blueocean-rest-impl/src/test/java/io/jenkins/blueocean/service/embedded/BaseTest.java @@ -15,6 +15,7 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.MockFolder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -227,24 +228,6 @@ public abstract class BaseTest { throw new RuntimeException(e); } } -// -// protected void validateMultiBranchPipeline(WorkflowMultiBranchProject p, Map resp, int numBranches){ -// validateMultiBranchPipeline(p, resp, numBranches, -1, -1); -// } -// protected void validateMultiBranchPipeline(WorkflowMultiBranchProject p, Map resp, int numBranches, int numSuccBranches, int numOfFailingBranches){ -// Assert.assertEquals("jenkins", resp.get("organization")); -// Assert.assertEquals(p.getName(), resp.get("name")); -// Assert.assertEquals(p.getDisplayName(), resp.get("displayName")); -// Assert.assertNull(resp.get("lastSuccessfulRun")); -// Assert.assertEquals(numBranches, resp.get("totalNumberOfBranches")); -// if(numOfFailingBranches >= 0) { -// Assert.assertEquals(numOfFailingBranches, resp.get("numberOfFailingBranches")); -// } -// if(numSuccBranches >= 0) { -// Assert.assertEquals(numSuccBranches, resp.get("numberOfSuccessfulBranches")); -// } -// Assert.assertEquals(p.getBuildHealth().getScore(), resp.get("weatherScore")); -// } protected void validatePipeline(Job p, Map resp){ Assert.assertEquals("jenkins", resp.get("organization")); @@ -271,6 +254,17 @@ public abstract class BaseTest { } } + + protected void validateFolder(MockFolder folder, Map resp){ + Assert.assertEquals("jenkins", resp.get("organization")); + Assert.assertEquals(folder.getName(), resp.get("name")); + Assert.assertEquals(folder.getDisplayName(), resp.get("displayName")); + Assert.assertEquals(folder.getFullName(), resp.get("fullName")); + Assert.assertNull(resp.get("lastSuccessfulRun")); + Assert.assertEquals(folder.getAllJobs().size(), resp.get("numberOfPipelines")); + Assert.assertEquals(folder.getAllJobs().size(), resp.get("numberOfPipelines")); + } + protected void validateRun(Run r, Map resp){ validateRun(r,resp, "FINISHED"); } @@ -285,39 +279,12 @@ public abstract class BaseTest { Assert.assertEquals(state, resp.get("state")); } -// protected String getNodeName(FlowNode n){ -// return n.getAction(ThreadNameAction.class) != null -// ? n.getAction(ThreadNameAction.class).getThreadName() -// : n.getDisplayName(); -// } private String getBaseUrl(String path){ return baseUrl + path; } -// protected List getStages(FlowGraphTable nodeGraphTable){ -// List 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 getParallelNodes(FlowGraphTable nodeGraphTable){ -// List parallelNodes = new ArrayList<>(); -// -// for(FlowGraphTable.Row row: nodeGraphTable.getRows()){ -// if(PipelineNodeUtil.isParallelBranch(row.getNode())){ -// parallelNodes.add(row.getNode()); -// } -// } -// return parallelNodes; -// } - protected String getHrefFromLinks(Map resp, String link){ Map links = (Map) resp.get("_links"); if(links == null){ diff --git a/blueocean-rest-impl/src/test/java/io/jenkins/blueocean/service/embedded/ProfileApiTest.java b/blueocean-rest-impl/src/test/java/io/jenkins/blueocean/service/embedded/ProfileApiTest.java index 9b787dc2..eeea0855 100644 --- a/blueocean-rest-impl/src/test/java/io/jenkins/blueocean/service/embedded/ProfileApiTest.java +++ b/blueocean-rest-impl/src/test/java/io/jenkins/blueocean/service/embedded/ProfileApiTest.java @@ -2,11 +2,13 @@ package io.jenkins.blueocean.service.embedded; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import hudson.model.FreeStyleProject; import hudson.model.Project; import hudson.model.User; import hudson.tasks.Mailer; import org.junit.Assert; import org.junit.Test; +import org.jvnet.hudson.test.MockFolder; import java.util.Collections; import java.util.List; @@ -90,8 +92,10 @@ public class ProfileApiTest extends BaseTest{ j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); hudson.model.User user = j.jenkins.getUser("alice"); user.setFullName("Alice Cooper"); + Project p = j.createFreeStyleProject("pipeline1"); + Map map = new RequestBuilder(baseUrl) .put("/organizations/jenkins/pipelines/pipeline1/favorite") .auth("alice", "alice") @@ -104,11 +108,121 @@ public class ProfileApiTest extends BaseTest{ .auth("alice","alice") .build(List.class); - Assert.assertEquals(l.size(), 1); + Assert.assertEquals(1, l.size()); Map pipeline = (Map)((Map)l.get(0)).get("item"); validatePipeline(p, pipeline); + String href = getHrefFromLinks((Map)l.get(0),"self"); + Assert.assertEquals("/blue/rest/organizations/jenkins/pipelines/pipeline1/favorite/", href); + map = new RequestBuilder(baseUrl) + .put(href.substring("/blue/rest".length())) + .auth("alice", "alice") + .data(ImmutableMap.of("favorite", false)) + .build(Map.class); + + validatePipeline(p, (Map) map.get("item")); + + l = new RequestBuilder(baseUrl) + .get("/users/"+user.getId()+"/favorites/") + .auth("alice","alice") + .build(List.class); + + Assert.assertEquals(0, l.size()); + + + new RequestBuilder(baseUrl) + .get("/users/"+user.getId()+"/favorites/") + .auth("bob","bob") + .status(403) + .build(String.class); + + } + + @Test + public void createUserFavouriteFolderTest() throws Exception { + j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); + hudson.model.User user = j.jenkins.getUser("alice"); + user.setFullName("Alice Cooper"); + + MockFolder folder1 = j.createFolder("folder1"); + Project p = folder1.createProject(FreeStyleProject.class, "pipeline1"); + + Map map = new RequestBuilder(baseUrl) + .put("/organizations/jenkins/pipelines/folder1/pipelines/pipeline1/favorite/") + .auth("alice", "alice") + .data(ImmutableMap.of("favorite", true)) + .build(Map.class); + + validatePipeline(p, (Map) map.get("item")); + List l = new RequestBuilder(baseUrl) + .get("/users/"+user.getId()+"/favorites/") + .auth("alice","alice") + .build(List.class); + + Assert.assertEquals(1, l.size()); + Map pipeline = (Map)((Map)l.get(0)).get("item"); + + validatePipeline(p, pipeline); + + String href = getHrefFromLinks((Map)l.get(0),"self"); + + Assert.assertEquals("/blue/rest/organizations/jenkins/pipelines/folder1/pipelines/pipeline1/favorite/", href); + + map = new RequestBuilder(baseUrl) + .put(href.substring("/blue/rest".length())) + .auth("alice", "alice") + .data(ImmutableMap.of("favorite", false)) + .build(Map.class); + + validatePipeline(p, (Map) map.get("item")); + + l = new RequestBuilder(baseUrl) + .get("/users/"+user.getId()+"/favorites/") + .auth("alice","alice") + .build(List.class); + + Assert.assertEquals(0, l.size()); + + + map = new RequestBuilder(baseUrl) + .put("/organizations/jenkins/pipelines/folder1/favorite/") + .auth("alice", "alice") + .data(ImmutableMap.of("favorite", true)) + .build(Map.class); + + validateFolder(folder1, (Map) map.get("item")); + l = new RequestBuilder(baseUrl) + .get("/users/"+user.getId()+"/favorites/") + .auth("alice","alice") + .build(List.class); + + Assert.assertEquals(1, l.size()); + Map folder = (Map)((Map)l.get(0)).get("item"); + + validateFolder(folder1, folder); + + href = getHrefFromLinks((Map)l.get(0),"self"); + + Assert.assertEquals("/blue/rest/organizations/jenkins/pipelines/folder1/favorite/", href); + + map = new RequestBuilder(baseUrl) + .put(href.substring("/blue/rest".length())) + .auth("alice", "alice") + .data(ImmutableMap.of("favorite", false)) + .build(Map.class); + + validateFolder(folder1, (Map) map.get("item")); + + l = new RequestBuilder(baseUrl) + .get("/users/"+user.getId()+"/favorites/") + .auth("alice","alice") + .build(List.class); + + Assert.assertEquals(0, l.size()); + + + new RequestBuilder(baseUrl) .get("/users/"+user.getId()+"/favorites/") .auth("bob","bob") diff --git a/blueocean-rest/README.md b/blueocean-rest/README.md index 88a8cc62..d5404c17 100644 --- a/blueocean-rest/README.md +++ b/blueocean-rest/README.md @@ -56,7 +56,9 @@ - [Favorite API](#favorite-api) - [Favorite a pipeline](#favorite-a-pipeline) - [Favorite a multi branch pipeline](#favorite-a-multi-branch-pipeline) + - [Un-favorite a multi branch pipeline](#un-favorite-a-multi-branch-pipeline) - [Favorite a multi branch pipeline branch](#favorite-a-multi-branch-pipeline-branch) + - [Un-favorite a multi branch pipeline branch](#un-favorite-a-multi-branch-pipeline-branch) - [Fetch user favorites](#fetch-user-favorites) - [Log API](#log-api) - [Fetching logs](#fetching-logs) @@ -1279,7 +1281,7 @@ If favorite request is successful then the repsonse is favorited item. "_links" : { "self" : { "_class" : "io.jenkins.blueocean.rest.hal.Link", - "href" : "/blue/rest/users/alice/favorites/pipeline1/" + "href" : "/blue/rest/organizations/jenkins/pipelines/pipeline1/favorite/" } }, "item" : { @@ -1319,19 +1321,32 @@ If favorite request is successful then the repsonse is favorited item. ## Favorite a pipeline Returns 200 on success. Must be authenticated. - curl -u bob:bob -H"Content-Type:application/json" -XPUT -d '{"favorite":true} ttp://localhost:56748/jenkins/blue/rest/organizations/jenkins/pipelines/pipeline1/favorite + curl -u bob:bob -H"Content-Type:application/json" -XPUT -d '{"favorite":true} ttp://localhost:56748/jenkins/blue/rest/organizations/jenkins/pipelines/pipeline1/favorite/ ## Favorite a multi branch pipeline Must be authenticated. -This favorites the master branch. Returns 200 on success. 500 if master does not exist +Favorited multi-branch pipeline returns master branch as favorited item. Returns 200 on success. 400 if master does not exist + + curl -u bob:bob -H"Content-Type:application/json" -XPUT -d '{"favorite":true} http://localhost:56748/jenkins/blue/rest/organizations/jenkins/pipelines/pipeline1/favorite/ + +## Un-favorite a multi branch pipeline +Must be authenticated. + +This un-favorites the master branch. Returns 200 on success. 400 if master does not exist + + curl -u bob:bob -H"Content-Type:application/json" -XPUT -d '{"favorite":false} http://localhost:56748/jenkins/blue/rest/organizations/jenkins/pipelines/pipeline1/favorite/ - curl -u bob:bob -H"Content-Type:application/json" -XPUT -d '{"favorite":true} http://localhost:56748/jenkins/blue/rest/organizations/jenkins/pipelines/pipeline1/favorite ## Favorite a multi branch pipeline branch Returns 200 on success. Must be authenticated. - curl -H"Content-Type:application/json" -XPUT -d '{"favorite":true} http://localhost:56748/jenkins/blue/rest/organizations/jenkins/pipelines/pipeline1/branches/master/favorite + curl -H"Content-Type:application/json" -XPUT -d '{"favorite":true} http://localhost:56748/jenkins/blue/rest/organizations/jenkins/pipelines/pipeline1/branches/master/favorite/ + +## Un-favorite a multi branch pipeline branch +Returns 200 on success. Must be authenticated. + + curl -H"Content-Type:application/json" -XPUT -d '{"favorite":false} http://localhost:56748/jenkins/blue/rest/organizations/jenkins/pipelines/pipeline1/branches/master/favorite/ ## Fetch user favorites From 91c4188539fc0c1f030b063afed9d6512a4bbc52 Mon Sep 17 00:00:00 2001 From: Keith Zantow Date: Wed, 27 Jul 2016 11:59:16 -0400 Subject: [PATCH 2/5] [FIXED JENKINS-36336] Always show fixed tests if there are some (#373) * JENKINS-36336 Always show fixed tests if there are some * JENKINS-36930 Test results display not handling REGRESSION case --- .../js/components/testing/TestResults.jsx | 25 +++++------ .../src/test/js/testResult-spec.js | 41 ++++++++++++++++++- 2 files changed, 52 insertions(+), 14 deletions(-) diff --git a/blueocean-dashboard/src/main/js/components/testing/TestResults.jsx b/blueocean-dashboard/src/main/js/components/testing/TestResults.jsx index 8475c8d4..3c5c26ca 100644 --- a/blueocean-dashboard/src/main/js/components/testing/TestResults.jsx +++ b/blueocean-dashboard/src/main/js/components/testing/TestResults.jsx @@ -32,6 +32,7 @@ const TestCaseResultRow = (props) => { let statusIndicator = null; switch (t.status) { + case 'REGRESSION': case 'FAILED': statusIndicator = StatusIndicator.validResultValues.failure; break; @@ -67,12 +68,11 @@ export default class TestResult extends Component { const suites = this.props.testResults.suites; const tests = [].concat.apply([], suites.map(t => t.cases)); - // possible statuses: PASSED, FAILED, SKIPPED - const failures = tests.filter(t => t.status === 'FAILED'); + // one of 5 possible statuses: PASSED, FIXED, SKIPPED, FAILED, REGRESSION see: hudson.tasks.junit.CaseResult$Status :( const fixed = tests.filter(t => t.status === 'FIXED'); const skipped = tests.filter(t => t.status === 'SKIPPED'); - const newFailures = failures.filter(t => t.age === 1); - const existingFailures = failures.filter(t => t.age > 1); + const newFailures = tests.filter(t => (t.age <= 1 && t.status === 'FAILED') || t.status === 'REGRESSION'); + const existingFailures = tests.filter(t => t.age > 1 && t.status === 'FAILED'); let passBlock = null; let newFailureBlock = null; @@ -129,13 +129,6 @@ export default class TestResult extends Component { ); } - if (fixed.length > 0) { - fixedBlock = (
-

Fixed

- {fixed.map((t, i) => )} -
); - } - if (skipped.length > 0) { skippedBlock = (

Skipped - {skipped.length}

@@ -144,14 +137,22 @@ export default class TestResult extends Component { } } + // always show fixed, whether showing totals or the encouraging message + if (fixed.length > 0) { + fixedBlock = (
+

Fixed

+ {fixed.map((t, i) => )} +
); + } + return (
+ {passBlock} {summaryBlock} {newFailureBlock} {existingFailureBlock} {fixedBlock} {skippedBlock} - {passBlock}
); } diff --git a/blueocean-dashboard/src/test/js/testResult-spec.js b/blueocean-dashboard/src/test/js/testResult-spec.js index 5daf13b4..14560bf1 100644 --- a/blueocean-dashboard/src/test/js/testResult-spec.js +++ b/blueocean-dashboard/src/test/js/testResult-spec.js @@ -62,6 +62,25 @@ describe("TestResults", () => { assert.equal(newFailed, 1); }); + it("Handles REGRESSION case", () => { + var failures = { + "_class":"hudson.tasks.junit.TestResult", + "duration":0.008, "empty":false, "failCount":3, "passCount":0, "skipCount":0, "suites":[ + { "duration":0, "id":null, "name":"failure.TestThisWontFail", "stderr":null, "stdout":null, "timestamp":null, "cases": [ + {"age":5,"className":"failure.TestThisWontFail","duration":0,"errorDetails":null,"errorStackTrace":null,"failedSince":0,"name":"aPassingTest2","skipped":false,"skippedMessage":null,"status":"FAILED","stderr":null,"stdout":null}, + {"age":2,"className":"failure.TestThisWontFail","duration":0,"errorDetails":null,"errorStackTrace":null,"failedSince":0,"name":"aPassingTest3","skipped":false,"skippedMessage":null,"status":"REGRESSION","stderr":null,"stdout":null}, + {"age":1,"className":"failure.TestThisWontFail","duration":0,"errorDetails":null,"errorStackTrace":null,"failedSince":0,"name":"aPassingTest4","skipped":false,"skippedMessage":null,"status":"FAILED","stderr":null,"stdout":null}, + ], + }]}; + + let wrapper = shallow(); + const newFailed = wrapper.find('.new-failure-block h4').text(); + assert.equal(newFailed, 'New failing - 2'); + + const failed = wrapper.find('.existing-failure-block h4').text(); + assert.equal(failed, 'Existing failures - 1'); + }); + it("All passing shown", () => { let wrapper = shallow(); let isDone = wrapper.html().indexOf('done_all') > 0; @@ -78,7 +97,25 @@ describe("TestResults", () => { }]}; wrapper = shallow(); - isDone = wrapper.html().indexOf('done_all') > 0; - assert(isDone, "Done all not found, when should be"); + let html = wrapper.html(); + assert(html.indexOf('done_all') > 0, "Done all not found, when should be"); + assert(html.indexOf('fixed-block') < 0, "No fixed tests!"); + }); + + it("All passing and fixed shown", () => { + var successWithFixed = { + "_class":"hudson.tasks.junit.TestResult", + "duration":0.008, "empty":false, "failCount":0, "passCount":3, "skipCount":0, "suites":[ + { "duration":0, "id":null, "name":"failure.TestThisWontFail", "stderr":null, "stdout":null, "timestamp":null, "cases": [ + {"age":0,"className":"failure.TestThisWontFail","duration":0,"errorDetails":null,"errorStackTrace":null,"failedSince":0,"name":"aPassingTest2","skipped":false,"skippedMessage":null,"status":"FIXED","stderr":null,"stdout":null}, + {"age":0,"className":"failure.TestThisWontFail","duration":0,"errorDetails":null,"errorStackTrace":null,"failedSince":0,"name":"aPassingTest3","skipped":false,"skippedMessage":null,"status":"PASSED","stderr":null,"stdout":null}, + {"age":0,"className":"failure.TestThisWontFail","duration":0,"errorDetails":null,"errorStackTrace":null,"failedSince":0,"name":"aPassingTest4","skipped":false,"skippedMessage":null,"status":"PASSED","stderr":null,"stdout":null}, + ], + }]}; + + let wrapper = shallow(); + let html = wrapper.html(); + assert(html.indexOf('done_all') > 0, "Done all not found, when should be"); + assert(html.indexOf('fixed-block') > 0, "Should have fixed tests!"); }); }); From 4b96cb3af63064036aedc475c5c8540142d010ce Mon Sep 17 00:00:00 2001 From: Thorsten Scherler Date: Wed, 27 Jul 2016 20:10:29 +0200 Subject: [PATCH 3/5] [JENKINS-36169] Override resultItem css to remove border and padding (#371) * [JENKINS-36169] Override resultItem css to remove border and padding * [JENKINS-36169] remove override since it is not needed anymore * [JENKINS-36169] tick version so changes from jdl are visible * [JENKINS-36169] pump version * [JENKINS-36169] The border is needed in the test view * [JENKINS-36169] using now published version --- blueocean-dashboard/package.json | 2 +- .../src/main/js/components/Step.jsx | 8 ++++++-- blueocean-dashboard/src/main/less/core.less | 14 ++++++++++---- blueocean-dashboard/src/main/less/variables.less | 2 ++ blueocean-web/package.json | 2 +- 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/blueocean-dashboard/package.json b/blueocean-dashboard/package.json index 4838c70f..5e493d59 100644 --- a/blueocean-dashboard/package.json +++ b/blueocean-dashboard/package.json @@ -35,7 +35,7 @@ "skin-deep": "^0.16.0" }, "dependencies": { - "@jenkins-cd/design-language": "0.0.64", + "@jenkins-cd/design-language": "0.0.65", "@jenkins-cd/js-extensions": "0.0.19", "@jenkins-cd/js-modules": "0.0.5", "@jenkins-cd/sse-gateway": "0.0.6", diff --git a/blueocean-dashboard/src/main/js/components/Step.jsx b/blueocean-dashboard/src/main/js/components/Step.jsx index 22869df3..764909c6 100644 --- a/blueocean-dashboard/src/main/js/components/Step.jsx +++ b/blueocean-dashboard/src/main/js/components/Step.jsx @@ -108,7 +108,7 @@ export default class Node extends Component { resultRun.toLowerCase() === 'failure' || (resultRun.toLowerCase() === 'running' && followAlong) ; - return (
+ return (
}   + /> } + + { !log && +   + }
); } diff --git a/blueocean-dashboard/src/main/less/core.less b/blueocean-dashboard/src/main/less/core.less index 2cbea83d..f12758a7 100644 --- a/blueocean-dashboard/src/main/less/core.less +++ b/blueocean-dashboard/src/main/less/core.less @@ -10,10 +10,10 @@ height: 100%; box-sizing: border-box; z-index: 1000; - + &.not-found { background: @blueocean-blue; - + &:before { content: ''; background: url('./icons/kanagawa.svg') no-repeat bottom left; @@ -29,7 +29,7 @@ height: 100%; } } - + .message-box { position: absolute; top: 50%; @@ -40,7 +40,7 @@ padding: 2em; border-radius: .2em; box-shadow: 2px 2px 8px rgba(0, 0, 0, .1); - + .message { margin: 1em 0 2em 0; } @@ -158,3 +158,9 @@ display: flex; align-items: center; } + +.logConsole .result-item-children { + background-color: @pre-bg; + border: none; + padding: 0; +} diff --git a/blueocean-dashboard/src/main/less/variables.less b/blueocean-dashboard/src/main/less/variables.less index ba8357fc..2c1d9505 100644 --- a/blueocean-dashboard/src/main/less/variables.less +++ b/blueocean-dashboard/src/main/less/variables.less @@ -1,2 +1,4 @@ @blueocean-blue: #4A90E2; @blueocean-blue-darkened: darken(@blueocean-blue, 20%); +@gray-base: #000; +@pre-bg: lighten(@gray-base, 20%); // #333 diff --git a/blueocean-web/package.json b/blueocean-web/package.json index be0f2894..4e4cdf69 100644 --- a/blueocean-web/package.json +++ b/blueocean-web/package.json @@ -25,7 +25,7 @@ "zombie": "^4.2.1" }, "dependencies": { - "@jenkins-cd/design-language": "0.0.64", + "@jenkins-cd/design-language": "0.0.65", "@jenkins-cd/js-extensions": "0.0.19", "@jenkins-cd/js-modules": "0.0.5", "history": "2.0.2", From b8789358b1d19a499cd91b913075177a4a7cd41c Mon Sep 17 00:00:00 2001 From: Cliff Meyers Date: Wed, 27 Jul 2016 15:30:46 -0400 Subject: [PATCH 4/5] [JENKINS-35837] fix test regression --- .../src/test/js/components/PipelineCard-spec.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blueocean-personalization/src/test/js/components/PipelineCard-spec.jsx b/blueocean-personalization/src/test/js/components/PipelineCard-spec.jsx index 632e000a..e1a201c7 100644 --- a/blueocean-personalization/src/test/js/components/PipelineCard-spec.jsx +++ b/blueocean-personalization/src/test/js/components/PipelineCard-spec.jsx @@ -26,7 +26,7 @@ describe('PipelineCard', () => { assert.equal(wrapper.find('LiveStatusIndicator').length, 1); assert.equal(wrapper.find('.name').length, 1); - assert.equal(wrapper.find('.name').text(), 'Jenkins / blueocean'); + assert.equal(wrapper.find('.name').text(), ''); assert.equal(wrapper.find('.branch').length, 1); assert.equal(wrapper.find('.branchText').text(), 'feature/JENKINS-123'); assert.equal(wrapper.find('.commit').length, 1); From 3958b8aaa3bf99f86e75035264f3da5b6405910b Mon Sep 17 00:00:00 2001 From: Thorsten Scherler Date: Thu, 28 Jul 2016 13:01:40 +0200 Subject: [PATCH 5/5] Jenkins 36131 "Show all" link required anywhere a log is displayed (#378) * [JENKINS-36131] WIP testing with content length * [JENKINS-36131] WIP add parameter fetchAll and refetch the logs when present. Broken for me ATM unknown reason. will merge master now in here * [JENKINS-36131] WIP first working version with steps. * [JENKINS-36131] WIP first working version as well for freestyle, but I need to refactor the thing. using the hash is not optimal, will switch to the backend hook start=0 * [JENKINS-36131] Switch to trigger ?start=0 which enables us to link in full extended logs. Now writing AT for it * [JENKINS-36131] Make the link to the full log more visible * [JENKINS-36131] easier matching for AT * [JENKINS-36131] in follow along the Full Log button should not be shown, since you see everything already --- .../src/main/js/components/LogConsole.jsx | 18 ++++++- .../src/main/js/components/LogToolbar.jsx | 6 ++- .../main/js/components/RunDetailsPipeline.jsx | 39 +++++++++++--- .../src/main/js/components/Step.jsx | 51 +++++++++++++------ .../src/main/js/redux/actions.js | 13 +++++ .../src/main/js/util/UrlUtils.js | 32 +++++++++++- blueocean-dashboard/src/main/less/core.less | 20 ++++++++ .../src/main/less/variables.less | 2 + 8 files changed, 153 insertions(+), 28 deletions(-) diff --git a/blueocean-dashboard/src/main/js/components/LogConsole.jsx b/blueocean-dashboard/src/main/js/components/LogConsole.jsx index 6478edc7..32033379 100644 --- a/blueocean-dashboard/src/main/js/components/LogConsole.jsx +++ b/blueocean-dashboard/src/main/js/components/LogConsole.jsx @@ -108,7 +108,7 @@ export class LogConsole extends Component { render() { const lines = this.state.lines; - const { prefix = '' } = this.props; + const { prefix = '', hasMore = false } = this.props; // if hasMore true then show link to full log if (!lines) { return null; } @@ -116,8 +116,21 @@ export class LogConsole extends Component { return ( + { hasMore && } { lines.map((line, index) =>

- {line} + {line} +

)}
); } } @@ -129,6 +142,7 @@ LogConsole.propTypes = { scrollToAnchorTimeOut: func, scrollBottom: func, prefix: string, + hasMore: bool, }; export default scrollHelper(LogConsole); diff --git a/blueocean-dashboard/src/main/js/components/LogToolbar.jsx b/blueocean-dashboard/src/main/js/components/LogToolbar.jsx index ba244529..09329963 100644 --- a/blueocean-dashboard/src/main/js/components/LogToolbar.jsx +++ b/blueocean-dashboard/src/main/js/components/LogToolbar.jsx @@ -1,6 +1,7 @@ import React, { Component, PropTypes } from 'react'; import { Icon } from 'react-material-icons-blue'; +import { fetchAllSuffix as suffix } from '../util/UrlUtils'; const { string } = PropTypes; @@ -12,6 +13,7 @@ export default class LogToolbar extends Component { if (!url) { return null; } + const logUrl = url.includes(suffix) ? url : `${url}${suffix}`; const style = { fill: '#4a4a4a' }; return (
@@ -21,13 +23,13 @@ export default class LogToolbar extends Component { diff --git a/blueocean-dashboard/src/main/js/components/RunDetailsPipeline.jsx b/blueocean-dashboard/src/main/js/components/RunDetailsPipeline.jsx index b29b263b..d6315975 100644 --- a/blueocean-dashboard/src/main/js/components/RunDetailsPipeline.jsx +++ b/blueocean-dashboard/src/main/js/components/RunDetailsPipeline.jsx @@ -17,7 +17,7 @@ import { createSelector, } from '../redux'; -import { calculateStepsBaseUrl, calculateRunLogURLObject, calculateNodeBaseUrl } from '../util/UrlUtils'; +import { calculateStepsBaseUrl, calculateRunLogURLObject, calculateNodeBaseUrl, calculateFetchAll } from '../util/UrlUtils'; import { calculateNode } from '../util/KaraokeHelper'; @@ -45,7 +45,9 @@ export class RunDetailsPipeline extends Component { } else { // console.log('fetch the log directly') const logGeneral = calculateRunLogURLObject(this.mergedConfig); - fetchLog({ ...logGeneral }); + // fetchAll indicates whether we want all logs + const fetchAll = this.mergedConfig.fetchAll; + fetchLog({ ...logGeneral, fetchAll }); } // Listen for pipeline flow node events. @@ -127,10 +129,13 @@ export class RunDetailsPipeline extends Component { // if we have actions we fire them this.props[nodeAction.action](this.mergedConfig); } + const fetchAll = this.mergedConfig.fetchAll; + // console.log('this.mergedConfig.fetchAll', fetchAll) // if we only interested in logs (in case of e.g. freestyle) const { logs, fetchLog } = nextProps; - if (logs !== this.props.logs) { + if (logs !== this.props.logs || fetchAll) { const logGeneral = calculateRunLogURLObject(this.mergedConfig); + // console.log('logGenralReceive', logGeneral) const log = logs ? logs[logGeneral.url] : null; if (log && log !== null) { // we may have a streaming log @@ -144,20 +149,26 @@ export class RunDetailsPipeline extends Component { this.timeout = setTimeout(() => fetchLog({ ...logGeneral, newStart }), 1000); } } + } else if (fetchAll) { + // kill current timeout if any + clearTimeout(this.timeout); + // we need to get mpre input from the log stream + this.timeout = setTimeout(() => fetchLog({ ...logGeneral, fetchAll }), 1000); } } } + componentWillUnmount() { const domNode = ReactDOM.findDOMNode(this.refs.scrollArea); - domNode.removeEventListener('wheel', this._onScrollHandler); - document.removeEventListener('keydown', this._handleKeys); if (this.listener.sse) { sse.unsubscribe(this.listener.sse); delete this.listener.sse; } this.props.cleanNodePointer(); clearTimeout(this.timeout); + domNode.removeEventListener('wheel', this._onScrollHandler); + document.removeEventListener('keydown', this._handleKeys); } // need to register handler to step out of karaoke mode @@ -183,6 +194,7 @@ export class RunDetailsPipeline extends Component { isMultiBranch, params: { pipeline: name, branch, runId, node: nodeParam }, } = props; + const fetchAll = calculateFetchAll(props); // we would use default properties however the node can be null so no default properties will be triggered let { nodeReducer } = props; if (!nodeReducer) { @@ -191,7 +203,7 @@ export class RunDetailsPipeline extends Component { // if we have a node param we do not want the calculation of the focused node const node = nodeParam || nodeReducer.id; - const mergedConfig = { ...config, name, branch, runId, isMultiBranch, node, nodeReducer, followAlong }; + const mergedConfig = { ...config, name, branch, runId, isMultiBranch, node, nodeReducer, followAlong, fetchAll }; return mergedConfig; } @@ -267,6 +279,19 @@ export class RunDetailsPipeline extends Component { }; const noSteps = !log && currentSteps && currentSteps.model && currentSteps.model.length === 0; const shouldShowLogHeader = log !== null || !noSteps; + const logProps = { + scrollToBottom, + key: logGeneral.url, + }; + if (log) { + // in follow along the Full Log button should not be shown, since you see everything already + if (followAlong) { + logProps.hasMore = false; + } else { + logProps.hasMore = log.hasMore; + } + logProps.logArray = log.logArray; + } return (
{ nodes && nodes[nodeKey] && } - { log && } + { log && }
); } diff --git a/blueocean-dashboard/src/main/js/components/Step.jsx b/blueocean-dashboard/src/main/js/components/Step.jsx index 764909c6..c3852314 100644 --- a/blueocean-dashboard/src/main/js/components/Step.jsx +++ b/blueocean-dashboard/src/main/js/components/Step.jsx @@ -1,6 +1,6 @@ import React, { Component, PropTypes } from 'react'; import { ResultItem } from '@jenkins-cd/design-language'; -import { calculateLogUrl } from '../util/UrlUtils'; +import { calculateFetchAll, calculateLogUrl } from '../util/UrlUtils'; import LogConsole from './LogConsole'; @@ -18,7 +18,8 @@ export default class Node extends Component { const { config = {} } = this.context; const node = this.expandAnchor(this.props); if (node && node.isFocused) { - const mergedConfig = { ...config, node, nodesBaseUrl }; + const fetchAll = node.fetchAll; + const mergedConfig = { ...config, node, nodesBaseUrl, fetchAll }; fetchLog(mergedConfig); } } @@ -33,8 +34,9 @@ export default class Node extends Component { } const { config = {} } = this.context; const node = this.expandAnchor(nextProps); - const mergedConfig = { ...config, node, nodesBaseUrl }; - if (logs && logs !== this.props.logs) { + const fetchAll = node.fetchAll; + const mergedConfig = { ...config, node, nodesBaseUrl, fetchAll }; + if (logs && logs !== this.props.logs || fetchAll) { const key = calculateLogUrl(mergedConfig); const log = logs ? logs[key] : null; if (log && log !== null) { @@ -47,6 +49,9 @@ export default class Node extends Component { this.clearThisTimeout(); this.timeout = setTimeout(() => fetchLog({ ...mergedConfig }), 1000); } + } else if (!log && fetchAll) { // in case the link "full log" is clicked we need to trigger a refetch + this.clearThisTimeout(); + this.timeout = setTimeout(() => fetchLog({ ...mergedConfig }), 1000); } } } @@ -60,26 +65,33 @@ export default class Node extends Component { clearTimeout(this.timeout); } } - // Calculate whether we need to expand the step due to linking + /* + * Calculate whether we need to expand the step due to linking. + * When we trigger a log-0 that means we want to see the full log + */ expandAnchor(props) { const { node, location: { hash: anchorName } } = props; const isFocused = true; + const fetchAll = calculateFetchAll(props); + const general = { ...node, fetchAll }; // e.g. #step-10-log-1 or #step-10 if (anchorName) { const stepReg = /step-([0-9]{1,})?($|-log-([0-9]{1,})$)/; const match = stepReg.exec(anchorName); + if (match && match[1] && match[1] === node.id) { - return { ...node, isFocused }; + return { ...general, isFocused }; } } else if (this.state && this.state.isFocused) { - return { ...node, isFocused }; + return { ...general, isFocused }; } - return { ...node }; + return general; } render() { const { logs, nodesBaseUrl, fetchLog, followAlong } = this.props; const node = this.expandAnchor(this.props); + const fetchAll = node.fetchAll; // Early out if (!node || !fetchLog) { return null; @@ -95,7 +107,7 @@ export default class Node extends Component { } = node; const resultRun = result === 'UNKNOWN' || !result ? state : result; - const log = logs ? logs[calculateLogUrl({ ...config, node, nodesBaseUrl })] : null; + const log = logs ? logs[calculateLogUrl({ ...config, node, nodesBaseUrl, fetchAll })] : null; const getLogForNode = () => { // in case we do not have logs, or the logs are have no information attached we refetch them if (!log || !log.logArray) { @@ -108,6 +120,20 @@ export default class Node extends Component { resultRun.toLowerCase() === 'failure' || (resultRun.toLowerCase() === 'running' && followAlong) ; + const logProps = { + scrollToBottom, + key: id, + prefix: `step-${id}-`, + }; + if (log) { + // in follow along the Full Log button should not be shown, since you see everything already + if (followAlong) { + logProps.hasMore = false; + } else { + logProps.hasMore = log.hasMore; + } + logProps.logArray = log.logArray; + } return (
- { log && } + { log && } { !log &&   diff --git a/blueocean-dashboard/src/main/js/redux/actions.js b/blueocean-dashboard/src/main/js/redux/actions.js index a654e47a..771b997f 100644 --- a/blueocean-dashboard/src/main/js/redux/actions.js +++ b/blueocean-dashboard/src/main/js/redux/actions.js @@ -94,6 +94,7 @@ export const actionHandlers = { [ACTION_TYPES.SET_LOGS](state, { payload }): State { const logs = { ...state.logs } || {}; logs[payload.logUrl] = payload; + return state.set('logs', logs); }, @@ -768,6 +769,7 @@ export const actions = { const data = getState().adminStore.logs; const logUrl = calculateLogUrl(config); if ( + config.fetchAll || !data || !data[logUrl] || config.newStart > 0 || (data && data[logUrl] && data[logUrl].newStart > 0 || !data[logUrl].logArray) @@ -777,10 +779,21 @@ export const actions = { config.newStart || null, response => response.response.text() .then(text => { + // By default only last 150 KB log data is returned in the response. + const maxLength = 150000; + const contentLength = Number(response.response.headers.get('X-Text-Size')); + // set flag that there are more logs then we deliver + let hasMore = contentLength > maxLength; + // when we came from ?start=0, hasMore has to be false since there is no more + // console.log(config.fetchAll, 'inner') + if (config.fetchAll) { + hasMore = false; + } const { newStart } = response; const payload = { logUrl, newStart, + hasMore, }; if (text && !!text.trim()) { payload.logArray = text.trim().split('\n'); diff --git a/blueocean-dashboard/src/main/js/util/UrlUtils.js b/blueocean-dashboard/src/main/js/util/UrlUtils.js index 834ed234..ae2ff650 100644 --- a/blueocean-dashboard/src/main/js/util/UrlUtils.js +++ b/blueocean-dashboard/src/main/js/util/UrlUtils.js @@ -40,16 +40,43 @@ export const buildRunDetailsUrl = (organization, pipeline, branch, runId, tabNam */ export const uriString = (input) => encodeURIComponent(input).replace(/%2F/g, '%252F'); +// general fetchAllTrigger +export const fetchAllSuffix = '?start=0'; + +// Add fetchAllSuffix in case it is needed +export const applyFetchAll = function (config, url) { +// if we pass fetchAll means we want the full log -> start=0 will trigger that on the server + if (config.fetchAll && !url.includes(fetchAllSuffix)) { + return `${url}${fetchAllSuffix}`; + } + return url; +}; + +// using the hook 'location.search'.includes('start=0') to trigger fetchAll +export const calculateFetchAll = function (props) { + const { location: { search } } = props; + + if (search) { + const stepReg = /start=([0-9]{1,})/; + const match = stepReg.exec(search); + if (match && match[1] && Number(match[1]) === 0) { + return true; + } + } + return false; +}; + /* * helper to calculate log url. When we have a node we get create a special url, otherwise we use the url passed to us * @param config { nodesBaseUrl, node, url} */ export const calculateLogUrl = (config) => { + let returnUrl = config.url; if (config.node) { const { nodesBaseUrl, node } = config; - return `${nodesBaseUrl}/${node.id}/log/`; + returnUrl = `${nodesBaseUrl}/${node.id}/log/`; } - return config.url; + return applyFetchAll(config, returnUrl); }; /* @@ -105,6 +132,7 @@ export function calculateRunLogURLObject(config) { url = `${baseUrl}/runs/${runId}/log/`; fileName = `${runId}.txt`; } + url = applyFetchAll(config, url); return { url, fileName, diff --git a/blueocean-dashboard/src/main/less/core.less b/blueocean-dashboard/src/main/less/core.less index 527861f7..c435de36 100644 --- a/blueocean-dashboard/src/main/less/core.less +++ b/blueocean-dashboard/src/main/less/core.less @@ -170,3 +170,23 @@ border: none; padding: 0; } + +code div { + font-size: 12px; + margin: 5px; + display: flex; + align-items: center; + justify-content: center; +} + +code div a{ + border: solid 1px @pre-color; + padding: 3px 10px; + margin: 5px; + color: @pre-color; +} + +code div a:hover{ + background-color: @pre-color-hover; +} + diff --git a/blueocean-dashboard/src/main/less/variables.less b/blueocean-dashboard/src/main/less/variables.less index 2c1d9505..5bc2e25b 100644 --- a/blueocean-dashboard/src/main/less/variables.less +++ b/blueocean-dashboard/src/main/less/variables.less @@ -2,3 +2,5 @@ @blueocean-blue-darkened: darken(@blueocean-blue, 20%); @gray-base: #000; @pre-bg: lighten(@gray-base, 20%); // #333 +@pre-color: #f5f5f5; +@pre-color-hover: #444!important;