Skip to content

Commit 8d21d7c

Browse files
committed
Refactor RCA tools and add new types for improved functionality and readability
1 parent 8c3a224 commit 8d21d7c

File tree

5 files changed

+317
-180
lines changed

5 files changed

+317
-180
lines changed

src/tools/rca-agent-utils/format-rca.ts

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,47 @@
1+
import logger from "../../logger.js";
2+
13
// Utility function to format RCA data for better readability
24
export function formatRCAData(rcaData: any): string {
5+
logger.info(
6+
`Formatting RCA data for output: ${JSON.stringify(rcaData, null, 2)}`,
7+
);
38
if (!rcaData || !rcaData.testCases || rcaData.testCases.length === 0) {
49
return "No RCA data available.";
510
}
611

712
let output = "## Root Cause Analysis Report\n\n";
8-
13+
914
rcaData.testCases.forEach((testCase: any, index: number) => {
10-
// Show test case name first with smaller heading
11-
output += `### ${testCase.displayName || `Test Case ${index + 1}`}\n`;
15+
// Show test case ID with smaller heading
16+
output += `### Test Case ${index + 1}\n`;
1217
output += `**Test ID:** ${testCase.id}\n`;
1318
output += `**Status:** ${testCase.state}\n\n`;
1419

15-
if (testCase.rcaData?.originalResponse?.rcaData) {
16-
const rca = testCase.rcaData.originalResponse.rcaData;
17-
20+
// Access RCA data from the correct path
21+
const rca = testCase.rcaData?.rcaData;
22+
23+
if (rca) {
1824
if (rca.root_cause) {
1925
output += `**Root Cause:** ${rca.root_cause}\n\n`;
2026
}
21-
27+
28+
if (rca.failure_type) {
29+
output += `**Failure Type:** ${rca.failure_type}\n\n`;
30+
}
31+
2232
if (rca.description) {
23-
output += `**Description:**\n${rca.description}\n\n`;
33+
output += `**Detailed Analysis:**\n${rca.description}\n\n`;
2434
}
25-
35+
2636
if (rca.possible_fix) {
2737
output += `**Recommended Fix:**\n${rca.possible_fix}\n\n`;
2838
}
39+
} else if (testCase.rcaData?.error) {
40+
output += `**Error:** ${testCase.rcaData.error}\n\n`;
41+
} else if (testCase.state === "failed") {
42+
output += `**Note:** RCA analysis failed or is not available for this test case.\n\n`;
2943
}
30-
44+
3145
output += "---\n\n";
3246
});
3347

src/tools/rca-agent-utils/get-failed-test-id.ts

Lines changed: 51 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,76 @@
1-
interface TestDetails {
2-
status: string;
3-
details: any;
4-
children?: TestDetails[];
5-
display_name?: string;
6-
}
1+
import { TestStatus, FailedTestInfo, TestRun, TestDetails } from "./types.js";
72

8-
interface TestRun {
9-
hierarchy: TestDetails[];
10-
pagination?: {
11-
has_next: boolean;
12-
next_page: string | null;
13-
};
14-
}
3+
let hasNext = false;
4+
let nextPageUrl: string | null = null;
155

16-
export interface FailedTestInfo {
17-
id: string;
18-
displayName: string;
19-
}
20-
21-
export async function getFailedTestIds(
6+
export async function getTestIds(
227
buildId: string,
238
authString: string,
9+
status?: TestStatus,
2410
): Promise<FailedTestInfo[]> {
25-
const baseUrl = `https://api-automation.browserstack.com/ext/v1/builds/${buildId}/testRuns?test_statuses=failed`;
26-
let nextUrl = baseUrl;
11+
const baseUrl = `https://api-automation.browserstack.com/ext/v1/builds/${buildId}/testRuns`;
12+
13+
// Build initial URL
14+
const initialUrl = new URL(baseUrl);
15+
if (status) initialUrl.searchParams.set("test_statuses", status);
16+
17+
// Use stored nextPageUrl if available, otherwise fresh URL
18+
const requestUrl =
19+
hasNext && nextPageUrl ? nextPageUrl : initialUrl.toString();
2720
let allFailedTests: FailedTestInfo[] = [];
28-
let requestNumber = 0;
2921

3022
// Construct Basic auth header
3123
const encodedCredentials = Buffer.from(authString).toString("base64");
3224
const authHeader = `Basic ${encodedCredentials}`;
3325

3426
try {
35-
while (true) {
36-
requestNumber++;
37-
38-
const response = await fetch(nextUrl, {
39-
headers: {
40-
Authorization: authHeader,
41-
"Content-Type": "application/json",
42-
},
43-
});
44-
45-
if (!response.ok) {
46-
throw new Error(
47-
`Failed to fetch test runs: ${response.status} ${response.statusText}`,
48-
);
49-
}
50-
51-
const data = (await response.json()) as TestRun;
52-
53-
// Extract failed IDs from current page
54-
if (data.hierarchy && data.hierarchy.length > 0) {
55-
const currentFailedTests = extractFailedTestIds(data.hierarchy);
56-
allFailedTests = allFailedTests.concat(currentFailedTests);
57-
}
58-
59-
// Check for pagination termination conditions
60-
if (!data.pagination?.has_next || !data.pagination.next_page) {
61-
break;
62-
}
27+
const response = await fetch(requestUrl, {
28+
headers: {
29+
Authorization: authHeader,
30+
"Content-Type": "application/json",
31+
},
32+
});
33+
34+
if (!response.ok) {
35+
throw new Error(
36+
`Failed to fetch test runs: ${response.status} ${response.statusText}`,
37+
);
38+
}
6339

64-
// Safety limit to prevent runaway requests
65-
if (requestNumber >= 5) {
66-
break;
67-
}
40+
const data = (await response.json()) as TestRun;
6841

69-
// Prepare next request
70-
nextUrl = `${baseUrl}?next_page=${encodeURIComponent(data.pagination.next_page)}`;
42+
// Extract failed IDs from current page
43+
if (data.hierarchy && data.hierarchy.length > 0) {
44+
allFailedTests = extractFailedTestIds(data.hierarchy);
7145
}
7246

73-
// Return unique failed test IDs
47+
// Update pagination state in memory
48+
hasNext = data.pagination?.has_next || false;
49+
nextPageUrl =
50+
hasNext && data.pagination?.next_page
51+
? buildNextPageUrl(baseUrl, status, data.pagination.next_page)
52+
: null;
53+
54+
// Return failed test IDs from current page only
7455
return allFailedTests;
7556
} catch (error) {
7657
console.error("Error fetching failed tests:", error);
7758
throw error;
7859
}
7960
}
8061

62+
// Helper to build next page URL safely
63+
function buildNextPageUrl(
64+
baseUrl: string,
65+
status: TestStatus | undefined,
66+
nextPage: string,
67+
): string {
68+
const url = new URL(baseUrl);
69+
if (status) url.searchParams.set("test_statuses", status);
70+
url.searchParams.set("next_page", nextPage);
71+
return url.toString();
72+
}
73+
8174
// Recursive function to extract failed test IDs from hierarchy
8275
function extractFailedTestIds(hierarchy: TestDetails[]): FailedTestInfo[] {
8376
let failedTests: FailedTestInfo[] = [];
@@ -87,10 +80,7 @@ function extractFailedTestIds(hierarchy: TestDetails[]): FailedTestInfo[] {
8780
if (node.details?.observability_url) {
8881
const idMatch = node.details.observability_url.match(/details=(\d+)/);
8982
if (idMatch) {
90-
failedTests.push({
91-
id: idMatch[1],
92-
displayName: node.display_name || `Test ${idMatch[1]}`
93-
});
83+
failedTests.push({ id: idMatch[1] });
9484
}
9585
}
9686
}

0 commit comments

Comments
 (0)