Skip to content
This repository was archived by the owner on Mar 28, 2023. It is now read-only.

Commit 85f67df

Browse files
committed
Support "current branch" view
1 parent 0378577 commit 85f67df

8 files changed

Lines changed: 237 additions & 10 deletions

File tree

package.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,15 @@
6767
"light": "resources/icons/light/refresh.svg"
6868
}
6969
},
70+
{
71+
"command": "github-actions.explorer.current-branch.refresh",
72+
"category": "GitHub Actions",
73+
"title": "Refresh current branch",
74+
"icon": {
75+
"dark": "resources/icons/dark/refresh.svg",
76+
"light": "resources/icons/light/refresh.svg"
77+
}
78+
},
7079
{
7180
"command": "github-actions.explorer.openRun",
7281
"category": "GitHub Actions",
@@ -175,6 +184,10 @@
175184
],
176185
"views": {
177186
"github-actions": [
187+
{
188+
"id": "github-actions.current-branch",
189+
"name": "Current Branch"
190+
},
178191
{
179192
"id": "github-actions.workflows",
180193
"name": "Workflows"
@@ -200,6 +213,11 @@
200213
"command": "github-actions.explorer.refresh",
201214
"group": "navigation",
202215
"when": "view == github-actions.workflows || view == github-actions.settings"
216+
},
217+
{
218+
"command": "github-actions.explorer.current-branch.refresh",
219+
"group": "navigation",
220+
"when": "view == github-actions.current-branch"
203221
}
204222
],
205223
"editor/title": [

src/extension.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import * as vscode from "vscode";
22

3-
import { init as initLogger, log } from "./log";
3+
import { init as initLogger, log, logDebug } from "./log";
44

5+
import { CurrentBranchTreeProvider } from "./treeViews/currentBranch";
56
import { LogScheme } from "./logs/constants";
67
import { SettingsTreeProvider } from "./treeViews/settings";
78
import { WorkflowStepLogFoldingProvider } from "./logs/foldingProvider";
@@ -64,13 +65,55 @@ export function activate(context: vscode.ExtensionContext) {
6465
)
6566
);
6667

68+
const currentBranchTreeProvider = new CurrentBranchTreeProvider();
69+
context.subscriptions.push(
70+
vscode.window.registerTreeDataProvider(
71+
"github-actions.current-branch",
72+
currentBranchTreeProvider
73+
)
74+
);
75+
6776
context.subscriptions.push(
6877
vscode.commands.registerCommand("github-actions.explorer.refresh", () => {
6978
workflowTreeProvider.refresh();
7079
settingsTreeProvider.refresh();
7180
})
7281
);
7382

83+
context.subscriptions.push(
84+
vscode.commands.registerCommand(
85+
"github-actions.explorer.current-branch.refresh",
86+
() => {
87+
currentBranchTreeProvider.refresh();
88+
}
89+
)
90+
);
91+
92+
(async () => {
93+
const context = await getGitHubContext();
94+
if (!context) {
95+
logDebug("Could not register branch change event handler");
96+
return;
97+
}
98+
99+
for (const repo of context.repos) {
100+
let currentAhead = repo.repositoryState.HEAD?.ahead;
101+
let currentHeadName = repo.repositoryState.HEAD?.name;
102+
repo.repositoryState.onDidChange(() => {
103+
// When the current head/branch changes, or the number of commits ahead changes (which indicates
104+
// a push), refresh the current-branch view
105+
if (
106+
repo.repositoryState.HEAD?.name !== currentHeadName ||
107+
(repo.repositoryState.HEAD?.ahead || 0) < (currentAhead || 0)
108+
) {
109+
currentHeadName = repo.repositoryState.HEAD?.name;
110+
currentAhead = repo.repositoryState.HEAD?.ahead;
111+
currentBranchTreeProvider.refresh();
112+
}
113+
});
114+
}
115+
})();
116+
74117
//
75118
// Commands
76119
//

src/git/repository.ts

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

3-
import { API, GitExtension, RefType } from "../typings/git";
3+
import { API, GitExtension, RefType, RepositoryState } from "../typings/git";
44
import { logDebug, logError } from "../log";
55

66
import { Octokit } from "@octokit/rest";
@@ -120,6 +120,7 @@ export async function getGitHubUrls(): Promise<
120120

121121
export interface GitHubRepoContext {
122122
client: Octokit;
123+
repositoryState: RepositoryState;
123124

124125
workspaceUri: vscode.Uri;
125126

@@ -146,6 +147,12 @@ export async function getGitHubContext(): Promise<GitHubContext | undefined> {
146147
}
147148

148149
try {
150+
const git = await getGitExtension();
151+
if (!git) {
152+
logDebug("Could not find git extension");
153+
return;
154+
}
155+
149156
const session = await getSession();
150157
const client = getClient(session.accessToken);
151158

@@ -166,9 +173,12 @@ export async function getGitHubContext(): Promise<GitHubContext | undefined> {
166173
owner: protocolInfo.protocol.owner,
167174
});
168175

176+
const repo = git.getRepository(protocolInfo.workspaceUri);
177+
169178
return {
170179
workspaceUri: protocolInfo.workspaceUri,
171180
client,
181+
repositoryState: repo!.state,
172182
name: protocolInfo.protocol.repositoryName,
173183
owner: protocolInfo.protocol.owner,
174184
id: repoInfo.data.id,
@@ -236,9 +246,21 @@ export async function getGitHubContextForDocumentUri(
236246

237247
const workspaceUri = vscode.workspace.getWorkspaceFolder(documentUri);
238248
if (!workspaceUri) {
239-
debugger;
240249
return;
241250
}
242251

243252
return getGitHubContextForWorkspaceUri(workspaceUri.uri);
244253
}
254+
255+
export function getCurrentBranch(state: RepositoryState): string | undefined {
256+
const head = state.HEAD;
257+
if (!head) {
258+
return;
259+
}
260+
261+
if (head.type != RefType.Head) {
262+
return;
263+
}
264+
265+
return head.name;
266+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import * as vscode from "vscode";
2+
3+
import { GitHubRepoContext, getCurrentBranch } from "../../git/repository";
4+
5+
import { NoRunForBranchNode } from "./noRunForBranchNode";
6+
import { WorkflowRunNode } from "../workflows/workflowRunNode";
7+
import { logDebug } from "../../log";
8+
9+
export class CurrentBranchRepoNode extends vscode.TreeItem {
10+
constructor(
11+
public readonly gitHubRepoContext: GitHubRepoContext,
12+
public readonly currentBranchName: string
13+
) {
14+
super(gitHubRepoContext.name, vscode.TreeItemCollapsibleState.Collapsed);
15+
16+
this.description = currentBranchName;
17+
this.contextValue = "cb-repo";
18+
}
19+
20+
async getRuns(): Promise<(WorkflowRunNode | NoRunForBranchNode)[]> {
21+
logDebug("Getting workflow runs for current branch");
22+
23+
return (
24+
(await getCurrentBranchWorkflowRunNodes(this.gitHubRepoContext)) || []
25+
);
26+
}
27+
}
28+
29+
export async function getCurrentBranchWorkflowRunNodes(
30+
gitHubRepoContext: GitHubRepoContext
31+
): Promise<(WorkflowRunNode | NoRunForBranchNode)[] | undefined> {
32+
const currentBranch = getCurrentBranch(gitHubRepoContext.repositoryState);
33+
if (!currentBranch) {
34+
logDebug(`Could not find current branch for ${gitHubRepoContext.name}`);
35+
return [];
36+
}
37+
38+
const result = await gitHubRepoContext.client.actions.listWorkflowRunsForRepo(
39+
{
40+
owner: gitHubRepoContext.owner,
41+
repo: gitHubRepoContext.name,
42+
branch: currentBranch,
43+
}
44+
);
45+
46+
const resp = result.data;
47+
const runs = resp.workflow_runs;
48+
49+
if (runs?.length == 0) {
50+
return [new NoRunForBranchNode()];
51+
}
52+
53+
return runs.map((wr) => {
54+
const wf = wr.workflow_id;
55+
56+
// TODO: Do we need to include the workflow name here?
57+
return new WorkflowRunNode(gitHubRepoContext, wr, wr.name ?? undefined);
58+
});
59+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import * as vscode from "vscode";
2+
3+
export class NoRunForBranchNode extends vscode.TreeItem {
4+
constructor() {
5+
super("No runs for current branch");
6+
}
7+
}

src/treeViews/currentBranch.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import * as vscode from "vscode";
2+
3+
import {
4+
CurrentBranchRepoNode,
5+
getCurrentBranchWorkflowRunNodes,
6+
} from "./current-branch/currentBranchRepoNode";
7+
import { getCurrentBranch, getGitHubContext } from "../git/repository";
8+
9+
import { NoRunForBranchNode } from "./current-branch/noRunForBranchNode";
10+
import { WorkflowJobNode } from "./workflows/workflowJobNode";
11+
import { WorkflowRunNode } from "./workflows/workflowRunNode";
12+
import { WorkflowStepNode } from "./workflows/workflowStepNode";
13+
import { logDebug } from "../log";
14+
15+
type CurrentBranchTreeNode =
16+
| CurrentBranchRepoNode
17+
| WorkflowRunNode
18+
| WorkflowJobNode
19+
| WorkflowStepNode
20+
| NoRunForBranchNode;
21+
22+
export class CurrentBranchTreeProvider
23+
implements vscode.TreeDataProvider<CurrentBranchTreeNode>
24+
{
25+
private _onDidChangeTreeData =
26+
new vscode.EventEmitter<CurrentBranchTreeNode | null>();
27+
readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
28+
29+
refresh(): void {
30+
this._onDidChangeTreeData.fire(null);
31+
}
32+
33+
getTreeItem(
34+
element: CurrentBranchTreeNode
35+
): vscode.TreeItem | Thenable<vscode.TreeItem> {
36+
return element;
37+
}
38+
39+
async getChildren(
40+
element?: CurrentBranchTreeNode | undefined
41+
): Promise<CurrentBranchTreeNode[]> {
42+
if (!element) {
43+
const gitHubContext = await getGitHubContext();
44+
if (!gitHubContext) {
45+
return [];
46+
}
47+
48+
if (gitHubContext.repos.length === 1) {
49+
return (
50+
(await getCurrentBranchWorkflowRunNodes(gitHubContext.repos[0])) || []
51+
);
52+
}
53+
54+
if (gitHubContext.repos.length > 1) {
55+
return gitHubContext.repos
56+
.map((repoContext): CurrentBranchRepoNode | undefined => {
57+
const currentBranch = getCurrentBranch(repoContext.repositoryState);
58+
if (!currentBranch) {
59+
logDebug(`Could not find current branch for ${repoContext.name}`);
60+
return undefined;
61+
}
62+
63+
return new CurrentBranchRepoNode(repoContext, currentBranch);
64+
})
65+
.filter((x) => x !== undefined) as CurrentBranchRepoNode[];
66+
}
67+
} else if (element instanceof CurrentBranchRepoNode) {
68+
return element.getRuns();
69+
} else if (element instanceof WorkflowRunNode) {
70+
return element.getJobs();
71+
} else if (element instanceof WorkflowJobNode) {
72+
return element.getSteps();
73+
}
74+
75+
return [];
76+
}
77+
}

src/treeViews/workflows/workflowNode.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@ export class WorkflowNode extends vscode.TreeItem {
6161
const resp = result.data;
6262
const runs = resp.workflow_runs;
6363

64-
return runs.map(
65-
(wr) => new WorkflowRunNode(this.gitHubRepoContext, this.wf, wr)
66-
);
64+
return runs.map((wr) => new WorkflowRunNode(this.gitHubRepoContext, wr));
6765
}
6866
}

src/treeViews/workflows/workflowRunNode.ts

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

3-
import { Workflow, WorkflowJob, WorkflowRun } from "../../model";
3+
import { WorkflowJob, WorkflowRun } from "../../model";
44

55
import { GitHubRepoContext } from "../../git/repository";
66
import { WorkflowJobNode } from "./workflowJobNode";
@@ -10,10 +10,13 @@ import { logDebug } from "../../log";
1010
export class WorkflowRunNode extends vscode.TreeItem {
1111
constructor(
1212
public readonly gitHubRepoContext: GitHubRepoContext,
13-
public readonly workflow: Workflow,
14-
public readonly run: WorkflowRun
13+
public readonly run: WorkflowRun,
14+
public readonly workflowName?: string
1515
) {
16-
super(`#${run.id}`, vscode.TreeItemCollapsibleState.Collapsed);
16+
super(
17+
`${workflowName ? workflowName + " " : ""}#${run.id}`,
18+
vscode.TreeItemCollapsibleState.Collapsed
19+
);
1720

1821
this.description = `${run.event} (${(run.head_sha || "").substr(0, 7)})`;
1922

0 commit comments

Comments
 (0)