Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 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
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ public static final class SessionStatus {
public static final class QualityDashboardAPI {
public static final String QEI_DEFAULT_URL = "https://quality-engineering-insights.browserstack.com";
public static String host = QEI_DEFAULT_URL;
// Cache configuration
public static final long CACHE_DURATION_MS = 60 * 60 * 1000L; // 1 hour in milliseconds

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok for now but ideally it should be configurable either on client side or server side.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted. As discussed, we will handle this in the future.


public static String getHost() {
return host;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,28 @@
import hudson.Extension;
import hudson.init.InitMilestone;
import hudson.init.Initializer;
import hudson.model.Job;
import hudson.model.Result;
import hudson.model.Run;
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;
import java.util.logging.Logger;

@Extension
public class QualityDashboardInit {

private static final Logger LOGGER = Logger.getLogger(QualityDashboardInit.class.getName());
static QualityDashboardAPIUtil apiUtil = new QualityDashboardAPIUtil();

@Initializer(after = InitMilestone.PLUGINS_PREPARED)
@Initializer(after = InitMilestone.JOB_LOADED)
public static void postInstall() {
try {
initQDSetupIfRequired();
Expand All @@ -51,9 +54,11 @@ private static String exceptionToString(Throwable throwable) {
private static void initQDSetupIfRequired() throws JsonProcessingException {
BrowserStackCredentials browserStackCredentials = QualityDashboardUtil.getBrowserStackCreds();
try {
if(browserStackCredentials!=null) {
if(browserStackCredentials != null) {
apiUtil.logToQD(browserStackCredentials,"Starting plugin data export to QD");
checkQDIntegrationAndDumpMetaData(browserStackCredentials);
} else {
LOGGER.info("BrowserStack credentials not found. Skipping Quality Dashboard initialization.");
}
} catch (Exception e) {
try {
Expand Down Expand Up @@ -114,32 +119,38 @@ private static List<String> getAllPipelines(BrowserStackCredentials browserStack
totalPipelines = jenkins.getAllItems().size();
jenkins.getAllItems().forEach(job -> {
try {
String jobType = job.getClass().getSimpleName();
boolean isWorkflowJob = job instanceof WorkflowJob;

// Logging job details
apiUtil.logToQD(
browserStackCredentials,
String.format(
"Job name: %s, instance type: %s, and is_workflow_job: %s",
job.getName(),
job.getClass().getSimpleName(),
(job instanceof WorkflowJob) ? "yes" : "no"
jobType,
isWorkflowJob ? "yes" : "no"
)
);
if ( job instanceof Job) {
String pipelineName = job.getFullName();
allPipelines.add(pipelineName);
}
else{
apiUtil.logToQD(browserStackCredentials, "Skipping job: " + job.getName() + " as it is not a Job instance");
}

} catch (JsonProcessingException e) {
// Handling the exception and logging an error
System.err.println("Error processing JSON for job: " + job.getName());
LOGGER.info("Error processing JSON for job: " + job.getName() + " - " + e.getMessage());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should be logger error right? And till will also be displayed to user correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@karanshah-browserstack , yes, the user will be able to see it in the Jenkins System Log. I've changed it to a warning in all places.

e.printStackTrace();
}

if (job instanceof WorkflowJob) {
String pipelineName = job.getFullName(); // Getting pipeline name
allPipelines.add(pipelineName);
}
});
} else {
try {
apiUtil.logToQD(browserStackCredentials, "Issue getting Jenkins Instance");
} catch (JsonProcessingException e) {
System.err.println("Error logging issue with Jenkins instance.");
LOGGER.info("Error logging issue with Jenkins instance.");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same error Logger comment as above for all logger related changes

e.printStackTrace();
}
}
Expand All @@ -149,7 +160,7 @@ private static List<String> getAllPipelines(BrowserStackCredentials browserStack
apiUtil.logToQD(browserStackCredentials,"Total Pipelines detected : " + allPipelines.size());
} catch (JsonProcessingException e) {
// Handling the exception and logging an error
System.err.println("Error processing JSON for total pipelines: ");
LOGGER.info("Error processing JSON for total pipelines: " + e.getMessage());
e.printStackTrace();
}
// Returning the list of filtered pipelines
Expand Down Expand Up @@ -188,10 +199,12 @@ private static List<PipelineDetails> getAllBuilds(BrowserStackCredentials browse
Jenkins jenkins = Jenkins.getInstanceOrNull();
Instant thresholdInstant = Instant.now().minus(getHistoryForDays(browserStackCredentials), ChronoUnit.DAYS);
if (jenkins != null) {
jenkins.getAllItems().forEach(job -> {
if (job instanceof WorkflowJob) {
jenkins.getAllItems().forEach(item -> {
// Support both WorkflowJob and Matrix projects (and potentially other job types)
if (item instanceof Job) {
Job<?, ?> job = (Job<?, ?>) item;
String pipelineName = job.getFullName();
List<WorkflowRun> allBuilds = ((WorkflowJob) job).getBuilds();
List<? extends Run<?, ?>> allBuilds = job.getBuilds();
if(!allBuilds.isEmpty()) {
allBuilds.stream().filter(build -> Instant.ofEpochMilli(build.getTimeInMillis()).isAfter(thresholdInstant)).forEach(
build -> {
Expand All @@ -201,7 +214,19 @@ private static List<PipelineDetails> getAllBuilds(BrowserStackCredentials browse
long endTimeInMillis = build.getTimeInMillis();
Timestamp endTime = new Timestamp(endTimeInMillis);
String result = overallResult != null ? overallResult.toString() : null;
PipelineDetails pipelineDetail = new PipelineDetails(pipelineName, buildNumber, duration, result, endTime);

// Get root upstream project information for QEI with build number (returns in format "project#build")
String rootUpstreamProject = "";
String immediateParentProject = "";
try {
rootUpstreamProject = UpstreamPipelineResolver.resolveRootUpstreamProject(build, browserStackCredentials);
immediateParentProject = UpstreamPipelineResolver.resolveImmediateUpstreamProjectForQEI(build, browserStackCredentials);
} catch (Exception e) {
LOGGER.info("Error resolving upstream project for " + pipelineName + " build number " + buildNumber + ": " + e.getMessage());
e.printStackTrace();
}
PipelineDetails pipelineDetail = new PipelineDetails(pipelineName, buildNumber, duration, result,
endTime, rootUpstreamProject, immediateParentProject);
allBuildResults.add(pipelineDetail);
}
);
Expand Down Expand Up @@ -249,9 +274,8 @@ private static int getHistoryForDays(BrowserStackCredentials browserStackCredent
}
} catch(IOException e) {
e.printStackTrace();
} finally {
return no_of_days;
}
return no_of_days;
}

private static int getProjectPageSize(BrowserStackCredentials browserStackCredentials) {
Expand All @@ -268,9 +292,8 @@ private static int getProjectPageSize(BrowserStackCredentials browserStackCreden
}
} catch(IOException e) {
e.printStackTrace();
} finally {
return projectPageSize;
}
return projectPageSize;
}

private static int getResultPageSize(BrowserStackCredentials browserStackCredentials) {
Expand All @@ -287,9 +310,8 @@ private static int getResultPageSize(BrowserStackCredentials browserStackCredent
}
} catch(IOException e) {
e.printStackTrace();
} finally {
return resultPageSize;
}
return resultPageSize;
}
}

Expand All @@ -307,13 +329,22 @@ class PipelineDetails {

@JsonProperty("endTime")
private Timestamp endTime;

public PipelineDetails(String pipelineName, Integer buildNumber, Long buildDuration, String buildStatus, Timestamp endTime) {

@JsonProperty("rootProject")
private String rootProject;

@JsonProperty("immediateParentProject")
private String immediateParentProject;

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,22 @@
import okhttp3.RequestBody;
import okhttp3.Response;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import java.io.IOException;
import java.io.Serializable;
import java.util.logging.Logger;

@Extension
public class QualityDashboardInitItemListener extends ItemListener {

private static final Logger LOGGER = Logger.getLogger(QualityDashboardInitItemListener.class.getName());

@Override
public void onCreated(Item job) {
try {
BrowserStackCredentials browserStackCredentials = QualityDashboardUtil.getBrowserStackCreds();
if(browserStackCredentials == null) {
LOGGER.info("BrowserStackCredentials not found. Please ensure they are configured correctly.");
return;
}
QualityDashboardAPIUtil apiUtil = new QualityDashboardAPIUtil();
apiUtil.logToQD(browserStackCredentials, "Item Created : " + job.getClass().getName()) ;

Expand All @@ -35,7 +41,8 @@ public void onCreated(Item job) {
String jsonBody = getJsonReqBody(new ItemUpdate(itemName, itemType));
syncItemListToQD(jsonBody, Constants.QualityDashboardAPI.getItemCrudEndpoint(), "POST");

} catch(IOException e) {
} catch(Exception e) {
LOGGER.info("Error syncing item creation to Quality Dashboard: " + e.getMessage());
e.printStackTrace();
}
}
Expand All @@ -52,7 +59,8 @@ public void onDeleted(Item job) {
try {
String jsonBody = getJsonReqBody(new ItemUpdate(itemName, itemType));
syncItemListToQD(jsonBody, Constants.QualityDashboardAPI.getItemCrudEndpoint(), "DELETE");
} catch(IOException e) {
} catch(Exception e) {
LOGGER.info("Error syncing item deletion to Quality Dashboard: " + e.getMessage());
e.printStackTrace();
}
}
Expand All @@ -67,7 +75,8 @@ public void onRenamed(Item job, String oldName, String newName) {
newName = job.getParent().getFullName() + "/" + newName;
String jsonBody = getJsonReqBody(new ItemRename(oldName, newName, itemType));
syncItemListToQD(jsonBody, Constants.QualityDashboardAPI.getItemCrudEndpoint(), "PUT");
} catch(IOException e) {
} catch(Exception e) {
LOGGER.info("Error syncing item rename to Quality Dashboard: " + e.getMessage());
e.printStackTrace();
}
}
Expand All @@ -93,6 +102,10 @@ private Response syncItemListToQD(String jsonBody, String url, String typeOfRequ
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), jsonBody);
QualityDashboardAPIUtil apiUtil = new QualityDashboardAPIUtil();
BrowserStackCredentials browserStackCredentials = QualityDashboardUtil.getBrowserStackCreds();
if(browserStackCredentials == null) {
LOGGER.info("BrowserStack credentials not found. Please ensure they are configured correctly.");
return null;
}
if(typeOfRequest.equals("PUT")) {
apiUtil.logToQD(browserStackCredentials, "Syncing Item Update - PUT");
return apiUtil.makePutRequestToQd(url, browserStackCredentials, requestBody);
Expand Down
Loading