Skip to content

Commit f9beeaf

Browse files
committed
Add Pinned Workflow Refresh Etag Support
1 parent a2760cd commit f9beeaf

7 files changed

Lines changed: 77 additions & 24 deletions

File tree

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@
9494
"github-actions.workflows.pinned.refresh.interval": {
9595
"type": "number",
9696
"description": "Time to wait between calls to update pinned workflows in seconds",
97-
"default": 30,
97+
"default": 1,
9898
"scope": "window"
9999
},
100100
"github-actions.remote-name": {
@@ -527,6 +527,8 @@
527527
},
528528
"devDependencies": {
529529
"@eslint/js": "^9.30.1",
530+
"@octokit/openapi-types": "^25.1.0",
531+
"@octokit/plugin-rest-endpoint-methods": "^16.0.0",
530532
"@types/jest": "^29.5.14",
531533
"@types/libsodium-wrappers": "^0.7.14",
532534
"@types/node": "^20.19.4",

pnpm-lock.yaml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/api/api.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {getGitHubApiUri} from "../configuration/configuration";
44
import {throttling} from "@octokit/plugin-throttling";
55
import {retry} from "@octokit/plugin-retry";
66
import {conditionalRequest} from "./conditionalRequests";
7+
import {createOctokitLogger} from "../log";
78

89
export const userAgent = `VS Code GitHub Actions (${version})`;
910

@@ -13,6 +14,7 @@ export type GhaOctokit = InstanceType<typeof GhaOctokit>;
1314
export function getClient(token: string) {
1415
return new GhaOctokit({
1516
auth: token,
17+
log: createOctokitLogger(),
1618
userAgent: userAgent,
1719
baseUrl: getGitHubApiUri(),
1820
throttle: {

src/api/conditionalRequests.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {logError} from "../log";
1+
import {logDebug, logError, logTrace} from "../log";
22
import {assertRequestError} from "../error";
33

44
export const conditionalRequest = () => ({
@@ -41,15 +41,20 @@ async function returnIfChanged<TResponse, TParams>(
4141
: TParams & {headers: Record<string, string>};
4242

4343
const cacheKey = cacheId ?? JSON.stringify(options);
44+
logDebug("Performing conditional request for cache key:", cacheKey);
45+
4446
// Add If-None-Match header if we have an ETag for this key
4547
const cacheMatch = timestamp ? timestampMap.get(cacheKey) : etagMap.get(cacheKey);
4648
const cacheHeader = timestamp ? "if-modified-since" : "if-none-match";
4749
if (cacheMatch) {
50+
logTrace("Cache Key FOUND:", cacheKey);
4851
// Initialize headers if they don't exist
4952
if (!options.headers) {
5053
options.headers = {};
5154
}
5255
options.headers[cacheHeader] = cacheMatch;
56+
} else {
57+
logTrace("Cache Key MISS:", cacheKey);
5358
}
5459

5560
// TODO: Figure out how to make this type safe
@@ -59,6 +64,7 @@ async function returnIfChanged<TResponse, TParams>(
5964
// Store the new ETag if provided
6065
const responseEtag = timestamp ? response.headers.last_modified : response.headers.etag;
6166
if (responseEtag) {
67+
logTrace("Cache Key STORE:", cacheKey, responseEtag);
6268
if (timestamp) {
6369
timestampMap.set(cacheKey, responseEtag);
6470
} else {
@@ -72,6 +78,7 @@ async function returnIfChanged<TResponse, TParams>(
7278

7379
// Resource not modified (304), return undefined.
7480
if (err.status === 304) {
81+
logTrace("Content Not Modified:", cacheId);
7582
return undefined;
7683
}
7784

src/configuration/configuration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export function isPinnedWorkflowsRefreshEnabled(): boolean {
9797
}
9898

9999
export function pinnedWorkflowsRefreshInterval(): number {
100-
return getConfiguration().get<number>(getSettingsKey("workflows.pinned.refresh.interval"), 60);
100+
return getConfiguration().get<number>(getSettingsKey("workflows.pinned.refresh.interval"), 1);
101101
}
102102

103103
export function getRemoteName(): string {

src/log.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ export function init() {
66
logger = vscode.window.createOutputChannel("GitHub Actions", {log: true});
77
}
88

9+
export function logError(e: Error, ...values: unknown[]) {
10+
logger.error(e, values);
11+
}
12+
13+
export function logWarn(...values: unknown[]) {
14+
logger.warn(values.join(" "));
15+
}
16+
917
export function log(...values: unknown[]) {
1018
logger.info(values.join(" "));
1119
}
@@ -14,10 +22,28 @@ export function logDebug(...values: unknown[]) {
1422
logger.debug(values.join(" "));
1523
}
1624

17-
export function logError(e: Error, ...values: unknown[]) {
18-
logger.error(e, values);
25+
export function logTrace(...values: unknown[]) {
26+
logger.trace(values.join(" "));
1927
}
2028

2129
export function revealLog() {
2230
logger.show();
2331
}
32+
33+
const octoPrefix = "[Octokit]";
34+
35+
export function createOctokitLogger() {
36+
return {
37+
debug: (...args: unknown[]) => logTrace(octoPrefix, "[DBG]", ...args),
38+
info: (...args: unknown[]) => logTrace(octoPrefix, "[INF]", ...args),
39+
warn: (...args: unknown[]) => logTrace(octoPrefix, "[WRN]", ...args),
40+
error: (...args: unknown[]) =>
41+
logTrace(
42+
octoPrefix,
43+
"[ERR]",
44+
args[0] instanceof Error ? (args[0] as Error) : new Error(octoPrefix + String(args[0])),
45+
...args.slice(1)
46+
)
47+
};
48+
}
49+

src/pinnedWorkflows/pinnedWorkflows.ts

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
import {getGitHubContextForWorkspaceUri, GitHubRepoContext} from "../git/repository";
1010

1111
import {sep} from "path";
12-
import {log, logError} from "../log";
12+
import {log, logDebug, logError} from "../log";
1313
import {Workflow} from "../model";
1414
import {RunStore} from "../store/store";
1515
import {WorkflowRun} from "../store/workflowRun";
@@ -114,7 +114,6 @@ async function updatePinnedWorkflows() {
114114
});
115115

116116
const workflowByPath: {[id: string]: Workflow} = {};
117-
// @ts-expect-error FIXME: Newer Typescript catches a problem that previous didn't. This will be fixed in Octokit bump.
118117
workflows.forEach(w => (workflowByPath[w.path] = w));
119118

120119
for (const pinnedWorkflow of workflowsByWorkspace.get(workspaceName) || []) {
@@ -129,12 +128,6 @@ async function updatePinnedWorkflows() {
129128
}
130129
}
131130

132-
async function refreshPinnedWorkflows() {
133-
for (const pinnedWorkflow of pinnedWorkflows) {
134-
await refreshPinnedWorkflow(pinnedWorkflow);
135-
}
136-
}
137-
138131
function clearPinnedWorkflows() {
139132
// Remove any existing pinned workflows
140133
for (const pinnedWorkflow of pinnedWorkflows) {
@@ -150,8 +143,8 @@ function createPinnedWorkflow(gitHubRepoContext: GitHubRepoContext, workflow: Wo
150143

151144
const pinnedWorkflow = {
152145
gitHubRepoContext,
153-
workflowId: workflow.id,
154-
workflowName: workflow.name,
146+
workflowId: (workflow as any).id,
147+
workflowName: (workflow as any).name,
155148
lastRunId: undefined,
156149
statusBarItem
157150
};
@@ -161,20 +154,37 @@ function createPinnedWorkflow(gitHubRepoContext: GitHubRepoContext, workflow: Wo
161154
return pinnedWorkflow;
162155
}
163156

157+
async function refreshPinnedWorkflows() {
158+
for (const pinnedWorkflow of pinnedWorkflows) {
159+
await refreshPinnedWorkflow(pinnedWorkflow);
160+
}
161+
}
162+
164163
async function refreshPinnedWorkflow(pinnedWorkflow: PinnedWorkflow) {
165164
const {gitHubRepoContext} = pinnedWorkflow;
166165
const {client} = gitHubRepoContext;
167-
166+
logDebug("Checking for updates to pinned workflow", pinnedWorkflow.workflowName, "in", gitHubRepoContext.name);
168167
try {
169-
const run = await client.paginate(client.actions.listWorkflowRuns, {
170-
owner: gitHubRepoContext.owner,
171-
repo: gitHubRepoContext.name,
172-
workflow_id: pinnedWorkflow.workflowId,
173-
per_page: 1,
174-
page: 1
175-
});
168+
const workflowRunCacheId = `MostRecentRun-${gitHubRepoContext.owner}/${gitHubRepoContext.name}/${pinnedWorkflow.workflowId}`;
169+
const workflowRunResult = await client.conditionalRequest(
170+
client.actions.listWorkflowRuns,
171+
{
172+
owner: gitHubRepoContext.owner,
173+
repo: gitHubRepoContext.name,
174+
workflow_id: pinnedWorkflow.workflowId,
175+
per_page: 1,
176+
page: 1
177+
},
178+
workflowRunCacheId
179+
);
180+
181+
if (workflowRunResult === undefined) {
182+
logDebug("No new runs detected for pinned workflow", pinnedWorkflow.workflowName);
183+
return;
184+
}
176185

177-
const mostRecentRun = run[0];
186+
const mostRecentRun = workflowRunResult.data.workflow_runs[0];
187+
logDebug("Updating pinned workflow", pinnedWorkflow.workflowName, "with most recent run id", mostRecentRun?.id);
178188

179189
runStore.addRun(gitHubRepoContext, mostRecentRun);
180190

0 commit comments

Comments
 (0)