Skip to content

Commit

Permalink
#309 JobList API to support Tree and Depth
Browse files Browse the repository at this point in the history
  • Loading branch information
choweiyuan committed Aug 24, 2023
1 parent e2a3572 commit 97f9333
Show file tree
Hide file tree
Showing 8 changed files with 291 additions and 9 deletions.
1 change: 0 additions & 1 deletion src/main/java/com/cdancy/jenkins/rest/domain/job/Job.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ public abstract class Job {
public abstract String clazz();

public abstract String name();

public abstract String url();

@Nullable
Expand Down
58 changes: 58 additions & 0 deletions src/main/java/com/cdancy/jenkins/rest/domain/job/JobListTree.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.cdancy.jenkins.rest.domain.job;

import java.util.List;

import com.google.auto.value.AutoValue;
import org.jclouds.javax.annotation.Nullable;
import org.jclouds.json.SerializedNames;

@AutoValue
public abstract class JobListTree {

@Nullable
public abstract String clazz();

@Nullable
public abstract String name();

@Nullable
public abstract String fullName();

@Nullable
public abstract List<JobListTree> jobs();

@Nullable
public abstract String color();

@Nullable
public abstract String url();

@SerializedNames({"_class", "name", "fullName", "jobs", "color", "url"})
public static JobListTree create(
@Nullable String clazz,
@Nullable String name,
@Nullable String fullName,
@Nullable List<JobListTree> jobs,
@Nullable String color,
@Nullable String url
) {
return new AutoValue_JobListTree(clazz, name, fullName, jobs, color, url);
}
}
9 changes: 9 additions & 0 deletions src/main/java/com/cdancy/jenkins/rest/features/JobsApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,15 @@ public interface JobsApi {
@GET
JobList jobList(@PathParam("folderPath") @ParamParser(FolderPathParser.class) String folderPath);

@Named("jobs:get-jobs-tree")
@Path("{folderPath}api/json")
@Fallback(Fallbacks.NullOnNotFoundOr404.class)
@Consumes(MediaType.APPLICATION_JSON)
@GET
JobListTree jobList(@PathParam("folderPath") @ParamParser(FolderPathParser.class) String folderPath, @Nullable @QueryParam("depth") Integer depth,
@Nullable @QueryParam("tree") String tree);


@Named("jobs:job-info")
@Path("{optionalFolderPath}job/{name}/api/json")
@Fallback(Fallbacks.NullOnNotFoundOr404.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@
*/
package com.cdancy.jenkins.rest.features;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;

import com.cdancy.jenkins.rest.BaseJenkinsApiLiveTest;
import com.cdancy.jenkins.rest.domain.common.IntegerResponse;
Expand All @@ -28,8 +26,10 @@
import com.cdancy.jenkins.rest.domain.plugins.Plugins;
import com.cdancy.jenkins.rest.domain.queue.QueueItem;
import com.google.common.collect.Lists;
import org.testng.annotations.AfterClass;
import org.testng.annotations.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.testng.Assert.*;

@Test(groups = "live", testName = "JobsApiLiveTest", singleThreaded = true)
Expand All @@ -45,6 +45,11 @@ public class JobsApiLiveTest extends BaseJenkinsApiLiveTest {
private static final String PIPELINE_JOB_NAME = "PipelineSleep";
private static final String PIPELINE_WITH_ACTION_JOB_NAME = "PipelineAction";

@AfterClass
public void removeJobs() {
api().delete(null, "DevTest");
}

@Test
public void testCreateJob() {
String config = payloadFromResource("/freestyle-project-no-params.xml");
Expand Down Expand Up @@ -174,12 +179,18 @@ public void testKillPipelineBuild() throws InterruptedException {
assertTrue(success.value());
}

@Test(dependsOnMethods = {"testCreateJob", "testCreateJobForEmptyAndNullParams", "testKillPipelineBuild", "testKillFreeStyleBuild", "testDeleteFolders"})
public void testGetJobListFromRoot() {
@Test(dependsOnMethods = {"testBuildJob", "testCreateJobForEmptyAndNullParams", "testKillFreeStyleBuild", "testKillPipelineBuild", "testCreateFoldersInJenkins"})
public void testGetJobListFromRootWithFolders() {
JobList output = api().jobList("");
assertNotNull(output);
assertFalse(output.jobs().isEmpty());
assertEquals(output.jobs().size(), 2);
assertThat(output.jobs())
.isNotEmpty()
.hasSize(3)
.contains(
Job.create("hudson.model.FreeStyleProject", "DevTest", "http://127.0.0.1:8080/job/DevTest/", "blue"),
Job.create("hudson.model.FreeStyleProject", "JobForEmptyAndNullParams", "http://127.0.0.1:8080/job/JobForEmptyAndNullParams/", "blue"),
Job.create("com.cloudbees.hudson.plugins.folder.Folder", "test-folder", "http://127.0.0.1:8080/job/test-folder/", null)
);
}

@Test(dependsOnMethods = "testCreateJob")
Expand Down Expand Up @@ -439,6 +450,75 @@ public void testGetJobListInFolder() {
assertEquals(output.jobs().get(0), Job.create("hudson.model.FreeStyleProject", "JobInFolder", System.getProperty("test.jenkins.endpoint")+"/job/test-folder/job/test-folder-1/job/JobInFolder/", "notbuilt"));
}

@Test(dependsOnMethods = "testCreateJobInFolder")
public void testGetJobListByDepth2() {
JobListTree output = api().jobList("", 2, null);
assertNotNull(output);
assertFalse(output.jobs().isEmpty());
assertEquals(output.jobs().size(), 3);
JobListTree lastRootElement = output.jobs().get(2);
List<JobListTree> childJobs = lastRootElement.jobs();
assertThat(childJobs).isNotEmpty()
.hasSize(1);
assertThat(childJobs.get(0).jobs()).isNotEmpty()
.hasSize(1);
}

@Test(dependsOnMethods = "testCreateJobInFolder")
public void testGetJobListByDepth1() {
JobListTree output = api().jobList("", 1, null);
assertNotNull(output);
assertFalse(output.jobs().isEmpty());
assertEquals(output.jobs().size(), 3);
JobListTree lastRootElement = output.jobs().get(2);
List<JobListTree> childJobs = lastRootElement.jobs();
assertThat(childJobs).isNotEmpty()
.hasSize(1);
assertThat(childJobs.get(0).jobs()).isNull();
}

@Test(dependsOnMethods = "testCreateJobInFolder")
public void testGetJobListInSelectedFolderWithTreeOnlyGivingFullNameOnCurrentFolder() {
JobListTree output = api().jobList("test-folder/test-folder-1", null, "fullName");
assertNotNull(output);
assertNull(output.jobs());
assertEquals(output, JobListTree.create("com.cloudbees.hudson.plugins.folder.Folder", null, "test-folder/test-folder-1", null, null, null));
}

@Test(dependsOnMethods = "testCreateJobInFolder")
public void testGetJobListFromRootWithTreeCanReturnNestedJob() {
JobListTree output = api().jobList("", null, "jobs[fullName,jobs[fullName,jobs[fullName]]]");
assertNotNull(output);
List<JobListTree> grandChildJob = Lists.newArrayList(JobListTree.create("hudson.model.FreeStyleProject", null, "test-folder/test-folder-1/JobInFolder", null, null, null));
JobListTree childJob = JobListTree.create("com.cloudbees.hudson.plugins.folder.Folder", null, "test-folder/test-folder-1", grandChildJob, null, null);
assertThat(output.jobs())
.isNotEmpty()
.hasSize(3)
.contains(
JobListTree.create("hudson.model.FreeStyleProject", null, "DevTest", null, null, null),
JobListTree.create("hudson.model.FreeStyleProject", null, "JobForEmptyAndNullParams", null, null, null),
JobListTree.create("com.cloudbees.hudson.plugins.folder.Folder", null, "test-folder", Lists.newArrayList(childJob), null, null)
);
}

@Test(dependsOnMethods = "testCreateJobInFolder")
public void testGetJobListInFolderWithTreeReturnAll() {
JobListTree output = api().jobList("test-folder/test-folder-1", null, "jobs[*]");
assertNotNull(output);
assertFalse(output.jobs().isEmpty());
assertEquals(output.jobs().size(), 1);
assertEquals(output.jobs().get(0), JobListTree.create("hudson.model.FreeStyleProject", "JobInFolder", "test-folder/test-folder-1/JobInFolder", null, "notbuilt", "http://127.0.0.1:8080/job/test-folder/job/test-folder-1/job/JobInFolder/"));
}

@Test(dependsOnMethods = "testCreateJobInFolder")
public void testGetJobListInFolderWithTreeOnlyGivingNameAndColor() {
JobListTree output = api().jobList("test-folder/test-folder-1", null, "jobs[name,color]");
assertNotNull(output);
assertFalse(output.jobs().isEmpty());
assertEquals(output.jobs().size(), 1);
assertEquals(output.jobs().get(0), JobListTree.create("hudson.model.FreeStyleProject", "JobInFolder", null, null, "notbuilt", null));
}

@Test(dependsOnMethods = "testCreateJobInFolder")
public void testUpdateJobConfigInFolder() {
String config = payloadFromResource("/freestyle-project.xml");
Expand Down Expand Up @@ -567,7 +647,7 @@ public void testGetBuildParametersOfJobForEmptyAndNullParams() {
assertTrue(parameters.get(1).value().isEmpty());
}

@Test(dependsOnMethods = { "testGetBuildParametersOfJobForEmptyAndNullParams", "testGetJobListFromRoot"})
@Test(dependsOnMethods = { "testGetBuildParametersOfJobForEmptyAndNullParams", "testGetJobListFromRootWithFolders"})
public void testDeleteJobForEmptyAndNullParams() {
RequestStatus success = api().delete(null, "JobForEmptyAndNullParams");
assertTrue(success.value());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.cdancy.jenkins.rest.domain.common.IntegerResponse;
import com.cdancy.jenkins.rest.domain.common.RequestStatus;
import com.cdancy.jenkins.rest.domain.job.*;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.gson.JsonObject;
import okhttp3.mockwebserver.MockResponse;
Expand Down Expand Up @@ -61,6 +62,70 @@ public void testGetInnerFolderJobList() throws Exception {
}
}

public void testGetJobListWithDepth() throws Exception {
MockWebServer server = mockWebServer();

String body = payloadFromResource("/getJobListByDepth.json");
server.enqueue(new MockResponse().setBody(body).setResponseCode(200));
JenkinsApi jenkinsApi = api(server.url("/").url());
try (jenkinsApi) {
JobsApi api = jenkinsApi.jobsApi();
JobListTree output = api.jobList("", 0, null);
assertNotNull(output);
assertNotNull(output.jobs());
assertEquals(output.jobs().size(), 1);
assertEquals(output.jobs().get(0), JobListTree.create("hudson.model.FreeStyleProject", "DevTest", null, null, "notbuilt", "http://localhost:8080/job/DevTest/"));
assertSent(server, "GET", "/api/json", ImmutableMap.of("depth", "0"));
} finally {
server.shutdown();
}
}

public void testGetJobListWithTreeByFullName() throws Exception {
MockWebServer server = mockWebServer();

String body = payloadFromResource("/jobsInJenkinsFolderByFullName.json");
server.enqueue(new MockResponse().setBody(body).setResponseCode(200));
JenkinsApi jenkinsApi = api(server.url("/").url());
Map<String, String> queryParams = ImmutableMap.of("tree", "jobs%5BfullName%5D");
try (jenkinsApi) {
JobsApi api = jenkinsApi.jobsApi();
JobListTree output = api.jobList("Folder1/Folder 2", null, "jobs[fullName]");
assertNotNull(output);
assertNotNull(output.jobs());
assertEquals(output.jobs().size(), 1);
assertEquals(output.jobs().get(0), JobListTree.create("hudson.model.FreeStyleProject", null, "Test Project", null, null, null));
assertSent(server, "GET", "/job/Folder1/job/Folder%202/api/json", queryParams);
} finally {
server.shutdown();
}
}

public void testGetNestedJobList() throws Exception {
MockWebServer server = mockWebServer();

String body = payloadFromResource("/nestedJobList.json");
server.enqueue(new MockResponse().setBody(body).setResponseCode(200));
JenkinsApi jenkinsApi = api(server.url("/").url());
Map<String, String> queryParams = ImmutableMap.of("tree", "jobs%5BfullName,jobs%5BfullName%5D%5D");
try (jenkinsApi) {
JobsApi api = jenkinsApi.jobsApi();
JobListTree output = api.jobList("Folder1/Folder 2", null, "jobs[fullName,jobs[fullName]]");
assertNotNull(output);
assertNotNull(output.jobs());
assertEquals(output.jobs().size(), 2);
assertEquals(output.jobs().get(0), JobListTree.create("hudson.model.FreeStyleProject", null, "DevTest", null, null, null));
JobListTree actualFolder = output.jobs().get(1);
assertEquals(actualFolder.clazz(), "com.cloudbees.hudson.plugins.folder.Folder");
assertEquals(actualFolder.fullName(), "test-folder");
assertEquals(actualFolder.jobs().size(), 1);
assertEquals(actualFolder.jobs().get(0).fullName(), "test-folder/test-folder-1");
assertSent(server, "GET", "/job/Folder1/job/Folder%202/api/json", queryParams);
} finally {
server.shutdown();
}
}

public void testGetRootFolderJobList() throws Exception {
MockWebServer server = mockWebServer();

Expand Down
43 changes: 43 additions & 0 deletions src/test/resources/getJobListByDepth.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"_class": "hudson.model.Hudson",
"assignedLabels": [
{
"name": "built-in"
}
],
"mode": "NORMAL",
"nodeDescription": "the Jenkins controller's built-in node",
"nodeName": "",
"numExecutors": 2,
"description": "Jenkins REST API Configuration as code test",
"jobs": [
{
"_class": "hudson.model.FreeStyleProject",
"name": "DevTest",
"url": "http://localhost:8080/job/DevTest/",
"color": "notbuilt"
}
],
"overallLoad": {},
"primaryView": {
"_class": "hudson.model.AllView",
"name": "all",
"url": "http://localhost:8080/"
},
"quietDownReason": null,
"quietingDown": false,
"slaveAgentPort": 50000,
"unlabeledLoad": {
"_class": "jenkins.model.UnlabeledLoadStatistics"
},
"url": null,
"useCrumbs": true,
"useSecurity": true,
"views": [
{
"_class": "hudson.model.AllView",
"name": "all",
"url": "http://localhost:8080/"
}
]
}
9 changes: 9 additions & 0 deletions src/test/resources/jobsInJenkinsFolderByFullName.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"_class" : "com.cloudbees.hudson.plugins.folder.Folder",
"jobs" : [
{
"_class" : "hudson.model.FreeStyleProject",
"fullName" : "Test Project"
}
]
}
19 changes: 19 additions & 0 deletions src/test/resources/nestedJobList.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"_class": "hudson.model.Hudson",
"jobs": [
{
"_class": "hudson.model.FreeStyleProject",
"fullName": "DevTest"
},
{
"_class": "com.cloudbees.hudson.plugins.folder.Folder",
"fullName": "test-folder",
"jobs": [
{
"_class": "com.cloudbees.hudson.plugins.folder.Folder",
"fullName": "test-folder/test-folder-1"
}
]
}
]
}

0 comments on commit 97f9333

Please sign in to comment.