package com.capitalone.dashboard.collector;
import com.capitalone.dashboard.model.*;
import com.capitalone.dashboard.util.Supplier;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestOperations;
import java.net.URI;
import java.util.*;
import java.util.regex.Pattern;
@Component
public class DefaultJenkinsClient implements JenkinsClient {
private static final Log LOG = LogFactory.getLog(DefaultJenkinsClient.class);
private final RestOperations rest;
private final Transformer<String, List<TestSuite>> cucumberTransformer;
private final Pattern cucumberJsonFilePattern;
private static final String JOBS_URL_SUFFIX = "/api/json?tree=jobs[name,url,builds[number,url]]";
private static final String BUILD_URL_SUFFIX = "/api/json?tree=timestamp,duration,number,fullDisplayName,building,artifacts[fileName,relativePath]";
@Autowired
public DefaultJenkinsClient(Supplier<RestOperations> restOperationsSupplier,
Transformer<String, List<TestSuite>> cucumberTransformer,
JenkinsSettings settings) {
this.rest = restOperationsSupplier.get();
this.cucumberTransformer = cucumberTransformer;
this.cucumberJsonFilePattern = Pattern.compile(settings.getCucumberJsonRegex());
}
@Override
public Map<JenkinsJob, Set<Build>> getInstanceJobs(String instanceUrl) {
Map<JenkinsJob, Set<Build>> result = new LinkedHashMap<>();
try {
JSONObject object = (JSONObject) new JSONParser().parse(getJson(instanceUrl, JOBS_URL_SUFFIX));
for (Object job : getJsonArray(object, "jobs")) {
JSONObject jsonJob = (JSONObject) job;
JenkinsJob jenkinsJob = new JenkinsJob();
jenkinsJob.setInstanceUrl(instanceUrl);
jenkinsJob.setJobName(getString(jsonJob, "name"));
jenkinsJob.setJobUrl(getString(jsonJob, "url"));
Set<Build> builds = new LinkedHashSet<>();
result.put(jenkinsJob, builds);
for (Object build : getJsonArray(jsonJob, "builds")) {
JSONObject jsonBuild = (JSONObject) build;
// A basic Build object. This will be fleshed out later if this is a new Build.
Build hudsonBuild = new Build();
hudsonBuild.setNumber(jsonBuild.get("number").toString());
hudsonBuild.setBuildUrl(getString(jsonBuild, "url"));
builds.add(hudsonBuild);
}
}
} catch (ParseException e) {
LOG.error("Parsing jobs on instance: " + instanceUrl, e);
} catch (RestClientException rce) {
LOG.error(rce);
}
return result;
}
@Override
public boolean buildHasCucumberResults(String buildUrl) {
JSONObject buildJson = null;
try {
// Get Build info
buildJson = (JSONObject) new JSONParser().parse(getJson(buildUrl, BUILD_URL_SUFFIX));
List<TestSuite> suites = new ArrayList<>();
Boolean building = (Boolean) buildJson.get("building");
if ((building != null) && !building) {
for (Object artifactObj : (JSONArray) buildJson.get("artifacts")) {
JSONObject artifact = (JSONObject) artifactObj;
// return true if we find an archived file that matches the naming of the regex config
if (cucumberJsonFilePattern.matcher(getString(artifact, "fileName")).matches()) {
return true;
// TODO: maybe we want to validate that we can parse the json
//String cucumberJson = getCucumberJson(buildUrl, getString(artifact, "relativePath"));
//suites.addAll(cucumberTransformer.transformer(cucumberJson));
}
}
}
return false;
} catch (ParseException e) {
LOG.error("Parsing jobs on instance: " + buildUrl, e);
}
return false;
}
@Override
public TestResult getCucumberTestResult(String buildUrl) {
try {
JSONObject buildJson = (JSONObject) new JSONParser().parse(getJson(buildUrl, BUILD_URL_SUFFIX));
List<TestSuite> suites = new ArrayList<>();
Boolean building = (Boolean) buildJson.get("building");
if (!building) {
for (Object artifactObj : (JSONArray) buildJson.get("artifacts")) {
JSONObject artifact = (JSONObject) artifactObj;
if (cucumberJsonFilePattern.matcher(getString(artifact, "fileName")).matches()) {
String cucumberJson = getCucumberJson(buildUrl, getString(artifact, "relativePath"));
suites.addAll(cucumberTransformer.transformer(cucumberJson));
}
}
}
if (!suites.isEmpty()) {
// There are test suites so let's construct a TestResult to encapsulate these results
TestResult testResult = new TestResult();
testResult.setDescription(getString(buildJson, "fullDisplayName"));
testResult.setExecutionId(buildJson.get("number").toString());
testResult.setUrl(buildUrl);
// Using the build times for start, end and duration is not ideal but the Cucumber JSON does not capture
// start or end times
testResult.setDuration(getLong(buildJson, "duration"));
testResult.setEndTime(getLong(buildJson, "timestamp"));
testResult.setStartTime(testResult.getEndTime() - testResult.getDuration());
testResult.getTestSuites().addAll(suites);
// Calculate counts based on test suites
for (TestSuite suite : suites) {
testResult.setFailureCount(testResult.getFailureCount() + suite.getFailureCount());
testResult.setErrorCount(testResult.getErrorCount() + suite.getErrorCount());
testResult.setSkippedCount(testResult.getSkippedCount() + suite.getSkippedCount());
testResult.setTotalCount(testResult.getTotalCount() + suite.getTotalCount());
}
return testResult;
}
} catch (ParseException e) {
LOG.error("Parsing jobs on instance: " + buildUrl, e);
} catch (RestClientException rce) {
LOG.error(rce);
}
// An exception occurred or this build does not have cucumber test results
return null;
}
// Helper Methods
private String getString(JSONObject json, String key) {
return (String) json.get(key);
}
private long getLong(JSONObject json, String key) {
Object value = json.get(key);
return value == null ? 0 : (long) value;
}
private JSONArray getJsonArray(JSONObject json, String key) {
Object array = json.get(key);
return array == null ? new JSONArray() : (JSONArray) array;
}
private String getJson(String baseUrl, String endpoint) {
String url = StringUtils.removeEnd(baseUrl, "/") + endpoint;
return rest.getForObject(URI.create(url), String.class);
}
private String getCucumberJson(String buildUrl, String artifactRelativePath) {
return getJson(buildUrl, "/artifact/" + artifactRelativePath);
}
}