Skip to content

Commit a530400

Browse files
authored
Merge branch 'master' into TRAP-505-version-bump
2 parents f149b78 + b296063 commit a530400

File tree

7 files changed

+555
-113
lines changed

7 files changed

+555
-113
lines changed

src/main/java/com/browserstack/automate/ci/common/constants/Constants.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ public static final class SessionStatus {
7777
public static final class QualityDashboardAPI {
7878
public static final String QEI_DEFAULT_URL = "https://quality-engineering-insights.browserstack.com";
7979
public static String host = QEI_DEFAULT_URL;
80+
// Cache configuration
81+
public static final long CACHE_DURATION_MS = 60 * 60 * 1000L; // 1 hour in milliseconds
8082

8183
public static String getHost() {
8284
return host;

src/main/java/com/browserstack/automate/ci/jenkins/observability/AccessControlsFilter.java

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@
2323
import javax.servlet.http.HttpServletResponse;
2424
import java.io.IOException;
2525
import java.io.ObjectStreamException;
26-
import java.util.Arrays;
27-
import java.util.Collections;
28-
import java.util.List;
26+
import java.util.*;
2927
import java.util.logging.Logger;
3028

3129
/**
@@ -65,12 +63,23 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
6563
HttpServletRequest req = (HttpServletRequest) request;
6664
HttpServletResponse resp = (HttpServletResponse) response;
6765

68-
resp.addHeader("Access-Control-Allow-Credentials", "true");
69-
resp.addHeader("Access-Control-Allow-Origin", "https://observability.browserstack.com");
70-
resp.addHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT");
71-
resp.addHeader("Access-Control-Allow-Headers", "*");
72-
resp.addHeader("Access-Control-Expose-Headers", "*");
73-
resp.addHeader("Access-Control-Max-Age", "999");
66+
Set<String> allowedOrigins = new HashSet<String>(Arrays.asList(
67+
"https://observability.browserstack.com",
68+
"https://automation.browserstack.com",
69+
"https://automate.browserstack.com",
70+
"https://app-automate.browserstack.com",
71+
"https://test-management.browserstack.com"
72+
));
73+
74+
String origin = req.getHeader("Origin");
75+
if (origin != null && allowedOrigins.contains(origin)) {
76+
resp.addHeader("Access-Control-Allow-Credentials", "true");
77+
resp.addHeader("Access-Control-Allow-Origin", origin);
78+
resp.addHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT");
79+
resp.addHeader("Access-Control-Allow-Headers", "*");
80+
resp.addHeader("Access-Control-Expose-Headers", "*");
81+
resp.addHeader("Access-Control-Max-Age", "999");
82+
}
7483

7584
if (req.getMethod().equals(PREFLIGHT_REQUEST)) {
7685
resp.setStatus(200);

src/main/java/com/browserstack/automate/ci/jenkins/qualityDashboard/QualityDashboardInit.java

Lines changed: 79 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,29 @@
99
import hudson.Extension;
1010
import hudson.init.InitMilestone;
1111
import hudson.init.Initializer;
12+
import hudson.model.Job;
1213
import hudson.model.Result;
14+
import hudson.model.Run;
1315
import java.sql.Timestamp;
1416
import java.time.Instant;
17+
import java.io.Serializable;
1518
import jenkins.model.Jenkins;
1619
import okhttp3.*;
1720
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
18-
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
1921
import java.time.temporal.ChronoUnit;
2022
import java.io.IOException;
2123
import java.net.HttpURLConnection;
2224
import java.util.ArrayList;
2325
import java.util.List;
26+
import java.util.logging.Logger;
2427

2528
@Extension
2629
public 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

Comments
 (0)