Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Quality Dashboard Integrated #76

Merged
merged 6 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
<tag>HEAD</tag>
</scm>

<!-- Adding a distribution management section here to override the now
defunct mave.jenkins-ci.org. If we update the parent pom version to newer
<!-- Adding a distribution management section here to override the now
defunct mave.jenkins-ci.org. If we update the parent pom version to newer
than 2.5 then this section is not needed. -->
<distributionManagement>
<repository>
Expand All @@ -53,7 +53,7 @@


<properties>
<!-- Baseline Jenkins version you use to build the plugin. Users must
<!-- Baseline Jenkins version you use to build the plugin. Users must
have this version or newer to run. -->
<jenkins.version>2.334</jenkins.version>
<java.level>8</java.level>
Expand All @@ -62,8 +62,8 @@
<junit-quickcheck.version>0.8</junit-quickcheck.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<!-- This is the testing Tracking Id. If you want to build the project
for production, please use the production profile which replaces this with
<!-- This is the testing Tracking Id. If you want to build the project
for production, please use the production profile which replaces this with
the production tracking id. -->
<ga.tracking.id>UA-79358556-2</ga.tracking.id>
</properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,23 @@ public static final class SessionStatus {
public static final String UNMARKED = "unmarked";
public static final String PASSED = "passed";
}

public static final class QualityDashboardAPI {

public static final String URL_BASE = "https://quality-dashboard-api.browserstack.com/api/v1/jenkins";
public static final String IS_INIT_SETUP_REQUIRED = URL_BASE + "/init-setup-required";

public static final String HISTORY_FOR_DAYS = URL_BASE + "/history-for-days";

public static final String ADD_ALL_PIPELINES = URL_BASE + "/add-all-pipelines";

public static final String ITEM_CRUD = URL_BASE + "/item";
public static final String IS_QD_ENABLED = URL_BASE + "/qd-enabled";
public static final String IS_PIPELINE_ENABLED = URL_BASE + "/pipeline-enabled";
public static final String GET_RESULT_DIRECTORY = URL_BASE + "/get-result-directory";

public static final String UPLOAD_RESULT_ZIP = URL_BASE + "/upload-result";
public static final String STORE_PIPELINE_RESULTS = URL_BASE + "/save-results";

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.browserstack.automate.ci.common.BrowserStackBuildWrapperOperations;
import com.browserstack.automate.ci.common.analytics.Analytics;
import com.browserstack.automate.ci.jenkins.local.LocalConfig;
import com.browserstack.automate.ci.jenkins.qualityDashboard.QualityDashboardInit;
import hudson.Extension;
import hudson.model.AbstractProject;
import hudson.model.Item;
Expand Down Expand Up @@ -51,8 +52,11 @@ public boolean configure(StaplerRequest req, JSONObject formData) throws FormExc
if (config.has("usageStatsEnabled")) {
setEnableUsageStats(config.getBoolean("usageStatsEnabled"));
}
if (config.has("credentialsId")){
QualityDashboardInit qdInit = new QualityDashboardInit();
qdInit.pluginConfiguredNotif();
}
}

return true;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.browserstack.automate.ci.jenkins.qualityDashboard;

import com.browserstack.automate.ci.jenkins.BrowserStackCredentials;
import okhttp3.*;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

