-
Notifications
You must be signed in to change notification settings - Fork 151
Fix automatic workflow run updates with improved polling mechanism #541
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
3692e70
f7cfef8
0e7ed61
decdefa
ba66672
89fbe5a
0b5c87d
a62d2e0
964065c
6fdb2ed
4c04b43
fc52ae5
a65e12f
a08153a
c358f6e
a909e59
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,26 +1,34 @@ | ||
| import {basename} from "path"; | ||
| import * as vscode from "vscode"; | ||
|
|
||
| import {getGitHead, getGitHubContextForWorkspaceUri, GitHubRepoContext} from "../git/repository"; | ||
| import {getWorkflowUri, parseWorkflowFile} from "../workflow/workflow"; | ||
|
|
||
| import {Workflow} from "../model"; | ||
| import {RunStore} from "../store/store"; | ||
|
|
||
| import {log} from "../log"; | ||
|
|
||
| interface TriggerRunCommandOptions { | ||
| wf?: Workflow; | ||
| gitHubRepoContext: GitHubRepoContext; | ||
| } | ||
|
|
||
| export function registerTriggerWorkflowRun(context: vscode.ExtensionContext) { | ||
| export function registerTriggerWorkflowRun(context: vscode.ExtensionContext, store: RunStore) { | ||
| context.subscriptions.push( | ||
| vscode.commands.registerCommand( | ||
| "github-actions.explorer.triggerRun", | ||
| async (args: TriggerRunCommandOptions | vscode.Uri) => { | ||
| let workflowUri: vscode.Uri | null = null; | ||
| let workflowIdForApi: number | string | undefined; | ||
|
|
||
| if (args instanceof vscode.Uri) { | ||
| workflowUri = args; | ||
| workflowIdForApi = basename(workflowUri.fsPath); | ||
| } else if (args.wf) { | ||
| const wf: Workflow = args.wf; | ||
| workflowUri = getWorkflowUri(args.gitHubRepoContext, wf.path); | ||
| workflowIdForApi = wf.id; | ||
| } | ||
|
|
||
| if (!workflowUri) { | ||
|
|
@@ -43,6 +51,28 @@ export function registerTriggerWorkflowRun(context: vscode.ExtensionContext) { | |
| return; | ||
| } | ||
|
|
||
| const relativeWorkflowPath = vscode.workspace.asRelativePath(workflowUri, false); | ||
| if (!workflowIdForApi) { | ||
| workflowIdForApi = basename(workflowUri.fsPath); | ||
| } | ||
|
|
||
| let latestRunId: number | undefined; | ||
| try { | ||
| log(`Fetching latest run for workflow: ${workflowIdForApi}`); | ||
| const result = await gitHubRepoContext.client.actions.listWorkflowRuns({ | ||
| owner: gitHubRepoContext.owner, | ||
| repo: gitHubRepoContext.name, | ||
| workflow_id: workflowIdForApi, | ||
| per_page: 1 | ||
| }); | ||
| latestRunId = result.data.workflow_runs[0]?.id; | ||
| log(`Latest run ID before trigger: ${latestRunId}`); | ||
| } catch (e) { | ||
| log(`Error fetching latest run: ${(e as Error).message}`); | ||
| } | ||
|
|
||
| let dispatched = false; | ||
|
|
||
| let selectedEvent: string | undefined; | ||
| if (workflow.events.workflow_dispatch !== undefined && workflow.events.repository_dispatch !== undefined) { | ||
| selectedEvent = await vscode.window.showQuickPick(["repository_dispatch", "workflow_dispatch"], { | ||
|
|
@@ -85,8 +115,6 @@ export function registerTriggerWorkflowRun(context: vscode.ExtensionContext) { | |
| } | ||
|
|
||
| try { | ||
| const relativeWorkflowPath = vscode.workspace.asRelativePath(workflowUri, false); | ||
|
|
||
| await gitHubRepoContext.client.actions.createWorkflowDispatch({ | ||
| owner: gitHubRepoContext.owner, | ||
| repo: gitHubRepoContext.name, | ||
|
|
@@ -95,6 +123,7 @@ export function registerTriggerWorkflowRun(context: vscode.ExtensionContext) { | |
| inputs | ||
| }); | ||
|
|
||
| dispatched = true; | ||
| vscode.window.setStatusBarMessage(`GitHub Actions: Workflow event dispatched`, 2000); | ||
| } catch (error) { | ||
| return vscode.window.showErrorMessage(`Could not create workflow dispatch: ${(error as Error)?.message}`); | ||
|
|
@@ -134,10 +163,49 @@ export function registerTriggerWorkflowRun(context: vscode.ExtensionContext) { | |
| client_payload: {} | ||
| }); | ||
|
|
||
| dispatched = true; | ||
| vscode.window.setStatusBarMessage(`GitHub Actions: Repository event '${event_type}' dispatched`, 2000); | ||
| } | ||
| } | ||
|
|
||
| if (dispatched) { | ||
| await vscode.window.withProgress( | ||
| { | ||
| location: vscode.ProgressLocation.Window, | ||
| title: "Waiting for workflow run to start..." | ||
| }, | ||
| async () => { | ||
| log("Starting loop to check for new workflow run..."); | ||
| for (let i = 0; i < 20; i++) { | ||
| if (i > 0) { | ||
| await new Promise(resolve => setTimeout(resolve, 1000)); | ||
| } | ||
| try { | ||
| log(`Checking for new run (attempt ${i + 1}/20)...`); | ||
| const result = await gitHubRepoContext.client.actions.listWorkflowRuns({ | ||
| owner: gitHubRepoContext.owner, | ||
| repo: gitHubRepoContext.name, | ||
| workflow_id: workflowIdForApi as string | number, | ||
| per_page: 1 | ||
| }); | ||
| const newLatestRunId = result.data.workflow_runs[0]?.id; | ||
| log(`Latest run ID found: ${newLatestRunId} (Previous: ${latestRunId ?? "none"})`); | ||
|
|
||
| if (newLatestRunId && newLatestRunId !== latestRunId) { | ||
| log(`Found new workflow run: ${newLatestRunId}. Triggering refresh and polling.`); | ||
| await vscode.commands.executeCommand("github-actions.explorer.refresh"); | ||
| // Poll for 15 minutes (225 * 4s) | ||
| store.pollRun(newLatestRunId, gitHubRepoContext, 4000, 225); | ||
spboyer marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| break; | ||
| } | ||
| } catch (e) { | ||
| log(`Error checking for new run: ${(e as Error).message}`); | ||
| } | ||
| } | ||
| } | ||
| ); | ||
| } | ||
|
|
||
| return vscode.commands.executeCommand("github-actions.explorer.refresh"); | ||
|
||
| } | ||
| ) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,7 @@ | ||
| import {setInterval} from "timers"; | ||
| import {EventEmitter} from "vscode"; | ||
| import {GitHubRepoContext} from "../git/repository"; | ||
| import {logDebug} from "../log"; | ||
| import {log, logDebug} from "../log"; | ||
| import * as model from "../model"; | ||
| import {WorkflowRun} from "./workflowRun"; | ||
|
|
||
|
|
@@ -20,6 +20,18 @@ type Updater = { | |
| export class RunStore extends EventEmitter<RunStoreEvent> { | ||
| private runs = new Map<number, WorkflowRun>(); | ||
| private updaters = new Map<number, Updater>(); | ||
| private _isFocused = true; | ||
| private _isViewVisible = true; | ||
|
|
||
| setFocused(focused: boolean) { | ||
| this._isFocused = focused; | ||
| logDebug(`[Store]: Focus state changed to ${String(focused)}`); | ||
| } | ||
|
|
||
| setViewVisible(visible: boolean) { | ||
| this._isViewVisible = visible; | ||
| logDebug(`[Store]: View visibility changed to ${String(visible)}`); | ||
| } | ||
|
|
||
| getRun(runId: number): WorkflowRun | undefined { | ||
| return this.runs.get(runId); | ||
|
|
@@ -46,6 +58,7 @@ export class RunStore extends EventEmitter<RunStoreEvent> { | |
| * Start polling for updates for the given run | ||
| */ | ||
| pollRun(runId: number, repoContext: GitHubRepoContext, intervalMs: number, attempts = 10) { | ||
| log(`Starting polling for run ${runId} every ${intervalMs}ms for ${attempts} attempts`); | ||
| const existingUpdater: Updater | undefined = this.updaters.get(runId); | ||
| if (existingUpdater && existingUpdater.handle) { | ||
| clearInterval(existingUpdater.handle); | ||
|
|
@@ -65,7 +78,11 @@ export class RunStore extends EventEmitter<RunStoreEvent> { | |
| } | ||
|
|
||
| private async fetchRun(updater: Updater) { | ||
| logDebug("Updating run: ", updater.runId); | ||
| if (!this._isFocused || !this._isViewVisible) { | ||
| return; | ||
| } | ||
|
|
||
| log(`Fetching run update: ${updater.runId}. Remaining attempts: ${updater.remainingAttempts}`); | ||
|
|
||
| updater.remainingAttempts--; | ||
|
Comment on lines
+81
to
87
|
||
| if (updater.remainingAttempts === 0) { | ||
|
|
@@ -83,6 +100,14 @@ export class RunStore extends EventEmitter<RunStoreEvent> { | |
| }); | ||
|
|
||
| const run = result.data; | ||
| log(`Polled run: ${run.id} Status: ${run.status || "null"} Conclusion: ${run.conclusion || "null"}`); | ||
| this.addRun(updater.repoContext, run); | ||
|
|
||
| if (run.status === "completed") { | ||
| if (updater.handle) { | ||
| clearInterval(updater.handle); | ||
| } | ||
| this.updaters.delete(updater.runId); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,6 +24,18 @@ export abstract class WorkflowRunTreeDataProvider { | |
| ): WorkflowRunNode[] { | ||
| return runData.map(runData => { | ||
| const workflowRun = this.store.addRun(gitHubRepoContext, runData); | ||
|
|
||
| // Auto-poll active runs | ||
| if ( | ||
| workflowRun.run.status === "in_progress" || | ||
| workflowRun.run.status === "queued" || | ||
| workflowRun.run.status === "waiting" || | ||
| workflowRun.run.status === "requested" | ||
| ) { | ||
| // Poll every 4 seconds for up to 15 minutes (225 attempts) | ||
| this.store.pollRun(workflowRun.run.id, gitHubRepoContext, 4000, 225); | ||
|
Comment on lines
+28
to
+36
|
||
| } | ||
|
|
||
| const node = new WorkflowRunNode( | ||
| this.store, | ||
| gitHubRepoContext, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The workflowIdForApi is set twice when args is a vscode.Uri - once at line 27 and again at line 56. The second assignment is redundant since the condition "!workflowIdForApi" will never be true when args is a Uri. Consider restructuring this logic to avoid the unnecessary check and assignment.