Skip to content

Commit b189a3f

Browse files
committed
Move workflow runs to separate store
1 parent 2341aee commit b189a3f

File tree

11 files changed

+233
-48
lines changed

11 files changed

+233
-48
lines changed

src/extension.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ import {registerUpdateSecret} from "./commands/secrets/updateSecret";
1515
import {registerTriggerWorkflowRun} from "./commands/triggerWorkflowRun";
1616
import {registerUnPinWorkflow} from "./commands/unpinWorkflow";
1717
import {initConfiguration} from "./configuration/configuration";
18-
import {getGitHubContext} from "./git/repository";
18+
import {getGitHubContext, GitHubRepoContext} from "./git/repository";
1919
import {LogScheme} from "./logs/constants";
2020
import {WorkflowStepLogProvider} from "./logs/fileProvider";
2121
import {WorkflowStepLogFoldingProvider} from "./logs/foldingProvider";
2222
import {WorkflowStepLogSymbolProvider} from "./logs/symbolProvider";
23+
import {WorkflowRun} from "./model";
2324
import {initPinnedWorkflows} from "./pinnedWorkflows/pinnedWorkflows";
25+
import {RunStore} from "./store/store";
2426
import {initWorkflowDocumentTracking} from "./tracker/workflowDocumentTracker";
2527
import {CurrentBranchTreeProvider} from "./treeViews/currentBranch";
2628
import {initResources} from "./treeViews/icons";
@@ -44,14 +46,15 @@ export async function activate(context: vscode.ExtensionContext) {
4446
await initWorkflowDocumentTracking(context);
4547

4648
// Tree views
49+
const store = new RunStore();
4750

48-
const workflowTreeProvider = new WorkflowsTreeProvider();
51+
const workflowTreeProvider = new WorkflowsTreeProvider(store);
4952
context.subscriptions.push(vscode.window.registerTreeDataProvider("github-actions.workflows", workflowTreeProvider));
5053

5154
const settingsTreeProvider = new SettingsTreeProvider();
5255
context.subscriptions.push(vscode.window.registerTreeDataProvider("github-actions.settings", settingsTreeProvider));
5356

54-
const currentBranchTreeProvider = new CurrentBranchTreeProvider();
57+
const currentBranchTreeProvider = new CurrentBranchTreeProvider(store);
5558
context.subscriptions.push(
5659
vscode.window.registerTreeDataProvider("github-actions.current-branch", currentBranchTreeProvider)
5760
);
@@ -63,6 +66,14 @@ export async function activate(context: vscode.ExtensionContext) {
6366
})
6467
);
6568

