diff --git a/.github/extensions/agentic-workflows-dashboard/app.js b/.github/extensions/agentic-workflows-dashboard/app.js index 4d022ea481a..d55f0766476 100644 --- a/.github/extensions/agentic-workflows-dashboard/app.js +++ b/.github/extensions/agentic-workflows-dashboard/app.js @@ -11,7 +11,7 @@ function combineOutput(stdout, stderr) { } function spawnExecFile(file, args, options, callback) { const { env, cwd, maxBuffer = 10 * 1024 * 1024 } = options ?? {}; - const spawnOptions = { env, cwd, stdio: ["ignore", "pipe", "pipe"], windowsHide: true }; + const spawnOptions = { env, cwd, stdio: ["ignore", "pipe", "pipe"], windowsHide: true, detached: true }; console.error(`${LOG} spawn file=${file} args=${JSON.stringify(args)} cwd=${cwd}`); const proc = spawn(file, args, spawnOptions); const stdoutChunks = []; @@ -735,8 +735,8 @@ function createDashboardDataAccess({ runGhAw, cacheTTL = CACHE_TTL_MS, logsOutpu const output = await runGhAw(args); return { command: rawCmd, output }; } catch (err) { - const error = err; - const msg = error.stderr || error.message || "Unknown error"; + const e = asError(err); + const msg = e.stderr || e.message || "Unknown error"; console.error(`${LOG2} execCommand error cmd="${rawCmd}": ${msg}`); return { command: rawCmd, output: msg, error: true }; } diff --git a/.github/extensions/agentic-workflows-dashboard/extension.mjs b/.github/extensions/agentic-workflows-dashboard/extension.mjs index 204409279df..819fb4636d5 100644 --- a/.github/extensions/agentic-workflows-dashboard/extension.mjs +++ b/.github/extensions/agentic-workflows-dashboard/extension.mjs @@ -1,10 +1,13 @@ import { createServer } from "node:http"; import { readFile } from "node:fs/promises"; -import { execFile } from "node:child_process/promises"; +import { execFile as execFileCb } from "node:child_process"; +import { promisify } from "node:util"; import { dirname, join, resolve } from "node:path"; import { homedir } from "node:os"; import { fileURLToPath } from "node:url"; +const execFile = promisify(execFileCb); + import { createCanvas, joinSession } from "@github/copilot-sdk/extension"; import { createGhAwRunnerWithStatus, DEFAULT_LOG_TIMEOUT_MINUTES, DEFAULT_RUN_COUNT, createDashboardDataAccess } from "./app.js"; @@ -109,6 +112,7 @@ async function startServer() { count: parseInt(reqUrl.searchParams.get("count") ?? String(DEFAULT_RUN_COUNT), 10), window: reqUrl.searchParams.get("window") ?? "7d", timeout: parseInt(reqUrl.searchParams.get("timeout") ?? String(DEFAULT_LOG_TIMEOUT_MINUTES), 10), + workflowName: reqUrl.searchParams.get("workflow_name") ?? "", }) ); } else if (pathname === "/api/usage") { diff --git a/.github/extensions/agentic-workflows-dashboard/src/app.ts b/.github/extensions/agentic-workflows-dashboard/src/app.ts index fa26bdc47dd..81772676b8b 100644 --- a/.github/extensions/agentic-workflows-dashboard/src/app.ts +++ b/.github/extensions/agentic-workflows-dashboard/src/app.ts @@ -22,6 +22,7 @@ interface DashboardState { reportWindows: ReportWindow[]; activeTab: DashboardTabId; selectedWindow: ReportWindow["id"]; + selectedWorkflowFilter: string; logsTimeout: number; pageSize: number; cliStatus: CLIStatus | null; @@ -76,6 +77,7 @@ interface DashboardState { loadExperimentPage(page: number): void; selectRun(runId: number): void; viewRunDetails(runId: number): void; + selectWorkflowFilter(workflowName: string): Promise; loadAudit(): Promise; clearAudit(): void; buildLogsCommand(count?: number): string; @@ -212,6 +214,7 @@ Alpine.data("dashboardApp", (): DashboardState => ({ reportWindows, activeTab: "definitions", selectedWindow: "7d", + selectedWorkflowFilter: "", logsTimeout: 1, pageSize: 20, cliStatus: null, @@ -252,7 +255,7 @@ Alpine.data("dashboardApp", (): DashboardState => ({ this.commandInput = this.buildLogsCommand(); await this.fetchCliStatus(); if (this.cliStatus?.available) { - await Promise.all([this.fetchDefinitions(), this.fetchRuns(), this.fetchUsage(), this.fetchExperiments()]); + await Promise.all([this.fetchDefinitions(), this.fetchUsage(), this.fetchExperiments()]); this.commandOutput = `$ ${this.cliStatus.command}\ngh aw version ${this.cliStatus.version}`; return; } @@ -278,6 +281,13 @@ Alpine.data("dashboardApp", (): DashboardState => ({ await Promise.all([this.fetchRuns(), this.fetchUsage()]); }, + async selectWorkflowFilter(workflowName) { + this.selectedWorkflowFilter = workflowName; + this.commandInput = this.buildLogsCommand(); + if (!this.cliStatus?.available) return; + await this.fetchRuns(); + }, + async fetchCliStatus() { this.loadingCliStatus = true; this.errorCliStatus = ""; @@ -305,6 +315,13 @@ Alpine.data("dashboardApp", (): DashboardState => ({ }, async fetchRuns() { + if (!this.selectedWorkflowFilter) { + this.runs = []; + this.runsMeta = null; + this.selectedRun = null; + this.loadRunPage(1); + return; + } this.loadingRuns = true; this.errorRuns = ""; try { @@ -313,6 +330,7 @@ Alpine.data("dashboardApp", (): DashboardState => ({ count: "100", window: this.selectedWindow, timeout: String(this.logsTimeout), + workflow_name: this.selectedWorkflowFilter, }); const data = await fetchJson(`/api/runs?${params.toString()}`); this.runsMeta = data; @@ -459,7 +477,8 @@ Alpine.data("dashboardApp", (): DashboardState => ({ buildLogsCommand(count = DEFAULT_LOGS_COMMAND_COUNT) { const window = this.currentWindow(); - return `gh aw logs --json -c ${count} --start-date ${window.startDate} --timeout ${this.logsTimeout}`; + const workflowPart = this.selectedWorkflowFilter ? ` ${this.selectedWorkflowFilter}` : ""; + return `gh aw logs --json -c ${count}${workflowPart} --start-date ${window.startDate} --timeout ${this.logsTimeout}`; }, buildMaintenanceCommand(action) { diff --git a/.github/extensions/agentic-workflows-dashboard/src/dashboard-cli.ts b/.github/extensions/agentic-workflows-dashboard/src/dashboard-cli.ts index 4b4bcda5db7..e5f949bdc5b 100644 --- a/.github/extensions/agentic-workflows-dashboard/src/dashboard-cli.ts +++ b/.github/extensions/agentic-workflows-dashboard/src/dashboard-cli.ts @@ -64,7 +64,7 @@ function combineOutput(stdout: string, stderr: string): string { function spawnExecFile(file: string, args: string[], options: ExecOptions, callback: ExecCallback): void { const { env, cwd, maxBuffer = 10 * 1024 * 1024 } = options ?? {}; - const spawnOptions: SpawnOptions = { env, cwd, stdio: ["ignore", "pipe", "pipe"], windowsHide: true }; + const spawnOptions: SpawnOptions = { env, cwd, stdio: ["ignore", "pipe", "pipe"], windowsHide: true, detached: true }; console.error(`${LOG} spawn file=${file} args=${JSON.stringify(args)} cwd=${cwd}`); const proc = spawn(file, args, spawnOptions); const stdoutChunks: Buffer[] = []; diff --git a/.github/extensions/agentic-workflows-dashboard/web/app.js b/.github/extensions/agentic-workflows-dashboard/web/app.js index c96d64faf97..0a59ddfcc7b 100644 --- a/.github/extensions/agentic-workflows-dashboard/web/app.js +++ b/.github/extensions/agentic-workflows-dashboard/web/app.js @@ -3309,9 +3309,9 @@ var dashboardTabs = [ { id: "commands", label: "Commands" } ]; var reportWindows = [ - { id: "3d", label: "3 days", startDate: "-3d" }, - { id: "7d", label: "7 days", startDate: "-1w" }, - { id: "1mo", label: "1 month", startDate: "-1mo" } + { id: "3d", label: "3 days", startDate: "-3d", days: 3 }, + { id: "7d", label: "7 days", startDate: "-1w", days: 7 }, + { id: "1mo", label: "1 month", startDate: "-1mo", days: 30 } ]; var DEFAULT_LOGS_COMMAND_COUNT = 25; function cliSourceLabel(cliStatus) { @@ -3394,6 +3394,7 @@ module_default.data("dashboardApp", () => ({ reportWindows, activeTab: "definitions", selectedWindow: "7d", + selectedWorkflowFilter: "", logsTimeout: 1, pageSize: 20, cliStatus: null, @@ -3433,7 +3434,7 @@ module_default.data("dashboardApp", () => ({ this.commandInput = this.buildLogsCommand(); await this.fetchCliStatus(); if (this.cliStatus?.available) { - await Promise.all([this.fetchDefinitions(), this.fetchRuns(), this.fetchUsage(), this.fetchExperiments()]); + await Promise.all([this.fetchDefinitions(), this.fetchUsage(), this.fetchExperiments()]); this.commandOutput = `$ ${this.cliStatus.command} gh aw version ${this.cliStatus.version}`; return; @@ -3455,6 +3456,12 @@ gh aw version ${this.cliStatus.version}`; if (!this.cliStatus?.available) return; await Promise.all([this.fetchRuns(), this.fetchUsage()]); }, + async selectWorkflowFilter(workflowName) { + this.selectedWorkflowFilter = workflowName; + this.commandInput = this.buildLogsCommand(); + if (!this.cliStatus?.available) return; + await this.fetchRuns(); + }, async fetchCliStatus() { this.loadingCliStatus = true; this.errorCliStatus = ""; @@ -3480,6 +3487,13 @@ gh aw version ${this.cliStatus.version}`; } }, async fetchRuns() { + if (!this.selectedWorkflowFilter) { + this.runs = []; + this.runsMeta = null; + this.selectedRun = null; + this.loadRunPage(1); + return; + } this.loadingRuns = true; this.errorRuns = ""; try { @@ -3487,7 +3501,8 @@ gh aw version ${this.cliStatus.version}`; const params = new URLSearchParams({ count: "100", window: this.selectedWindow, - timeout: String(this.logsTimeout) + timeout: String(this.logsTimeout), + workflow_name: this.selectedWorkflowFilter }); const data2 = await fetchJson(`/api/runs?${params.toString()}`); this.runsMeta = data2; @@ -3620,7 +3635,8 @@ gh aw version ${this.cliStatus.version}`; }, buildLogsCommand(count = DEFAULT_LOGS_COMMAND_COUNT) { const window2 = this.currentWindow(); - return `gh aw logs --json -c ${count} --start-date ${window2.startDate} --timeout ${this.logsTimeout}`; + const workflowPart = this.selectedWorkflowFilter ? ` ${this.selectedWorkflowFilter}` : ""; + return `gh aw logs --json -c ${count}${workflowPart} --start-date ${window2.startDate} --timeout ${this.logsTimeout}`; }, buildMaintenanceCommand(action) { if (action === "check-update") return "gh aw status --json"; diff --git a/.github/extensions/agentic-workflows-dashboard/web/index.html b/.github/extensions/agentic-workflows-dashboard/web/index.html index 1aa30f847f0..423abe16532 100644 --- a/.github/extensions/agentic-workflows-dashboard/web/index.html +++ b/.github/extensions/agentic-workflows-dashboard/web/index.html @@ -112,9 +112,27 @@

Workflow definitions
-
+

Workflow runs

- +
+ + +
+
+ +
+
Select a workflow above to load its runs.
+
Filtering by workflow avoids downloading logs for all workflows at once.
@@ -142,13 +160,12 @@

Workflow runs

-
+