Skip to content

Commit 4b2f799

Browse files
committed
intg 1575 adding support for showing bstack report in jenkins
1 parent f0f049d commit 4b2f799

File tree

7 files changed

+436
-0
lines changed

7 files changed

+436
-0
lines changed

src/main/java/com/browserstack/automate/ci/common/BrowserStackEnvVars.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ public interface BrowserStackEnvVars {
99
String BROWSERSTACK_LOCAL_IDENTIFIER = "BROWSERSTACK_LOCAL_IDENTIFIER";
1010
String BROWSERSTACK_BUILD = "BROWSERSTACK_BUILD";
1111
String BROWSERSTACK_BUILD_NAME = "BROWSERSTACK_BUILD_NAME";
12+
13+
String BROWSERSTACK_PROJECT_NAME = "BROWSERSTACK_PROJECT_NAME";
1214
String BROWSERSTACK_APP_ID = "BROWSERSTACK_APP_ID";
1315
String BROWSERSTACK_RERUN = "BROWSERSTACK_RERUN";
1416
String BROWSERSTACK_RERUN_TESTS = "BROWSERSTACK_RERUN_TESTS";

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ public class Constants {
1515
public static final String BROWSERSTACK_REPORT_PATH_PATTERN = "**/browserstack-artifacts/*";
1616
public static final String JENKINS_CI_PLUGIN = "JenkinsCiPlugin";
1717

18+
public static final String BROWSERSTACK_CONFIG_DETAILS_ENDPOINT = "http://localhost:3002/api/v1/external/ci/report-config";
19+
20+
public static final String REPORT_COMPLETED_STATUS = "COMPLETED";
21+
public static final String REPORT_IN_PROGRESS_STATUS = "IN_PROGRESS";
22+
1823
// Product
1924
public static final String AUTOMATE = "automate";
2025
public static final String APP_AUTOMATE = "app-automate";
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.browserstack.automate.ci.jenkins;
2+
3+
import com.browserstack.automate.ci.common.constants.Constants;
4+
import hudson.model.Run;
5+
import hudson.model.Action;
6+
7+
public abstract class AbstractBrowserStackReportFetcherForBuild implements Action {
8+
private Run<?, ?> run;
9+
10+
11+
@Override
12+
public String getIconFileName() {
13+
return Constants.BROWSERSTACK_LOGO;
14+
}
15+
16+
@Override
17+
public abstract String getDisplayName();
18+
19+
@Override
20+
public abstract String getUrlName();
21+
22+
public Run<?, ?> getBuild() {
23+
return run;
24+
}
25+
26+
public void setBuild(Run<?, ?> build) {
27+
this.run = build;
28+
}
29+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package com.browserstack.automate.ci.jenkins;
2+
3+
import com.browserstack.automate.ci.common.constants.Constants;
4+
import hudson.model.Run;
5+
import org.json.JSONObject;
6+
import okhttp3.*;
7+
8+
import java.io.PrintStream;
9+
10+
import static com.browserstack.automate.ci.common.logger.PluginLogger.log;
11+
import static com.browserstack.automate.ci.common.logger.PluginLogger.logError;
12+
13+
public class BrowserStackReportFetcherForBuild extends AbstractBrowserStackReportFetcherForBuild {
14+
15+
private BrowserStackCredentials credentials;
16+
private String reportUrl;
17+
private final String UUID;
18+
private String reportHtml;
19+
private final transient PrintStream logger;
20+
private String reportStyle;
21+
private String reportName;
22+
private String urlName;
23+
private static final String REPORT_IN_PROGRESS = "REPORT_IN_PROGRESS";
24+
private static final String REPORT_FAILED = "REPORT_FAILED";
25+
private static final OkHttpClient client = new OkHttpClient();
26+
makeRequestsUtil requestsUtil;
27+
28+
public BrowserStackReportFetcherForBuild(Run<?, ?> run, BrowserStackCredentials credentials, String reportUrl, String UUID, String reportName, String tabUrl, final PrintStream logger) {
29+
super();
30+
setBuild(run);
31+
this.credentials = credentials;
32+
this.reportUrl = reportUrl;
33+
this.UUID = UUID;
34+
this.reportHtml = null;
35+
this.reportStyle = "";
36+
this.logger = logger;
37+
this.reportName = reportName;
38+
this.urlName = tabUrl;
39+
requestsUtil = new makeRequestsUtil();
40+
}
41+
42+
43+
public String getReportHtml() {
44+
fetchReportConditions();
45+
return reportHtml;
46+
}
47+
public String getReportStyle() {
48+
fetchReportConditions();
49+
return reportStyle;
50+
}
51+
52+
private void fetchReportConditions() {
53+
if ( reportHtml==null || reportHtml.equals(REPORT_IN_PROGRESS) || reportHtml.equals(REPORT_FAILED)) {
54+
fetchReport();
55+
}
56+
}
57+
private void fetchReport() {
58+
String CIReportUrlWithParams = reportUrl + "?UUID=" + UUID + "&report_name=" + reportName;
59+
log(logger, "Fetching report from" + CIReportUrlWithParams);
60+
try {
61+
Response response = requestsUtil.makeRequest(CIReportUrlWithParams, credentials);
62+
if (response.isSuccessful()) {
63+
JSONObject reportResponse = new JSONObject(response.body().string());
64+
String reportStatus = reportResponse.optString("report_status");
65+
if(reportStatus.equalsIgnoreCase(Constants.REPORT_COMPLETED_STATUS)) {
66+
reportHtml = reportResponse.optString("report_html", null);
67+
reportStyle = reportResponse.optString("report_style", "");
68+
} else if(reportStatus.equalsIgnoreCase(Constants.REPORT_IN_PROGRESS_STATUS)){
69+
reportHtml = REPORT_IN_PROGRESS;
70+
} else {
71+
reportHtml = REPORT_FAILED;
72+
}
73+
logError(logger, "Received Non success response while fetching report" + response.code());
74+
}
75+
} catch (Exception e) {
76+
reportHtml = REPORT_FAILED;
77+
logError(logger, "Exception while fetching the report" + e.getMessage());
78+
}
79+
}
80+
81+
public boolean isReportInProgress() {
82+
return reportHtml.equals(REPORT_IN_PROGRESS);
83+
}
84+
85+
public boolean isReportFailed() {
86+
return reportHtml.equals(REPORT_FAILED);
87+
}
88+
89+
public boolean isReportAvailable() {
90+
if(reportHtml!=null && !reportHtml.equals(REPORT_IN_PROGRESS) && !reportHtml.equals(REPORT_FAILED)){
91+
return true;
92+
}
93+
return false;
94+
}
95+
96+
@Override
97+
public String getDisplayName() {
98+
return this.reportName;
99+
}
100+
101+
@Override
102+
public String getUrlName(){
103+
return this.urlName;
104+
}
105+
106+
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
package com.browserstack.automate.ci.jenkins;
2+
3+
import com.browserstack.automate.ci.common.BrowserStackEnvVars;
4+
import com.browserstack.automate.ci.common.constants.Constants;
5+
import edu.umd.cs.findbugs.annotations.NonNull;
6+
import hudson.EnvVars;
7+
import hudson.Extension;
8+
import hudson.model.AbstractProject;
9+
import hudson.model.Run;
10+
import hudson.model.TaskListener;
11+
import hudson.tasks.BuildStepDescriptor;
12+
import hudson.tasks.BuildStepMonitor;
13+
import hudson.tasks.Publisher;
14+
import hudson.tasks.Recorder;
15+
import hudson.FilePath;
16+
import hudson.Launcher;
17+
import jenkins.tasks.SimpleBuildStep;
18+
import org.json.JSONArray;
19+
import org.json.JSONObject;
20+
import okhttp3.*;
21+
import org.kohsuke.stapler.DataBoundConstructor;
22+
23+
import java.io.IOException;
24+
import java.io.PrintStream;
25+
import java.util.HashMap;
26+
import java.util.Map;
27+
import java.util.Optional;
28+
import java.util.concurrent.TimeUnit;
29+
import java.util.logging.Logger;
30+
31+
import static com.browserstack.automate.ci.common.logger.PluginLogger.log;
32+
import static com.browserstack.automate.ci.common.logger.PluginLogger.logError;
33+
34+
public class BrowserStackReportFetcherPublisher extends Recorder implements SimpleBuildStep {
35+
private static final Logger LOGGER = Logger.getLogger(BrowserStackReportFetcherPublisher.class.getName());
36+
private static final OkHttpClient client = new OkHttpClient();
37+
private static final int MAX_ATTEMPTS = 3;
38+
private static final int RETRY_DELAY_SECONDS = 10;
39+
private final Map<String, String> customEnvVars;
40+
41+
makeRequestsUtil requestsUtil;
42+
@DataBoundConstructor
43+
public BrowserStackReportFetcherPublisher(Map<String, String> customEnvVars) {
44+
this.customEnvVars = customEnvVars != null ? new HashMap<>(customEnvVars) : new HashMap<>();
45+
requestsUtil = new makeRequestsUtil();
46+
}
47+
48+
@Override
49+
public void perform(Run<?, ?> build, @NonNull FilePath workspace, @NonNull Launcher launcher, TaskListener listener) throws IOException, InterruptedException {
50+
final PrintStream logger = listener.getLogger();
51+
log(logger, "Adding BrowserStack Report");
52+
53+
EnvVars parentEnvs = build.getEnvironment(listener);
54+
parentEnvs.putAll(getCustomEnvVars());
55+
56+
String browserStackBuildName = Optional.ofNullable(parentEnvs.get(BrowserStackEnvVars.BROWSERSTACK_BUILD_NAME))
57+
.orElse(parentEnvs.get(Constants.JENKINS_BUILD_TAG));
58+
String projectName = parentEnvs.get(BrowserStackEnvVars.BROWSERSTACK_PROJECT_NAME);
59+
60+
BrowserStackBuildAction buildAction = build.getAction(BrowserStackBuildAction.class);
61+
if (buildAction == null || buildAction.getBrowserStackCredentials() == null) {
62+
logError(logger, "No BrowserStackBuildAction or credentials found");
63+
return;
64+
}
65+
66+
BrowserStackCredentials credentials = buildAction.getBrowserStackCredentials();
67+
68+
LOGGER.info("Adding BrowserStack Report Action");
69+
70+
JSONObject reportConfigDetailsResponse = fetchConfigDetails(logger, Constants.BROWSERSTACK_CONFIG_DETAILS_ENDPOINT, credentials);
71+
if (reportConfigDetailsResponse == null) {
72+
logError(logger, "Could not fetch the report config details");
73+
return;
74+
}
75+
76+
JSONArray configDetails = reportConfigDetailsResponse.getJSONArray("config_details");
77+
String lookUpURL = reportConfigDetailsResponse.getString("lookup_endpoint");
78+
79+
String UUID = fetchUUID(logger, lookUpURL, credentials, browserStackBuildName, projectName);
80+
if (UUID == null) {
81+
logError(logger, "Cannot find a build with name " + browserStackBuildName);
82+
return;
83+
}
84+
85+
for (int i = 0; i < configDetails.length(); i++) {
86+
JSONObject config = configDetails.getJSONObject(i);
87+
88+
String reportUrl = config.getString("report_url");
89+
String reportName = config.getString("report_name");
90+
String reportTabUrl = config.getString("report_tab_url");
91+
92+
build.addAction(new BrowserStackReportFetcherForBuild(build, credentials, reportUrl, UUID, reportName, reportTabUrl, logger));
93+
}
94+
95+
}
96+
97+
private String fetchUUID(PrintStream logger, String lookUpURL, BrowserStackCredentials credentials , String buildName, String projectName) throws InterruptedException {
98+
String lookUpURLWithParams = lookUpURL + "?build_name=" + buildName;
99+
if(projectName != null) {
100+
lookUpURLWithParams = lookUpURLWithParams + "&project_name" + projectName;
101+
}
102+
103+
log(logger, "Fetching build....");
104+
for (int attempts = 0; attempts < MAX_ATTEMPTS; attempts++) {
105+
try {
106+
Response response = requestsUtil.makeRequest(lookUpURLWithParams, credentials);
107+
if (response.isSuccessful()) {
108+
109+
JSONObject lookUpResponse = new JSONObject(response.body().string());
110+
String UUID = lookUpResponse.optString("UUID", null);
111+
112+
if(UUID !=null) {
113+
log(logger, "build found with "+ buildName + "and project name" + projectName);
114+
return UUID;
115+
}
116+
117+
LOGGER.info("build not found will retry in sometime.." + lookUpResponse.getString("message"));
118+
}
119+
} catch (Exception e) {
120+
logError(logger, "Attempt " + (attempts + 1) + " failed: " + e.getMessage());
121+
}
122+
TimeUnit.SECONDS.sleep(RETRY_DELAY_SECONDS);
123+
}
124+
125+
LOGGER.info("build not found even after "+ MAX_ATTEMPTS + "attempts");
126+
return null;
127+
}
128+
129+
private JSONObject fetchConfigDetails(PrintStream logger, String configDetailsURL, BrowserStackCredentials credentials) {
130+
log(logger, "Fetching config details for reports");
131+
try {
132+
Response response = requestsUtil.makeRequest(configDetailsURL, credentials);
133+
if (response.isSuccessful()) {
134+
return new JSONObject(response.body().string());
135+
}
136+
logError(logger, "Failed to fetch config details: " + response.code());
137+
} catch (Exception e) {
138+
logError(logger, "Exception occurred while fetching config details: " + e.getMessage());
139+
}
140+
return null;
141+
}
142+
143+
public Map<String, String> getCustomEnvVars() {
144+
return customEnvVars;
145+
}
146+
147+
@Override
148+
public BuildStepMonitor getRequiredMonitorService() {
149+
return BuildStepMonitor.NONE;
150+
}
151+
152+
@Extension
153+
public static final class DescriptorImpl extends BuildStepDescriptor<Publisher> {
154+
155+
@Override
156+
@SuppressWarnings("rawtypes")
157+
public boolean isApplicable(Class<? extends AbstractProject> aClass) {
158+
// indicates that this builder can be used with all kinds of project types
159+
return true;
160+
}
161+
162+
}
163+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.browserstack.automate.ci.jenkins;
2+
3+
import okhttp3.Credentials;
4+
import okhttp3.OkHttpClient;
5+
import okhttp3.Request;
6+
import okhttp3.Response;
7+
8+
import java.io.IOException;
9+
10+
public class makeRequestsUtil {
11+
OkHttpClient client = new OkHttpClient();
12+
13+
public Response makeRequest(String getUrl, BrowserStackCredentials browserStackCredentials) throws Exception{
14+
try {
15+
Request request = new Request.Builder()
16+
.url(getUrl)
17+
.header("Authorization", Credentials.basic(browserStackCredentials.getUsername(), browserStackCredentials.getDecryptedAccesskey()))
18+
.build();
19+
Response response = client.newCall(request).execute();
20+
return response;
21+
} catch(IOException e) {
22+
e.printStackTrace();
23+
throw e;
24+
}
25+
}
26+
}

0 commit comments

Comments
 (0)