69+
// TODO: CS: Remove!
70+
vscode.commands.registerCommand(
71+
"github-actions.explorer.poll",
72+
(args: {gitHubRepoContext: GitHubRepoContext; run: WorkflowRun}) => {
73+
store.pollRun(args.run.id, args.gitHubRepoContext, 2000, 10);
74+
}
75+
);
76+
6677
context.subscriptions.push(
6778
vscode.commands.registerCommand("github-actions.explorer.current-branch.refresh", () => {
6879
currentBranchTreeProvider.refresh();

src/log.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ enum LogLevel {
66
}
77

88
let logger: vscode.OutputChannel;
9-
const level: LogLevel = LogLevel.Debug;
9+
const level: LogLevel = PRODUCTION ? LogLevel.Info : LogLevel.Debug;
1010

1111
export function init() {
1212
logger = vscode.window.createOutputChannel("GitHub Actions");

src/store/store.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import {setInterval} from "timers";
2+
import {EventEmitter} from "vscode";
3+
import {GitHubRepoContext} from "../git/repository";
4+
import {logDebug} from "../log";
5+
import {WorkflowRun} from "../model";
6+
7+
export interface RunStoreEvent {
8+
run: WorkflowRun;
9+
}
10+
11+
type Updater = {
12+
intervalMs: number;
13+
remainingAttempts: number;
14+
repoContext: GitHubRepoContext;
15+
runId: number;
16+
handle: NodeJS.Timeout | undefined;
17+
};
18+
19+
export class RunStore extends EventEmitter<RunStoreEvent> {
20+
private runs = new Map<number, WorkflowRun>();
21+
private updaters = new Map<number, Updater>();
22+
23+
/**
24+
* Update the given run and inform any listener
25+
*/
26+
updateRun(run: WorkflowRun) {
27+
this.runs.set(run.id, run);
28+
this.fire({run});
29+
}
30+
31+
/**
32+
* Start polling for updates for the given run
33+
*/
34+
pollRun(runId: number, repoContext: GitHubRepoContext, intervalMs: number, attempts = 10) {
35+
let updater: Updater | undefined = this.updaters.get(runId);
36+
if (updater) {
37+
clearInterval(updater.handle);
38+
}
39+
40+
updater = {
41+
intervalMs,
42+
repoContext,
43+
runId,
44+
remainingAttempts: attempts,
45+
handle: undefined
46+
};
47+
48+
updater.handle = setInterval(async () => this.fetchRun(updater!), intervalMs);
49+
50+
this.updaters.set(runId, updater);
51+
}
52+
53+
private async fetchRun(updater: Updater) {
54+
logDebug("Updating run: " + updater.runId);
55+
56+
updater.remainingAttempts--;
57+
if (updater.remainingAttempts === 0) {
58+
clearInterval(updater.handle);
59+
this.updaters.delete(updater.runId);
60+
}
61+
62+
const result = await updater.repoContext.client.actions.getWorkflowRun({
63+
owner: updater.repoContext.owner,
64+
repo: updater.repoContext.name,
65+
run_id: updater.runId
66+
});
67+
68+
const run = result.data;
69+
this.updateRun(run);
70+
}
71+
}

src/treeViews/current-branch/currentBranchRepoNode.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import * as vscode from "vscode";
22

3-
import {GitHubRepoContext, getCurrentBranch} from "../../git/repository";
3+
import {getCurrentBranch, GitHubRepoContext} from "../../git/repository";
44

5-
import {NoRunForBranchNode} from "./noRunForBranchNode";
6-
import {WorkflowRunNode} from "../workflows/workflowRunNode";
75
import {logDebug} from "../../log";
6+
import {WorkflowRunNode} from "../shared/workflowRunNode";
7+
import {NoRunForBranchNode} from "./noRunForBranchNode";
88

99
export class CurrentBranchRepoNode extends vscode.TreeItem {
1010
constructor(public readonly gitHubRepoContext: GitHubRepoContext, public readonly currentBranchName: string) {

src/treeViews/currentBranch.ts

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,40 @@
11
import * as vscode from "vscode";
22

3-
import {CurrentBranchRepoNode, getCurrentBranchWorkflowRunNodes} from "./current-branch/currentBranchRepoNode";
4-
import {getCurrentBranch, getGitHubContext} from "../git/repository";
3+
import {getCurrentBranch, getGitHubContext, GitHubRepoContext} from "../git/repository";
4+
import {CurrentBranchRepoNode} from "./current-branch/currentBranchRepoNode";
55

6+
import {log, logDebug} from "../log";
7+
import {RunStore} from "../store/store";
68
import {NoRunForBranchNode} from "./current-branch/noRunForBranchNode";
7-
import {WorkflowJobNode} from "./workflows/workflowJobNode";
8-
import {WorkflowRunNode} from "./workflows/workflowRunNode";
9+
import {NoWorkflowJobsNode} from "./shared/noWorkflowJobsNode";
10+
import {WorkflowJobNode} from "./shared/workflowJobNode";
11+
import {WorkflowRunNode} from "./shared/workflowRunNode";
12+
import {WorkflowRunTreeDataProvider} from "./workflowRunTreeDataProvider";
913
import {WorkflowStepNode} from "./workflows/workflowStepNode";
10-
import {logDebug} from "../log";
1114

1215
type CurrentBranchTreeNode =
1316
| CurrentBranchRepoNode
1417
| WorkflowRunNode
1518
| WorkflowJobNode
19+
| NoWorkflowJobsNode
1620
| WorkflowStepNode
1721
| NoRunForBranchNode;
1822

19-
export class CurrentBranchTreeProvider implements vscode.TreeDataProvider<CurrentBranchTreeNode> {
20-
private _onDidChangeTreeData = new vscode.EventEmitter<CurrentBranchTreeNode | null>();
23+
export class CurrentBranchTreeProvider
24+
extends WorkflowRunTreeDataProvider
25+
implements vscode.TreeDataProvider<CurrentBranchTreeNode>
26+
{
27+
protected _onDidChangeTreeData = new vscode.EventEmitter<CurrentBranchTreeNode | null>();
2128
readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
2229

30+
constructor(store: RunStore) {
31+
super(store);
32+
}
33+
34+
protected _updateNode(node: WorkflowRunNode): void {
35+
this._onDidChangeTreeData.fire(node);
36+
}
37+
2338
refresh(): void {
2439
this._onDidChangeTreeData.fire(null);
2540
}
@@ -36,15 +51,22 @@ export class CurrentBranchTreeProvider implements vscode.TreeDataProvider<Curren
3651
}
3752

3853
if (gitHubContext.repos.length === 1) {
39-
return (await getCurrentBranchWorkflowRunNodes(gitHubContext.repos[0])) || [];
54+
const repoContext = gitHubContext.repos[0];
55+
const currentBranch = getCurrentBranch(repoContext.repositoryState);
56+
if (!currentBranch) {
57+
log(`Could not find current branch for ${repoContext.name}`);
58+
return [];
59+
}
60+
61+
return (await this.getRuns(repoContext, currentBranch)) || [];
4062
}
4163

42-
if (gitHubContext.repos.length > 1) {
64+
if (gitHubContext.repos.length === 1) {
4365
return gitHubContext.repos
4466
.map((repoContext): CurrentBranchRepoNode | undefined => {
4567
const currentBranch = getCurrentBranch(repoContext.repositoryState);
4668
if (!currentBranch) {
47-
logDebug(`Could not find current branch for ${repoContext.name}`);
69+
log(`Could not find current branch for ${repoContext.name}`);
4870
return undefined;
4971
}
5072

@@ -53,7 +75,7 @@ export class CurrentBranchTreeProvider implements vscode.TreeDataProvider<Curren
5375
.filter(x => x !== undefined) as CurrentBranchRepoNode[];
5476
}
5577
} else if (element instanceof CurrentBranchRepoNode) {
56-
return element.getRuns();
78+
return this.getRuns(element.gitHubRepoContext, element.currentBranchName);
5779
} else if (element instanceof WorkflowRunNode) {
5880
return element.getJobs();
5981
} else if (element instanceof WorkflowJobNode) {
@@ -62,4 +84,24 @@ export class CurrentBranchTreeProvider implements vscode.TreeDataProvider<Curren
6284

6385
return [];
6486
}
87+
88+
private async getRuns(gitHubRepoContext: GitHubRepoContext, currentBranchName: string): Promise<WorkflowRunNode[]> {
89+
logDebug("Getting workflow runs");
90+
91+
const result = await gitHubRepoContext.client.actions.listWorkflowRunsForRepo({
92+
owner: gitHubRepoContext.owner,
93+
repo: gitHubRepoContext.name,
94+
branch: currentBranchName
95+
});
96+
97+
const resp = result.data;
98+
const runs = resp.workflow_runs;
99+
100+
return runs.map(wr => {
101+
this.store.updateRun(wr);
102+
const node = new WorkflowRunNode(gitHubRepoContext, wr);
103+
this._runNodes.set(wr.id, node);
104+
return node;
105+
});
106+
}
65107
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import * as vscode from "vscode";
2+
3+
/**
4+
* When no github.com remote can be found in the current workspace.
5+
*/
6+
export class NoWorkflowJobsNode extends vscode.TreeItem {
7+
constructor() {
8+
super("No workflow jobs");
9+
}
10+
}

src/treeViews/workflows/workflowJobNode.ts renamed to src/treeViews/shared/workflowJobNode.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as vscode from "vscode";
22
import {GitHubRepoContext} from "../../git/repository";
33
import {WorkflowJob} from "../../model";
44
import {getIconForWorkflowRun} from "../icons";
5-
import {WorkflowStepNode} from "./workflowStepNode";
5+
import {WorkflowStepNode} from "../workflows/workflowStepNode";
66

77
export class WorkflowJobNode extends vscode.TreeItem {
88
constructor(public readonly gitHubRepoContext: GitHubRepoContext, public readonly job: WorkflowJob) {

src/treeViews/workflows/workflowRunNode.ts renamed to src/treeViews/shared/workflowRunNode.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,26 @@ import * as vscode from "vscode";
33
import {WorkflowJob, WorkflowRun} from "../../model";
44

55
import {GitHubRepoContext} from "../../git/repository";
6-
import {WorkflowJobNode} from "./workflowJobNode";
7-
import {getIconForWorkflowRun} from "../icons";
86
import {logDebug} from "../../log";
7+
import {getIconForWorkflowRun} from "../icons";
8+
import {NoWorkflowJobsNode} from "./noWorkflowJobsNode";
9+
import {WorkflowJobNode} from "./workflowJobNode";
910

1011
export class WorkflowRunNode extends vscode.TreeItem {
1112
constructor(
1213
public readonly gitHubRepoContext: GitHubRepoContext,
13-
public readonly run: WorkflowRun,
14+
public run: WorkflowRun,
1415
public readonly workflowName?: string
1516
) {
16-
super(`${workflowName ? workflowName + " " : ""}#${run.id}`, vscode.TreeItemCollapsibleState.Collapsed);
17+
super(WorkflowRunNode._getLabel(run, workflowName), vscode.TreeItemCollapsibleState.Collapsed);
18+
19+
this.updateRun(run);
20+
}
21+
22+
updateRun(run: WorkflowRun) {
23+
this.run = run;
1724

18-
this.description = `${run.event} (${(run.head_sha || "").substr(0, 7)})`;
25+
this.label = WorkflowRunNode._getLabel(run, this.workflowName);
1926

2027
this.contextValue = "run";
2128
if (this.run.status !== "completed") {
@@ -34,7 +41,7 @@ export class WorkflowRunNode extends vscode.TreeItem {
3441
this.tooltip = `${this.run.status || ""} ${this.run.conclusion || ""}`;
3542
}
3643

37-
async getJobs(): Promise<WorkflowJobNode[]> {
44+
async getJobs(): Promise<(WorkflowJobNode | NoWorkflowJobsNode)[]> {
3845
logDebug("Getting workflow jobs");
3946

4047
const result = await this.gitHubRepoContext.client.actions.listJobsForWorkflowRun({
@@ -45,7 +52,14 @@ export class WorkflowRunNode extends vscode.TreeItem {
4552

4653
const resp = result.data;
4754
const jobs: WorkflowJob[] = resp.jobs;
55+
if (jobs.length === 0) {
56+
return [new NoWorkflowJobsNode()];
57+
}
4858

4959
return jobs.map(job => new WorkflowJobNode(this.gitHubRepoContext, job));
5060
}
61+
62+
private static _getLabel(run: WorkflowRun, workflowName?: string): string {
63+
return `${workflowName ? workflowName + " " : ""}#${run.id}`;
64+
}
5165
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import {RunStore} from "../store/store";
2+
import {WorkflowRunNode} from "./shared/workflowRunNode";
3+
4+
export abstract class WorkflowRunTreeDataProvider {
5+
protected _runNodes = new Map<number, WorkflowRunNode>();
6+
7+
constructor(protected store: RunStore) {
8+
this.store.event(({run}) => {
9+
// Get tree node
10+
const node = this._runNodes.get(run.id);
11+
if (node) {
12+
node.updateRun(run);
13+
this._updateNode(node);
14+
}
15+
});
16+
}
17+
18+
protected abstract _updateNode(node: WorkflowRunNode): void;
19+
}

0 commit comments

Comments
 (0)