-
Notifications
You must be signed in to change notification settings - Fork 54
Adding parent-child job support to the pipeline job. #92
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
Changes from all commits
41efd1f
9df77f8
6abe177
cc13d94
691cdaa
cd1fb7c
c1f4865
a8fcb9f
b1d7a80
5056c1a
bddc072
5df30d4
25cc14b
bea898d
e2cad42
631cf69
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,25 +9,29 @@ | |
| 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 java.io.Serializable; | ||
| 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(); | ||
|
|
@@ -51,13 +55,15 @@ 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.warning("BrowserStack credentials not found. Skipping Quality Dashboard initialization."); | ||
| } | ||
| } catch (Exception e) { | ||
| try { | ||
| apiUtil.logToQD(browserStackCredentials, "Global exception in data export is:"); | ||
| apiUtil.logToQD(browserStackCredentials, "Global exception in data export is:" + exceptionToString(e)); | ||
| } catch (Exception ex) { | ||
| String exceptionString = exceptionToString(ex); | ||
| apiUtil.logToQD(browserStackCredentials, "Global exception in exception data export is:" + exceptionString); | ||
|
|
@@ -69,7 +75,7 @@ private static void initQDSetupIfRequired() throws JsonProcessingException { | |
|
|
||
| private static void checkQDIntegrationAndDumpMetaData(BrowserStackCredentials browserStackCredentials) throws JsonProcessingException { | ||
| if(initialQDSetupRequired(browserStackCredentials)) { | ||
| List<String> allPipelines = getAllPipelines(browserStackCredentials); | ||
| List<PipelineInfo> allPipelines = getAllPipelines(browserStackCredentials); | ||
| if(!allPipelines.isEmpty()){ | ||
| boolean projectsSavedSuccessfully = sendPipelinesPaginated(browserStackCredentials, allPipelines); | ||
| if(projectsSavedSuccessfully) { | ||
|
|
@@ -105,41 +111,47 @@ private static boolean initialQDSetupRequired(BrowserStackCredentials browserSta | |
| return false; | ||
| } | ||
|
|
||
| private static List<String> getAllPipelines(BrowserStackCredentials browserStackCredentials) { | ||
| List<String> allPipelines = new ArrayList<>(); | ||
| private static List<PipelineInfo> getAllPipelines(BrowserStackCredentials browserStackCredentials) { | ||
| List<PipelineInfo> allPipelines = new ArrayList<>(); | ||
| Jenkins jenkins = Jenkins.getInstanceOrNull(); | ||
| Integer totalPipelines = 0; | ||
|
|
||
| if (jenkins != null) { | ||
| totalPipelines = jenkins.getAllItems().size(); | ||
| jenkins.getAllItems().forEach(job -> { | ||
| try { | ||
| String itemType = QualityDashboardUtil.getItemTypeModified(job); | ||
| 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" | ||
| itemType, | ||
| isWorkflowJob ? "yes" : "no" | ||
| ) | ||
| ); | ||
| if (itemType != null && !itemType.equals("FOLDER")) { | ||
| String pipelineName = job.getFullName(); | ||
| allPipelines.add(new PipelineInfo(pipelineName, itemType)); | ||
| } | ||
| else{ | ||
| apiUtil.logToQD(browserStackCredentials, "Skipping job or Folder: " + job.getName() + " as it is not a Job or Folder instance"); | ||
| } | ||
|
|
||
| } catch (JsonProcessingException e) { | ||
| // Handling the exception and logging an error | ||
| System.err.println("Error processing JSON for job: " + job.getName()); | ||
| LOGGER.warning("Error processing JSON for job: " + job.getName() + " - " + e.getMessage()); | ||
| 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.warning("Error logging issue with Jenkins instance."); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this be error?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @karanshah-browserstack, We are logging this as a warning instead of an error because the logs appear on the user's Jenkins system. We want to avoid showing error logs to the user. |
||
| e.printStackTrace(); | ||
| } | ||
| } | ||
|
|
@@ -149,20 +161,20 @@ 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.warning("Error processing JSON for total pipelines: " + e.getMessage()); | ||
| e.printStackTrace(); | ||
| } | ||
| // Returning the list of filtered pipelines | ||
| return allPipelines; | ||
| } | ||
|
|
||
| private static boolean sendPipelinesPaginated(BrowserStackCredentials browserStackCredentials, List<String> allPipelines) { | ||
| private static boolean sendPipelinesPaginated(BrowserStackCredentials browserStackCredentials, List<PipelineInfo> allPipelines) { | ||
| boolean isSuccess = true; | ||
| int pageSize = getProjectPageSize(browserStackCredentials); | ||
| List<List<String>> pipelinesInSmallerBatches = Lists.partition(allPipelines, pageSize); | ||
| List<List<PipelineInfo>> pipelinesInSmallerBatches = Lists.partition(allPipelines, pageSize); | ||
| int totalPages = !pipelinesInSmallerBatches.isEmpty() ? pipelinesInSmallerBatches.size() : 0; | ||
| int page = 0; | ||
| for(List<String> singlePagePipelineList : pipelinesInSmallerBatches) { | ||
| for(List<PipelineInfo> singlePagePipelineList : pipelinesInSmallerBatches) { | ||
| try { | ||
| page++; | ||
| ObjectMapper objectMapper = new ObjectMapper(); | ||
|
|
@@ -188,10 +200,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 -> { | ||
|
|
@@ -201,7 +215,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.warning("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); | ||
| } | ||
| ); | ||
|
|
@@ -249,9 +275,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) { | ||
|
|
@@ -268,9 +293,8 @@ private static int getProjectPageSize(BrowserStackCredentials browserStackCreden | |
| } | ||
| } catch(IOException e) { | ||
| e.printStackTrace(); | ||
| } finally { | ||
| return projectPageSize; | ||
| } | ||
| return projectPageSize; | ||
| } | ||
|
|
||
| private static int getResultPageSize(BrowserStackCredentials browserStackCredentials) { | ||
|
|
@@ -287,9 +311,8 @@ private static int getResultPageSize(BrowserStackCredentials browserStackCredent | |
| } | ||
| } catch(IOException e) { | ||
| e.printStackTrace(); | ||
| } finally { | ||
| return resultPageSize; | ||
| } | ||
| return resultPageSize; | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -307,13 +330,35 @@ 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; | ||
| } | ||
| } | ||
|
|
||
| class PipelineInfo implements Serializable { | ||
| @JsonProperty("pipelineName") | ||
| private String pipelineName; | ||
|
|
||
| @JsonProperty("jobType") | ||
| private String jobType; | ||
|
|
||
| public PipelineInfo(String pipelineName, String jobType) { | ||
| this.pipelineName = pipelineName; | ||
| this.jobType = jobType; | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -325,9 +370,9 @@ class PipelinesPaginated { | |
| private int totalPages; | ||
|
|
||
| @JsonProperty("pipelines") | ||
| private List<String> pipelines; | ||
| private List<PipelineInfo> pipelines; | ||
|
|
||
| public PipelinesPaginated(int page, int totalPages, List<String> pipelines) { | ||
| public PipelinesPaginated(int page, int totalPages, List<PipelineInfo> pipelines) { | ||
| this.page = page; | ||
| this.totalPages = totalPages; | ||
| this.pipelines = pipelines; | ||
|
|
||
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.