public class QualityDashboardAPIUtil {

OkHttpClient client = new OkHttpClient();

public Response makeGetRequestToQd(String getUrl, BrowserStackCredentials browserStackCredentials) {
try {
Request request = new Request.Builder()
.url(getUrl)
.header("Authorization", Credentials.basic(browserStackCredentials.getUsername(), browserStackCredentials.getDecryptedAccesskey()))
.build();
Response response = client.newCall(request).execute();
return response;
} catch(IOException e) {
e.printStackTrace();
}
return null;
}

public Response makePostRequestToQd(String postUrl, BrowserStackCredentials browserStackCredentials, RequestBody requestBody) {
try {
Request request = new Request.Builder()
.url(postUrl)
.header("Authorization", Credentials.basic(browserStackCredentials.getUsername(), browserStackCredentials.getDecryptedAccesskey()))
.post(requestBody)
.build();
Response response = client.newCall(request).execute();
return response;
} catch(IOException e) {
e.printStackTrace();
}
return null;
}

public Response makePutRequestToQd(String postUrl, BrowserStackCredentials browserStackCredentials, RequestBody requestBody) {
try {
Request request = new Request.Builder()
.url(postUrl)
.header("Authorization", Credentials.basic(browserStackCredentials.getUsername(), browserStackCredentials.getDecryptedAccesskey()))
.put(requestBody)
.build();
Response response = client.newCall(request).execute();
return response;
} catch(IOException e) {
e.printStackTrace();
}
return null;
}

public Response makeDeleteRequestToQd(String postUrl, BrowserStackCredentials browserStackCredentials, RequestBody requestBody) {
try {
Request request = new Request.Builder()
.url(postUrl)
.header("Authorization", Credentials.basic(browserStackCredentials.getUsername(), browserStackCredentials.getDecryptedAccesskey()))
.delete(requestBody)
.build();
Response response = client.newCall(request).execute();
return response;
} catch(IOException e) {
e.printStackTrace();
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package com.browserstack.automate.ci.jenkins.qualityDashboard;

import com.browserstack.automate.ci.common.constants.Constants;
import com.browserstack.automate.ci.jenkins.BrowserStackCredentials;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import hudson.Extension;
import hudson.init.InitMilestone;
import hudson.init.Initializer;
import hudson.model.Result;
import java.sql.Timestamp;
import java.time.Instant;
import jenkins.model.Jenkins;
import okhttp3.*;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import java.time.temporal.ChronoUnit;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.List;

@Extension
public class QualityDashboardInit {

static QualityDashboardAPIUtil apiUtil = new QualityDashboardAPIUtil();

@Initializer(after = InitMilestone.PLUGINS_PREPARED)
public static void postInstall() {
initQDSetupIfRequired();
}

public void pluginConfiguredNotif() {
initQDSetupIfRequired();
}

private static void initQDSetupIfRequired() {
BrowserStackCredentials browserStackCredentials = QualityDashboardUtil.getBrowserStackCreds();
if(browserStackCredentials!=null) {
checkQDIntegrationAndDumpMetaData(browserStackCredentials);
}
}

private static void checkQDIntegrationAndDumpMetaData(BrowserStackCredentials browserStackCredentials) {
if(initialQDSetupRequired(browserStackCredentials)) {
List<PipelineDetailsMap> pipelineDetailsMapList = getExistingPipelineDump(browserStackCredentials);
if(!pipelineDetailsMapList.isEmpty()){
syncInitialDataWithQD(pipelineDetailsMapList, browserStackCredentials);
}
}
}

private static boolean initialQDSetupRequired(BrowserStackCredentials browserStackCredentials) {
try {
Response response = apiUtil.makeGetRequestToQd(Constants.QualityDashboardAPI.IS_INIT_SETUP_REQUIRED, browserStackCredentials);
if (response != null && response.code() == HttpURLConnection.HTTP_OK) {
ResponseBody responseBody = response.body();
if(responseBody != null && responseBody.string().equals("REQUIRED")) {
return true;
}
}
} catch(IOException e) {
e.printStackTrace();
}
return false;
}

private static List<PipelineDetailsMap> getExistingPipelineDump(BrowserStackCredentials browserStackCredentials) {
List<PipelineDetailsMap> pipelineDetailsMapList = new ArrayList<>();
Jenkins jenkins = Jenkins.getInstanceOrNull();
Instant thresholdInstant = Instant.now().minus(getHistoryForDays(browserStackCredentials), ChronoUnit.DAYS);
if (jenkins != null) {
jenkins.getAllItems().forEach(job -> {
if(job instanceof WorkflowJob) {
String pipelineName = job.getFullName();
List<PipelineDetails> pipelineDetailsList = new ArrayList<>();
List<WorkflowRun> allBuilds = ((WorkflowJob) job).getBuilds();
if(!allBuilds.isEmpty()) {
allBuilds.stream().filter(build -> Instant.ofEpochMilli(build.getTimeInMillis()).isAfter(thresholdInstant) ).forEach(
build -> {
int buildNumber = build.getNumber();
long duration = build.getDuration();
Result overallResult = build.getResult();
long endTimeInMillis = build.getTimeInMillis();
Timestamp endTime = new Timestamp(endTimeInMillis);
PipelineDetails pipelineDetail = new PipelineDetails(buildNumber, duration, overallResult.toString(), endTime );
pipelineDetailsList.add(pipelineDetail);
}
);
}
PipelineDetailsMap pipelineDetailsMap = new PipelineDetailsMap(pipelineName, pipelineDetailsList);
pipelineDetailsMapList.add(pipelineDetailsMap);
}
});
}
return pipelineDetailsMapList;
}

private static int getHistoryForDays(BrowserStackCredentials browserStackCredentials) {
int no_of_days = 90;
try {
Response response = apiUtil.makeGetRequestToQd(Constants.QualityDashboardAPI.HISTORY_FOR_DAYS, browserStackCredentials);
if (response != null && response.code() == HttpURLConnection.HTTP_OK) {
ResponseBody responseBody = response.body();
if(responseBody != null) {
String responseBodyStr = responseBody.string();
if(responseBodyStr!=null)
no_of_days = Integer.parseInt(responseBodyStr);
}
}
} catch(IOException e) {
e.printStackTrace();
} finally {
return no_of_days;
}
}

private static void syncInitialDataWithQD(List<PipelineDetailsMap> pipelineDetailsMapList, BrowserStackCredentials browserStackCredentials) {
try {
ObjectMapper objectMapper = new ObjectMapper();
String jsonBody = objectMapper.writeValueAsString(pipelineDetailsMapList);

RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), jsonBody);
Response response = apiUtil.makePostRequestToQd(Constants.QualityDashboardAPI.ADD_ALL_PIPELINES, browserStackCredentials, requestBody);
} catch(IOException e) {
e.printStackTrace();
}
}
}

class PipelineDetails {

@JsonProperty("buildNumber")
private Integer buildNumber;
@JsonProperty("buildDuration")
private Long buildDuration;
@JsonProperty("buildStatus")
private String buildStatus;

@JsonProperty("endTime")
private Timestamp endTime;

public PipelineDetails(Integer buildNumber, Long buildDuration, String buildStatus, Timestamp endTime) {
this.buildNumber = buildNumber;
this.buildDuration = buildDuration;
this.buildStatus = buildStatus;
this.endTime = endTime;
}
}

class PipelineDetailsMap {
@JsonProperty("pipelineName")
private String pipelineName;

@JsonProperty("pipelineDetails")
private List<PipelineDetails> pipelineDetails;

public PipelineDetailsMap(String pipelineName, List<PipelineDetails> pipelineDetails) {
this.pipelineName = pipelineName;
this.pipelineDetails = pipelineDetails;
}
}
Loading
Loading