From f5d993d12de49688a88f501240ea0314f667aa34 Mon Sep 17 00:00:00 2001 From: Jaydeep Dave Date: Thu, 20 Aug 2026 17:34:31 +0530 Subject: [PATCH 1/2] fix(rca): use canonical observability_url for the view-report link MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit QA: the terminal report URL used the build-UUID form (/builds/?tab=ai_report&subTab=tfa), which 302-redirects to the canonical dashboard path and the redirect drops the query string — so the dashboard loads the default sub-tab, not TFA, forcing a manual switch. Resolve viewReport to the build's canonical observability_url (projects//builds//) + tab=ai_report&subTab=tfa via one GET /ext/v1/builds/{uuid} (trigger response is a lean ack with no URL); fall back to the UUID form only if that read fails. Also align subTab to tfa across the UUID fallback + RESOLVED-turn guidance. --- src/tools/tfa-rca-utils/constants.ts | 26 ++++++++++++------ src/tools/tfa-rca-utils/trigger-report.ts | 32 ++++++++++++++++++++++- tests/tools/triggerRcaReport.test.ts | 15 +++++++++-- 3 files changed, 62 insertions(+), 11 deletions(-) diff --git a/src/tools/tfa-rca-utils/constants.ts b/src/tools/tfa-rca-utils/constants.ts index c913508..7851baa 100644 --- a/src/tools/tfa-rca-utils/constants.ts +++ b/src/tools/tfa-rca-utils/constants.ts @@ -23,15 +23,25 @@ export function getO11yUiBaseUrl(): string { } /** - * TRA UI deep-link for a build's AI report (confirmed shape, 2026-07-13): - * `/builds/?tab=ai_report&subTab=aitfa` — the AI-TFA - * sub-tab of the build's AI report. `{buildUuid}` is replaced with the - * caller-supplied build id. + * Query that deep-links a dashboard build URL to the AI-report TFA sub-tab. + * Appended to the build's canonical `observability_url` (preferred) or the + * UUID fallback below. */ -export const O11Y_UI_BUILD_PATH = - "/builds/{buildUuid}?tab=ai_report&subTab=aitfa"; +export const AI_REPORT_TFA_QUERY = "tab=ai_report&subTab=tfa"; -/** Human-facing TRA UI link for one build's full report. */ +/** Read a build's metadata — carries the canonical `observability_url`. */ +export const BUILD_DETAILS_PATH = "/ext/v1/builds/{buildUuid}"; + +/** + * UUID-form deep-link — `/builds/?tab=ai_report&subTab=tfa`. + * Fallback only, used when the build's canonical `observability_url` can't be + * read. Preferred is the canonical `observability_url` + `AI_REPORT_TFA_QUERY` + * (see `trigger-report.ts`): the UUID URL 302-redirects to the canonical path + * and the redirect drops the query string, landing on the wrong sub-tab. + */ +export const O11Y_UI_BUILD_PATH = `/builds/{buildUuid}?${AI_REPORT_TFA_QUERY}`; + +/** Human-facing TRA UI link for one build's full report (UUID fallback form). */ export function getO11yUiBuildUrl(buildUuid: string): string { return ( getO11yUiBaseUrl() + @@ -45,7 +55,7 @@ export function getO11yUiBuildUrl(buildUuid: string): string { * (build page → AI report → AI TFA sub-tab). */ export function getRcaViewGuidance(): string { - return `${getO11yUiBaseUrl()} — open the build's AI report (tab=ai_report, subTab=aitfa) to view the full RCA`; + return `${getO11yUiBaseUrl()} — open the build's AI report (tab=ai_report, subTab=tfa) to view the full RCA`; } /** Trigger (or read, when already complete) a build's Release Readiness report. */ diff --git a/src/tools/tfa-rca-utils/trigger-report.ts b/src/tools/tfa-rca-utils/trigger-report.ts index dd013c7..bb645b5 100644 --- a/src/tools/tfa-rca-utils/trigger-report.ts +++ b/src/tools/tfa-rca-utils/trigger-report.ts @@ -2,6 +2,8 @@ import { apiClient } from "../../lib/apiClient.js"; import { getBrowserStackAuth } from "../../lib/get-auth.js"; import { BrowserStackConfig } from "../../lib/types.js"; import { + AI_REPORT_TFA_QUERY, + BUILD_DETAILS_PATH, getO11yBaseUrl, getO11yUiBuildUrl, RELEASE_READINESS_TRIGGER_PATH, @@ -76,6 +78,34 @@ function mapTriggerError(status: number, data: unknown): TriggerRcaReportError { * a build via the o11y external API, returning a trimmed glimpse. Stateless: * nothing persists between calls. */ +/** + * Resolve the human-facing "view report" link to the build's canonical + * `observability_url` (`.../projects//builds//`) + the AI-report + * TFA sub-tab. The UUID form 302-redirects and the redirect drops the query + * string, so the dashboard lands on the wrong sub-tab (QA-reported). The + * trigger response is a lean ack with no `observability_url`, so read it from + * build metadata; fall back to the UUID deep-link only if that read fails. + */ +async function resolveViewReport( + buildUuid: string, + headers: Record, +): Promise { + try { + const url = + getO11yBaseUrl() + + BUILD_DETAILS_PATH.replace("{buildUuid}", encodeURIComponent(buildUuid)); + const resp = await apiClient.get({ url, headers, raise_error: false }); + const observabilityUrl = + resp.ok && typeof resp.data?.observability_url === "string" + ? resp.data.observability_url + : undefined; + if (observabilityUrl) return `${observabilityUrl}?${AI_REPORT_TFA_QUERY}`; + } catch { + // fall through to the UUID deep-link + } + return getO11yUiBuildUrl(buildUuid); +} + export async function triggerRcaReport( args: TriggerRcaReportArgs, config: BrowserStackConfig, @@ -119,6 +149,6 @@ export async function triggerRcaReport( totalPrs: summary.totalPrs, faultyPrNumbers: summary.faultyPrNumbers, failureReason: summary.failureReason, - viewReport: getO11yUiBuildUrl(args.buildUuid), + viewReport: await resolveViewReport(args.buildUuid, headers), }; } diff --git a/tests/tools/triggerRcaReport.test.ts b/tests/tools/triggerRcaReport.test.ts index 19201f2..d7d0b8e 100644 --- a/tests/tools/triggerRcaReport.test.ts +++ b/tests/tools/triggerRcaReport.test.ts @@ -36,6 +36,7 @@ const mockConfig = { }; const post = apiClient.post as Mock; +const get = apiClient.get as Mock; function ok(data: any, status = 200) { return { ok: true, status, data }; @@ -72,6 +73,10 @@ describe("triggerRcaReportTool", () => { it("success → trimmed glimpse with UI link; prs/workflows never echoed", async () => { post.mockResolvedValue(fullReport()); + // Canonical dashboard URL comes from the build's observability_url. + get.mockResolvedValue( + ok({ observability_url: `${UI_BASE}/projects/P+Name/builds/bname/1` }), + ); const result = await triggerRcaReportTool( { buildUuid: "b-1" }, @@ -91,7 +96,9 @@ describe("triggerRcaReportTool", () => { expect(payload.failureReason).toBe( "2 product regressions traced to PR #412", ); - expect(payload.viewReport).toBe(`${UI_BASE}/builds/b-1?tab=ai_report&subTab=aitfa`); + expect(payload.viewReport).toBe( + `${UI_BASE}/projects/P+Name/builds/bname/1?tab=ai_report&subTab=tfa`, + ); // Raw response is never echoed: no prs[]/workflows[] entries, no envelope. expect(payload.prs).toBeUndefined(); expect(payload.workflows).toBeUndefined(); @@ -132,6 +139,8 @@ describe("triggerRcaReportTool", () => { post.mockResolvedValue( ok({ state: "running", buildUuid: "b-2", triggeredAt: "now" }), ); + // Build metadata unavailable → fall back to the UUID deep-link (still subTab=tfa). + get.mockResolvedValue(nonOk(404)); const result = await triggerRcaReportTool( { buildUuid: "b-2" }, @@ -140,7 +149,9 @@ describe("triggerRcaReportTool", () => { const payload = JSON.parse(result.content[0].text as string); expect(payload.state).toBe("running"); expect(payload.verdict).toBeUndefined(); - expect(payload.viewReport).toBe(`${UI_BASE}/builds/b-2?tab=ai_report&subTab=aitfa`); + expect(payload.viewReport).toBe( + `${UI_BASE}/builds/b-2?tab=ai_report&subTab=tfa`, + ); }); it("403 plan/flag fence → clear domain error", async () => { From a0adb8f8f3d7c160cc9b98cf998cd68276b22d94 Mon Sep 17 00:00:00 2001 From: Jaydeep Dave Date: Thu, 20 Aug 2026 17:39:15 +0530 Subject: [PATCH 2/2] docs(rca): trim verbose comments on view-report URL resolution --- src/tools/tfa-rca-utils/constants.ts | 14 ++++---------- src/tools/tfa-rca-utils/trigger-report.ts | 10 ++++------ 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/tools/tfa-rca-utils/constants.ts b/src/tools/tfa-rca-utils/constants.ts index 7851baa..3ed100e 100644 --- a/src/tools/tfa-rca-utils/constants.ts +++ b/src/tools/tfa-rca-utils/constants.ts @@ -22,22 +22,16 @@ export function getO11yUiBaseUrl(): string { return appConfig.BROWSERSTACK_O11Y_UI_BASE_URL; } -/** - * Query that deep-links a dashboard build URL to the AI-report TFA sub-tab. - * Appended to the build's canonical `observability_url` (preferred) or the - * UUID fallback below. - */ +/** Query that deep-links a dashboard build URL to the AI-report TFA sub-tab. */ export const AI_REPORT_TFA_QUERY = "tab=ai_report&subTab=tfa"; /** Read a build's metadata — carries the canonical `observability_url`. */ export const BUILD_DETAILS_PATH = "/ext/v1/builds/{buildUuid}"; /** - * UUID-form deep-link — `/builds/?tab=ai_report&subTab=tfa`. - * Fallback only, used when the build's canonical `observability_url` can't be - * read. Preferred is the canonical `observability_url` + `AI_REPORT_TFA_QUERY` - * (see `trigger-report.ts`): the UUID URL 302-redirects to the canonical path - * and the redirect drops the query string, landing on the wrong sub-tab. + * UUID-form deep-link — fallback when the canonical `observability_url` can't + * be read. The UUID URL 302-redirects and the redirect drops the query string, + * so prefer the canonical URL (see `trigger-report.ts`). */ export const O11Y_UI_BUILD_PATH = `/builds/{buildUuid}?${AI_REPORT_TFA_QUERY}`; diff --git a/src/tools/tfa-rca-utils/trigger-report.ts b/src/tools/tfa-rca-utils/trigger-report.ts index bb645b5..7f976c8 100644 --- a/src/tools/tfa-rca-utils/trigger-report.ts +++ b/src/tools/tfa-rca-utils/trigger-report.ts @@ -79,12 +79,10 @@ function mapTriggerError(status: number, data: unknown): TriggerRcaReportError { * nothing persists between calls. */ /** - * Resolve the human-facing "view report" link to the build's canonical - * `observability_url` (`.../projects//builds//`) + the AI-report - * TFA sub-tab. The UUID form 302-redirects and the redirect drops the query - * string, so the dashboard lands on the wrong sub-tab (QA-reported). The - * trigger response is a lean ack with no `observability_url`, so read it from - * build metadata; fall back to the UUID deep-link only if that read fails. + * "View report" link = the build's canonical `observability_url` + the TFA + * sub-tab. The UUID form 302-redirects and drops the query, loading the wrong + * sub-tab; the trigger response carries no URL, so read it from build metadata + * and fall back to the UUID deep-link only if that read fails. */ async function resolveViewReport( buildUuid: string,