99import hudson .Extension ;
1010import hudson .init .InitMilestone ;
1111import hudson .init .Initializer ;
12+ import hudson .model .Job ;
1213import hudson .model .Result ;
14+ import hudson .model .Run ;
1315import java .sql .Timestamp ;
1416import java .time .Instant ;
17+ import java .io .Serializable ;
1518import jenkins .model .Jenkins ;
1619import okhttp3 .*;
1720import org .jenkinsci .plugins .workflow .job .WorkflowJob ;
18- import org .jenkinsci .plugins .workflow .job .WorkflowRun ;
1921import java .time .temporal .ChronoUnit ;
2022import java .io .IOException ;
2123import java .net .HttpURLConnection ;
2224import java .util .ArrayList ;
2325import java .util .List ;
26+ import java .util .logging .Logger ;
2427
2528@ Extension
2629public class QualityDashboardInit {
2730
31+ private static final Logger LOGGER = Logger .getLogger (QualityDashboardInit .class .getName ());
2832 static QualityDashboardAPIUtil apiUtil = new QualityDashboardAPIUtil ();
2933
30- @ Initializer (after = InitMilestone .PLUGINS_PREPARED )
34+ @ Initializer (after = InitMilestone .JOB_LOADED )
3135 public static void postInstall () {
3236 try {
3337 initQDSetupIfRequired ();
@@ -51,13 +55,15 @@ private static String exceptionToString(Throwable throwable) {
5155 private static void initQDSetupIfRequired () throws JsonProcessingException {
5256 BrowserStackCredentials browserStackCredentials = QualityDashboardUtil .getBrowserStackCreds ();
5357 try {
54- if (browserStackCredentials != null ) {
58+ if (browserStackCredentials != null ) {
5559 apiUtil .logToQD (browserStackCredentials ,"Starting plugin data export to QD" );
5660 checkQDIntegrationAndDumpMetaData (browserStackCredentials );
61+ } else {
62+ LOGGER .warning ("BrowserStack credentials not found. Skipping Quality Dashboard initialization." );
5763 }
5864 } catch (Exception e ) {
5965 try {
60- apiUtil .logToQD (browserStackCredentials , "Global exception in data export is:" );
66+ apiUtil .logToQD (browserStackCredentials , "Global exception in data export is:" + exceptionToString ( e ) );
6167 } catch (Exception ex ) {
6268 String exceptionString = exceptionToString (ex );
6369 apiUtil .logToQD (browserStackCredentials , "Global exception in exception data export is:" + exceptionString );
@@ -69,7 +75,7 @@ private static void initQDSetupIfRequired() throws JsonProcessingException {
6975
7076 private static void checkQDIntegrationAndDumpMetaData (BrowserStackCredentials browserStackCredentials ) throws JsonProcessingException {
7177 if (initialQDSetupRequired (browserStackCredentials )) {
72- List <String > allPipelines = getAllPipelines (browserStackCredentials );
78+ List <PipelineInfo > allPipelines = getAllPipelines (browserStackCredentials );
7379 if (!allPipelines .isEmpty ()){
7480 boolean projectsSavedSuccessfully = sendPipelinesPaginated (browserStackCredentials , allPipelines );
7581 if (projectsSavedSuccessfully ) {
@@ -105,41 +111,47 @@ private static boolean initialQDSetupRequired(BrowserStackCredentials browserSta
105111 return false ;
106112 }
107113
108- private static List <String > getAllPipelines (BrowserStackCredentials browserStackCredentials ) {
109- List <String > allPipelines = new ArrayList <>();
114+ private static List <PipelineInfo > getAllPipelines (BrowserStackCredentials browserStackCredentials ) {
115+ List <PipelineInfo > allPipelines = new ArrayList <>();
110116 Jenkins jenkins = Jenkins .getInstanceOrNull ();
111117 Integer totalPipelines = 0 ;
112118
113119 if (jenkins != null ) {
114120 totalPipelines = jenkins .getAllItems ().size ();
115121 jenkins .getAllItems ().forEach (job -> {
116122 try {
123+ String itemType = QualityDashboardUtil .getItemTypeModified (job );
124+ boolean isWorkflowJob = job instanceof WorkflowJob ;
125+
117126 // Logging job details
118127 apiUtil .logToQD (
119128 browserStackCredentials ,
120129 String .format (
121130 "Job name: %s, instance type: %s, and is_workflow_job: %s" ,
122131 job .getName (),
123- job . getClass (). getSimpleName () ,
124- ( job instanceof WorkflowJob ) ? "yes" : "no"
132+ itemType ,
133+ isWorkflowJob ? "yes" : "no"
125134 )
126135 );
136+ if (itemType != null && !itemType .equals ("FOLDER" )) {
137+ String pipelineName = job .getFullName ();
138+ allPipelines .add (new PipelineInfo (pipelineName , itemType ));
139+ }
140+ else {
141+ apiUtil .logToQD (browserStackCredentials , "Skipping job or Folder: " + job .getName () + " as it is not a Job or Folder instance" );
142+ }
143+
127144 } catch (JsonProcessingException e ) {
128145 // Handling the exception and logging an error
129- System . err . println ("Error processing JSON for job: " + job .getName ());
146+ LOGGER . warning ("Error processing JSON for job: " + job .getName () + " - " + e . getMessage ());
130147 e .printStackTrace ();
131148 }
132-
133- if (job instanceof WorkflowJob ) {
134- String pipelineName = job .getFullName (); // Getting pipeline name
135- allPipelines .add (pipelineName );
136- }
137149 });
138150 } else {
139151 try {
140152 apiUtil .logToQD (browserStackCredentials , "Issue getting Jenkins Instance" );
141153 } catch (JsonProcessingException e ) {
142- System . err . println ("Error logging issue with Jenkins instance." );
154+ LOGGER . warning ("Error logging issue with Jenkins instance." );
143155 e .printStackTrace ();
144156 }
145157 }
@@ -149,20 +161,20 @@ private static List<String> getAllPipelines(BrowserStackCredentials browserStack
149161 apiUtil .logToQD (browserStackCredentials ,"Total Pipelines detected : " + allPipelines .size ());
150162 } catch (JsonProcessingException e ) {
151163 // Handling the exception and logging an error
152- System . err . println ("Error processing JSON for total pipelines: " );
164+ LOGGER . warning ("Error processing JSON for total pipelines: " + e . getMessage () );
153165 e .printStackTrace ();
154166 }
155167 // Returning the list of filtered pipelines
156168 return allPipelines ;
157169 }
158170
159- private static boolean sendPipelinesPaginated (BrowserStackCredentials browserStackCredentials , List <String > allPipelines ) {
171+ private static boolean sendPipelinesPaginated (BrowserStackCredentials browserStackCredentials , List <PipelineInfo > allPipelines ) {
160172 boolean isSuccess = true ;
161173 int pageSize = getProjectPageSize (browserStackCredentials );
162- List <List <String >> pipelinesInSmallerBatches = Lists .partition (allPipelines , pageSize );
174+ List <List <PipelineInfo >> pipelinesInSmallerBatches = Lists .partition (allPipelines , pageSize );
163175 int totalPages = !pipelinesInSmallerBatches .isEmpty () ? pipelinesInSmallerBatches .size () : 0 ;
164176 int page = 0 ;
165- for (List <String > singlePagePipelineList : pipelinesInSmallerBatches ) {
177+ for (List <PipelineInfo > singlePagePipelineList : pipelinesInSmallerBatches ) {
166178 try {
167179 page ++;
168180 ObjectMapper objectMapper = new ObjectMapper ();
@@ -188,10 +200,12 @@ private static List<PipelineDetails> getAllBuilds(BrowserStackCredentials browse
188200 Jenkins jenkins = Jenkins .getInstanceOrNull ();
189201 Instant thresholdInstant = Instant .now ().minus (getHistoryForDays (browserStackCredentials ), ChronoUnit .DAYS );
190202 if (jenkins != null ) {
191- jenkins .getAllItems ().forEach (job -> {
192- if (job instanceof WorkflowJob ) {
203+ jenkins .getAllItems ().forEach (item -> {
204+ // Support both WorkflowJob and Matrix projects (and potentially other job types)
205+ if (item instanceof Job ) {
206+ Job <?, ?> job = (Job <?, ?>) item ;
193207 String pipelineName = job .getFullName ();
194- List <WorkflowRun > allBuilds = (( WorkflowJob ) job ) .getBuilds ();
208+ List <? extends Run <?, ?>> allBuilds = job .getBuilds ();
195209 if (!allBuilds .isEmpty ()) {
196210 allBuilds .stream ().filter (build -> Instant .ofEpochMilli (build .getTimeInMillis ()).isAfter (thresholdInstant )).forEach (
197211 build -> {
@@ -201,7 +215,19 @@ private static List<PipelineDetails> getAllBuilds(BrowserStackCredentials browse
201215 long endTimeInMillis = build .getTimeInMillis ();
202216 Timestamp endTime = new Timestamp (endTimeInMillis );
203217 String result = overallResult != null ? overallResult .toString () : null ;
204- PipelineDetails pipelineDetail = new PipelineDetails (pipelineName , buildNumber , duration , result , endTime );
218+
219+ // Get root upstream project information for QEI with build number (returns in format "project#build")
220+ String rootUpstreamProject = "" ;
221+ String immediateParentProject = "" ;
222+ try {
223+ rootUpstreamProject = UpstreamPipelineResolver .resolveRootUpstreamProject (build , browserStackCredentials );
224+ immediateParentProject = UpstreamPipelineResolver .resolveImmediateUpstreamProjectForQEI (build , browserStackCredentials );
225+ } catch (Exception e ) {
226+ LOGGER .warning ("Error resolving upstream project for " + pipelineName + " build number " + buildNumber + ": " + e .getMessage ());
227+ e .printStackTrace ();
228+ }
229+ PipelineDetails pipelineDetail = new PipelineDetails (pipelineName , buildNumber , duration , result ,
230+ endTime , rootUpstreamProject , immediateParentProject );
205231 allBuildResults .add (pipelineDetail );
206232 }
207233 );
@@ -249,9 +275,8 @@ private static int getHistoryForDays(BrowserStackCredentials browserStackCredent
249275 }
250276 } catch (IOException e ) {
251277 e .printStackTrace ();
252- } finally {
253- return no_of_days ;
254278 }
279+ return no_of_days ;
255280 }
256281
257282 private static int getProjectPageSize (BrowserStackCredentials browserStackCredentials ) {
@@ -268,9 +293,8 @@ private static int getProjectPageSize(BrowserStackCredentials browserStackCreden
268293 }
269294 } catch (IOException e ) {
270295 e .printStackTrace ();
271- } finally {
272- return projectPageSize ;
273296 }
297+ return projectPageSize ;
274298 }
275299
276300 private static int getResultPageSize (BrowserStackCredentials browserStackCredentials ) {
@@ -287,9 +311,8 @@ private static int getResultPageSize(BrowserStackCredentials browserStackCredent
287311 }
288312 } catch (IOException e ) {
289313 e .printStackTrace ();
290- } finally {
291- return resultPageSize ;
292314 }
315+ return resultPageSize ;
293316 }
294317}
295318
@@ -307,13 +330,35 @@ class PipelineDetails {
307330
308331 @ JsonProperty ("endTime" )
309332 private Timestamp endTime ;
310-
311- public PipelineDetails (String pipelineName , Integer buildNumber , Long buildDuration , String buildStatus , Timestamp endTime ) {
333+
334+ @ JsonProperty ("rootProject" )
335+ private String rootProject ;
336+
337+ @ JsonProperty ("immediateParentProject" )
338+ private String immediateParentProject ;
339+
340+ public PipelineDetails (String pipelineName , Integer buildNumber , Long buildDuration , String buildStatus ,
341+ Timestamp endTime , String rootProject , String immediateParentProject ) {
312342 this .pipelineName = pipelineName ;
313343 this .buildNumber = buildNumber ;
314344 this .buildDuration = buildDuration ;
315345 this .buildStatus = buildStatus ;
316346 this .endTime = endTime ;
347+ this .rootProject = rootProject ;
348+ this .immediateParentProject = immediateParentProject ;
349+ }
350+ }
351+
352+ class PipelineInfo implements Serializable {
353+ @ JsonProperty ("pipelineName" )
354+ private String pipelineName ;
355+
356+ @ JsonProperty ("jobType" )
357+ private String jobType ;
358+
359+ public PipelineInfo (String pipelineName , String jobType ) {
360+ this .pipelineName = pipelineName ;
361+ this .jobType = jobType ;
317362 }
318363}
319364
@@ -325,9 +370,9 @@ class PipelinesPaginated {
325370 private int totalPages ;
326371
327372 @ JsonProperty ("pipelines" )
328- private List <String > pipelines ;
373+ private List <PipelineInfo > pipelines ;
329374
330- public PipelinesPaginated (int page , int totalPages , List <String > pipelines ) {
375+ public PipelinesPaginated (int page , int totalPages , List <PipelineInfo > pipelines ) {
331376 this .page = page ;
332377 this .totalPages = totalPages ;
333378 this .pipelines = pipelines ;
0 commit comments