Skip to content

Commit 3c0fec9

Browse files
committed
RCA data fetching by improving pagination handling
1 parent 8d21d7c commit 3c0fec9

File tree

4 files changed

+54
-59
lines changed

4 files changed

+54
-59
lines changed

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

Lines changed: 40 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,66 @@
11
import { TestStatus, FailedTestInfo, TestRun, TestDetails } from "./types.js";
22

3-
let hasNext = false;
4-
let nextPageUrl: string | null = null;
5-
63
export async function getTestIds(
74
buildId: string,
85
authString: string,
96
status?: TestStatus,
107
): Promise<FailedTestInfo[]> {
118
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();
9+
let url = status ? `${baseUrl}?test_statuses=${status}` : baseUrl;
2010
let allFailedTests: FailedTestInfo[] = [];
11+
let requestNumber = 0;
2112

2213
// Construct Basic auth header
2314
const encodedCredentials = Buffer.from(authString).toString("base64");
2415
const authHeader = `Basic ${encodedCredentials}`;
2516

2617
try {
27-
const response = await fetch(requestUrl, {
28-
headers: {
29-
Authorization: authHeader,
30-
"Content-Type": "application/json",
31-
},
32-
});
18+
while (true) {
19+
requestNumber++;
20+
21+
const response = await fetch(url, {
22+
headers: {
23+
Authorization: authHeader,
24+
"Content-Type": "application/json",
25+
},
26+
});
27+
28+
if (!response.ok) {
29+
throw new Error(
30+
`Failed to fetch test runs: ${response.status} ${response.statusText}`,
31+
);
32+
}
3333

34-
if (!response.ok) {
35-
throw new Error(
36-
`Failed to fetch test runs: ${response.status} ${response.statusText}`,
37-
);
38-
}
34+
const data = (await response.json()) as TestRun;
3935

40-
const data = (await response.json()) as TestRun;
36+
// Extract failed IDs from current page
37+
if (data.hierarchy && data.hierarchy.length > 0) {
38+
const currentFailedTests = extractFailedTestIds(data.hierarchy);
39+
allFailedTests = allFailedTests.concat(currentFailedTests);
40+
}
4141

42-
// Extract failed IDs from current page
43-
if (data.hierarchy && data.hierarchy.length > 0) {
44-
allFailedTests = extractFailedTestIds(data.hierarchy);
45-
}
42+
// Check for pagination termination conditions
43+
if (!data.pagination?.has_next || !data.pagination.next_page) {
44+
break;
45+
}
46+
47+
// Safety limit to prevent runaway requests
48+
if (requestNumber >= 5) {
49+
break;
50+
}
4651

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;
52+
// Prepare next request
53+
url = `${baseUrl}?next_page=${encodeURIComponent(data.pagination.next_page)}`;
54+
}
5355

54-
// Return failed test IDs from current page only
56+
// Return unique failed test IDs
5557
return allFailedTests;
5658
} catch (error) {
5759
console.error("Error fetching failed tests:", error);
5860
throw error;
5961
}
6062
}
6163

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-
7464
// Recursive function to extract failed test IDs from hierarchy
7565
function extractFailedTestIds(hierarchy: TestDetails[]): FailedTestInfo[] {
7666
let failedTests: FailedTestInfo[] = [];
@@ -80,7 +70,10 @@ function extractFailedTestIds(hierarchy: TestDetails[]): FailedTestInfo[] {
8070
if (node.details?.observability_url) {
8171
const idMatch = node.details.observability_url.match(/details=(\d+)/);
8272
if (idMatch) {
83-
failedTests.push({ id: idMatch[1] });
73+
failedTests.push({
74+
test_id: idMatch[1],
75+
test_name: node.display_name || `Test ${idMatch[1]}`,
76+
});
8477
}
8578
}
8679
}

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

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { FailedTestInfo } from "./types.js";
21
import { RCAState, RCATestCase, RCAResponse } from "./types.js";
32

43
interface ScanProgressContext {
@@ -76,19 +75,19 @@ async function updateProgress(
7675
}
7776

7877
async function fetchInitialRCA(
79-
testInfo: FailedTestInfo,
78+
testId: string,
8079
headers: Record<string, string>,
8180
baseUrl: string,
8281
): Promise<RCATestCase> {
83-
const url = baseUrl.replace("{testId}", testInfo.id);
82+
const url = baseUrl.replace("{testId}", testId);
8483

8584
try {
8685
const response = await fetch(url, { headers });
8786

8887
if (!response.ok) {
8988
return {
90-
id: testInfo.id,
91-
testRunId: testInfo.id,
89+
id: testId,
90+
testRunId: testId,
9291
state: RCAState.LOG_FETCH_ERROR,
9392
rcaData: {
9493
error: `HTTP ${response.status}: Failed to start RCA analysis`,
@@ -115,8 +114,8 @@ async function fetchInitialRCA(
115114
else resultState = RCAState.PENDING;
116115

117116
return {
118-
id: testInfo.id,
119-
testRunId: testInfo.id,
117+
id: testId,
118+
testRunId: testId,
120119
state: resultState,
121120
...(resultState === RCAState.COMPLETED && { rcaData: data }),
122121
...(isFailedState(resultState) &&
@@ -129,8 +128,8 @@ async function fetchInitialRCA(
129128
};
130129
} catch (error) {
131130
return {
132-
id: testInfo.id,
133-
testRunId: testInfo.id,
131+
id: testId,
132+
testRunId: testId,
134133
state: RCAState.LLM_SERVICE_ERROR,
135134
rcaData: {
136135
error:
@@ -267,7 +266,7 @@ export async function getRCAData(
267266
await notifyProgress(context, "Starting RCA analysis for test cases...", 0);
268267

269268
const testCases = await Promise.all(
270-
testIds.map((testId) => fetchInitialRCA({ id: testId }, headers, baseUrl)),
269+
testIds.map((testId) => fetchInitialRCA(testId, headers, baseUrl)),
271270
);
272271

273272
const inProgressCount = testCases.filter((tc) =>

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ export interface TestRun {
2121
}
2222

2323
export interface FailedTestInfo {
24-
id: string;
24+
test_id: string;
25+
test_name: string;
2526
}
2627

2728
export enum RCAState {

src/tools/rca-agent.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@ export default function addRCATools(
9898
{
9999
testId: z
100100
.array(z.string())
101-
.describe("Array of test IDs to fetch RCA data for"),
101+
.describe(
102+
"Array of test IDs to fetch RCA data for If not provided call listTestIds tool first to get the IDs",
103+
),
102104
},
103105
async (args) => {
104106
try {

0 commit comments

Comments
 (0)