1+ package com .browserstack .automate .ci .jenkins .integrationService ;
2+
3+ import com .browserstack .automate .ci .common .constants .Constants ;
4+ import com .browserstack .automate .ci .jenkins .BrowserStackCredentials ;
5+ import com .google .gson .Gson ;
6+ import hudson .EnvVars ;
7+ import hudson .FilePath ;
8+ import hudson .model .Action ;
9+ import hudson .model .Run ;
10+ import hudson .model .TaskListener ;
11+ import hudson .tasks .ArtifactArchiver ;
12+ import org .json .JSONObject ;
13+ import okhttp3 .*;
14+
15+ import java .io .IOException ;
16+ import java .io .PrintStream ;
17+ import java .util .Arrays ;
18+ import java .util .HashMap ;
19+ import java .util .Map ;
20+
21+ import static com .browserstack .automate .ci .common .logger .PluginLogger .log ;
22+ import static com .browserstack .automate .ci .common .logger .PluginLogger .logError ;
23+
24+ public class BrowserStackTestReportAction implements Action {
25+
26+ private static final String DEFAULT_REPORT_TIMEOUT = "120" ;
27+ private static final String SUCCESS_REPORT = "SUCCESS_REPORT" ;
28+ private static final String REPORT_IN_PROGRESS = "REPORT_IN_PROGRESS" ;
29+ private static final String REPORT_FAILED = "REPORT_FAILED" ;
30+ private static final String RETRY_REPORT = "RETRY_REPORT" ;
31+ private static final String RATE_LIMIT = "RATE_LIMIT" ;
32+ private static final String TEST_AVAILABLE = "TEST_AVAILABLE" ;
33+ private static final int MAX_ATTEMPTS = 3 ;
34+
35+ private final transient PrintStream logger ;
36+ private final RequestsUtil requestsUtil ;
37+ private final BrowserStackCredentials credentials ;
38+ private final String buildName ;
39+ private final String buildCreatedAt ;
40+
41+ private Run <?, ?> run ;
42+ private String reportHtml ;
43+ private String reportStyle ;
44+ private String reportStatus ;
45+ private int maxRetryReportAttempt ;
46+
47+ public BrowserStackTestReportAction (Run <?, ?> run , BrowserStackCredentials credentials , String buildName , String buildCreatedAt , final PrintStream logger ) {
48+ this .run = run ;
49+ this .credentials = credentials ;
50+ this .buildName = buildName ;
51+ this .buildCreatedAt = buildCreatedAt ;
52+ this .logger = logger ;
53+ this .requestsUtil = new RequestsUtil ();
54+ this .reportHtml = null ;
55+ this .reportStyle = "" ;
56+ this .reportStatus = "" ;
57+ this .maxRetryReportAttempt = MAX_ATTEMPTS ;
58+ }
59+
60+ public String getReportHtml () {
61+ ensureReportFetched ();
62+ return reportHtml ;
63+ }
64+
65+ public String getReportStyle () {
66+ ensureReportFetched ();
67+ return reportStyle ;
68+ }
69+
70+ private void ensureReportFetched () {
71+ if (!isReportCompletedOrFailed ()) {
72+ fetchReport ();
73+ }
74+ }
75+
76+ private boolean isReportCompletedOrFailed () {
77+ return reportStatus .equals (SUCCESS_REPORT ) || reportStatus .equals (REPORT_FAILED );
78+ }
79+
80+ private void fetchReport () {
81+ Map <String , Object > params = createReportParams ();
82+ String reportUrl = Constants .CAD_BASE_URL + Constants .BROWSERSTACK_CONFIG_DETAILS_ENDPOINT ;
83+
84+ try {
85+ log (logger , "Fetching BrowserStack report..." );
86+ Response response = requestsUtil .makeRequest (reportUrl , credentials , createRequestBody (params ));
87+ handleResponse (response );
88+ } catch (Exception e ) {
89+ if (!isReportTestAvailable ()) {
90+ handleFetchException (e );
91+ }
92+ }
93+ }
94+
95+ private Map <String , Object > createReportParams () {
96+ String RquestTypeForJenkins = "POLL" ;
97+ Map <String , Object > params = new HashMap <>();
98+ params .put ("buildStartedAt" , buildCreatedAt );
99+ params .put ("originalBuildName" , buildName );
100+ params .put ("requestingCi" , Constants .INTEGRATIONS_TOOL_KEY );
101+ params .put ("reportFormat" , Arrays .asList ("richHtml" , "basicHtml" ));
102+ params .put ("requestType" , RquestTypeForJenkins );
103+ params .put ("userTimeout" , DEFAULT_REPORT_TIMEOUT );
104+ return params ;
105+ }
106+
107+ private RequestBody createRequestBody (Map <String , Object > params ) {
108+ Gson gson = new Gson ();
109+ String json = gson .toJson (params );
110+ return RequestBody .create (MediaType .parse ("application/json" ), json );
111+ }
112+
113+ private void handleResponse (Response response ) throws Exception {
114+ if (response .isSuccessful ()) {
115+ processSuccessfulResponse (response );
116+ } else {
117+ if (!isReportTestAvailable ()) {
118+ if (response .code () == 429 ) {
119+ reportStatus = RATE_LIMIT ;
120+ } else {
121+ reportStatus = REPORT_FAILED ;
122+ logError (logger , "Non-success response while fetching report: " + response .code ());
123+ }
124+ }
125+ }
126+ }
127+
128+ private void processSuccessfulResponse (Response response ) throws Exception {
129+ assert response .body () != null ;
130+ JSONObject reportResponse = new JSONObject (response .body ().string ());
131+ String responseReportStatus = reportResponse .optString ("reportStatus" );
132+ JSONObject report = reportResponse .optJSONObject ("report" );
133+
134+ switch (responseReportStatus .toUpperCase ()) {
135+ case "COMPLETED" :
136+ case "NOT_AVAILABLE" :
137+ setReportSuccess (report );
138+ break ;
139+ case "IN_PROGRESS" :
140+ reportStatus = REPORT_IN_PROGRESS ;
141+ break ;
142+ case "TEST_AVAILABLE" :
143+ setReportSuccess (report );
144+ reportStatus = TEST_AVAILABLE ;
145+ break ;
146+ default :
147+ reportStatus = REPORT_FAILED ;
148+ }
149+ }
150+
151+ private void setReportSuccess (JSONObject report ) {
152+ String defaultHTML = "<h1>No Report Found</h1>" ;
153+ reportStatus = SUCCESS_REPORT ;
154+ reportHtml = report != null ? report .optString ("richHtml" , defaultHTML ) : defaultHTML ;
155+ reportStyle = report != null ? report .optString ("richCss" , "" ) : "" ;
156+
157+ try {
158+ String basicHtml = report != null ? report .optString ("basicHtml" , defaultHTML ) : defaultHTML ;
159+ String fullHtml = "<!DOCTYPE html> <html><head> <head>" + basicHtml + "</html>" ;
160+
161+ // Save the HTML content to a file in the workspace
162+ FilePath workspace = new FilePath (run .getRootDir ()).getParent ();
163+ ArtifactArchiver artifactArchiver = getArtifactArchiver (workspace , fullHtml );
164+ artifactArchiver .perform (run , workspace , new EnvVars (), null , TaskListener .NULL );
165+ } catch (Exception e ) {
166+ logError (logger , "Failed to save or archive report artifact: " + e .getMessage ());
167+ }
168+ }
169+
170+ private static ArtifactArchiver getArtifactArchiver (FilePath workspace , String fullHtml ) throws IOException , InterruptedException {
171+ FilePath artifactsDir = new FilePath (workspace , Constants .BROWSERSTACK_REPORT_FOLDER );
172+ artifactsDir .mkdirs ();
173+ String htmlFileName = Constants .BROWSERSTACK_REPORT_FILENAME + ".html" ;
174+
175+ FilePath htmlFile = new FilePath (artifactsDir , htmlFileName );
176+
177+ htmlFile .write (fullHtml , "UTF-8" );
178+ String artifactFilePath = Constants .BROWSERSTACK_REPORT_FOLDER + "/" + htmlFileName ;
179+ // Archive the file as an artifact
180+ ArtifactArchiver artifactArchiver = new ArtifactArchiver (artifactFilePath );
181+ artifactArchiver .setAllowEmptyArchive (false );
182+ return artifactArchiver ;
183+ }
184+
185+ private void handleFetchException (Exception e ) {
186+ reportStatus = RETRY_REPORT ;
187+ maxRetryReportAttempt --;
188+ if (maxRetryReportAttempt < 0 ) {
189+ reportStatus = REPORT_FAILED ;
190+ }
191+ logError (logger , "Exception while fetching the report: " + Arrays .toString (e .getStackTrace ()));
192+ }
193+
194+ public boolean isReportInProgress () {
195+ return reportStatus .equals (REPORT_IN_PROGRESS );
196+ }
197+
198+ public boolean isReportFailed () {
199+ return reportStatus .equals (REPORT_FAILED );
200+ }
201+
202+ public boolean reportRetryRequired () {
203+ return reportStatus .equals (RETRY_REPORT );
204+ }
205+
206+ public boolean isUserRateLimited () {
207+ return reportStatus .equals (RATE_LIMIT );
208+ }
209+
210+ public boolean isReportAvailable () {
211+ return reportStatus .equals (SUCCESS_REPORT ) || reportStatus .equals (TEST_AVAILABLE );
212+ }
213+
214+ public boolean isReportTestAvailable () {
215+ return reportStatus .equals (TEST_AVAILABLE );
216+ }
217+
218+ public boolean reportHasStatus () {
219+ return !reportStatus .isEmpty () && (reportStatus .equals (REPORT_IN_PROGRESS ) || reportStatus .equals (REPORT_FAILED ));
220+ }
221+
222+ public Run <?, ?> getBuild () {
223+ return run ;
224+ }
225+
226+ public void setBuild (Run <?, ?> build ) {
227+ this .run = build ;
228+ }
229+
230+ @ Override
231+ public String getIconFileName () {
232+ return Constants .BROWSERSTACK_LOGO ;
233+ }
234+
235+ @ Override
236+ public String getDisplayName () {
237+ return Constants .BROWSERSTACK_REPORT_DISPLAY_NAME ;
238+ }
239+
240+ @ Override
241+ public String getUrlName () {
242+ return Constants .BROWSERSTACK_TEST_REPORT_URL ;
243+ }
244+ }
0 commit comments