Skip to content

Commit 4b38a3d

Browse files
authored
Refactor: Extract shared context helpers from update_issue and update_pull_request (#6565)
1 parent ba08700 commit 4b38a3d

10 files changed

Lines changed: 309 additions & 63 deletions

.changeset/patch-extract-update-context-helpers.md

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

.github/workflows/changeset.lock.yml

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

.github/workflows/poem-bot.lock.yml

Lines changed: 19 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.github/workflows/smoke-copilot-no-firewall.lock.yml

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

pkg/workflow/js.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ func GetJavaScriptSources() map[string]string {
322322
"generate_git_patch.cjs": generateGitPatchJSScript,
323323
"update_runner.cjs": updateRunnerScript,
324324
"update_pr_description_helpers.cjs": updatePRDescriptionHelpersScript,
325+
"update_context_helpers.cjs": updateContextHelpersScriptSource,
325326
"read_buffer.cjs": readBufferScript,
326327
"mcp_server_core.cjs": mcpServerCoreScript,
327328
"mcp_http_transport.cjs": mcpHTTPTransportScriptSource,
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// @ts-check
2+
/// <reference types="@actions/github-script" />
3+
4+
/**
5+
* Shared context helper functions for update workflows (issues, pull requests, etc.)
6+
*
7+
* This module provides reusable functions for determining if we're in a valid
8+
* context for updating a specific entity type and extracting entity numbers
9+
* from GitHub event payloads.
10+
*
11+
* @module update_context_helpers
12+
*/
13+
14+
/**
15+
* Check if the current context is a valid issue context
16+
* @param {string} eventName - GitHub event name
17+
* @param {any} _payload - GitHub event payload (unused but kept for interface consistency)
18+
* @returns {boolean} Whether context is valid for issue updates
19+
*/
20+
function isIssueContext(eventName, _payload) {
21+
return eventName === "issues" || eventName === "issue_comment";
22+
}
23+
24+
/**
25+
* Get issue number from the context payload
26+
* @param {any} payload - GitHub event payload
27+
* @returns {number|undefined} Issue number or undefined
28+
*/
29+
function getIssueNumber(payload) {
30+
return payload?.issue?.number;
31+
}
32+
33+
/**
34+
* Check if the current context is a valid pull request context
35+
* @param {string} eventName - GitHub event name
36+
* @param {any} payload - GitHub event payload
37+
* @returns {boolean} Whether context is valid for PR updates
38+
*/
39+
function isPRContext(eventName, payload) {
40+
const isPR =
41+
eventName === "pull_request" ||
42+
eventName === "pull_request_review" ||
43+
eventName === "pull_request_review_comment" ||
44+
eventName === "pull_request_target";
45+
46+
// Also check for issue_comment on a PR
47+
const isIssueCommentOnPR = eventName === "issue_comment" && payload?.issue && payload?.issue?.pull_request;
48+
49+
return isPR || !!isIssueCommentOnPR;
50+
}
51+
52+
/**
53+
* Get pull request number from the context payload
54+
* @param {any} payload - GitHub event payload
55+
* @returns {number|undefined} PR number or undefined
56+
*/
57+
function getPRNumber(payload) {
58+
if (payload?.pull_request) {
59+
return payload.pull_request.number;
60+
}
61+
// For issue_comment events on PRs, the PR number is in issue.number
62+
if (payload?.issue && payload?.issue?.pull_request) {
63+
return payload.issue.number;
64+
}
65+
return undefined;
66+
}
67+
68+
module.exports = {
69+
isIssueContext,
70+
getIssueNumber,
71+
isPRContext,
72+
getPRNumber,
73+
};
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import { describe, it, expect } from "vitest";
2+
3+
// Import the functions to test
4+
const { isIssueContext, getIssueNumber, isPRContext, getPRNumber } = require("./update_context_helpers.cjs");
5+
6+
describe("update_context_helpers", () => {
7+
describe("isIssueContext", () => {
8+
it("should return true for issues event", () => {
9+
expect(isIssueContext("issues", {})).toBe(true);
10+
});
11+
12+
it("should return true for issue_comment event", () => {
13+
expect(isIssueContext("issue_comment", {})).toBe(true);
14+
});
15+
16+
it("should return false for pull_request event", () => {
17+
expect(isIssueContext("pull_request", {})).toBe(false);
18+
});
19+
20+
it("should return false for push event", () => {
21+
expect(isIssueContext("push", {})).toBe(false);
22+
});
23+
24+
it("should return false for workflow_dispatch event", () => {
25+
expect(isIssueContext("workflow_dispatch", {})).toBe(false);
26+
});
27+
});
28+
29+
describe("getIssueNumber", () => {
30+
it("should return issue number from payload", () => {
31+
const payload = { issue: { number: 123 } };
32+
expect(getIssueNumber(payload)).toBe(123);
33+
});
34+
35+
it("should return undefined when issue is missing", () => {
36+
const payload = {};
37+
expect(getIssueNumber(payload)).toBeUndefined();
38+
});
39+
40+
it("should return undefined when issue.number is missing", () => {
41+
const payload = { issue: {} };
42+
expect(getIssueNumber(payload)).toBeUndefined();
43+
});
44+
45+
it("should handle null payload gracefully", () => {
46+
expect(getIssueNumber(null)).toBeUndefined();
47+
});
48+
49+
it("should handle undefined payload gracefully", () => {
50+
expect(getIssueNumber(undefined)).toBeUndefined();
51+
});
52+
});
53+
54+
describe("isPRContext", () => {
55+
it("should return true for pull_request event", () => {
56+
expect(isPRContext("pull_request", {})).toBe(true);
57+
});
58+
59+
it("should return true for pull_request_review event", () => {
60+
expect(isPRContext("pull_request_review", {})).toBe(true);
61+
});
62+
63+
it("should return true for pull_request_review_comment event", () => {
64+
expect(isPRContext("pull_request_review_comment", {})).toBe(true);
65+
});
66+
67+
it("should return true for pull_request_target event", () => {
68+
expect(isPRContext("pull_request_target", {})).toBe(true);
69+
});
70+
71+
it("should return true for issue_comment on PR", () => {
72+
const payload = {
73+
issue: {
74+
number: 100,
75+
pull_request: { url: "https://api.github.com/repos/owner/repo/pulls/100" },
76+
},
77+
};
78+
expect(isPRContext("issue_comment", payload)).toBe(true);
79+
});
80+
81+
it("should return false for issue_comment on issue", () => {
82+
const payload = {
83+
issue: {
84+
number: 123,
85+
},
86+
};
87+
expect(isPRContext("issue_comment", payload)).toBe(false);
88+
});
89+
90+
it("should return false for issues event", () => {
91+
expect(isPRContext("issues", {})).toBe(false);
92+
});
93+
94+
it("should return false for push event", () => {
95+
expect(isPRContext("push", {})).toBe(false);
96+
});
97+
98+
it("should return false for workflow_dispatch event", () => {
99+
expect(isPRContext("workflow_dispatch", {})).toBe(false);
100+
});
101+
});
102+
103+
describe("getPRNumber", () => {
104+
it("should return PR number from pull_request", () => {
105+
const payload = { pull_request: { number: 100 } };
106+
expect(getPRNumber(payload)).toBe(100);
107+
});
108+
109+
it("should return PR number from issue with pull_request", () => {
110+
const payload = {
111+
issue: {
112+
number: 200,
113+
pull_request: { url: "https://api.github.com/repos/owner/repo/pulls/200" },
114+
},
115+
};
116+
expect(getPRNumber(payload)).toBe(200);
117+
});
118+
119+
it("should prefer pull_request over issue", () => {
120+
const payload = {
121+
pull_request: { number: 100 },
122+
issue: { number: 200 },
123+
};
124+
expect(getPRNumber(payload)).toBe(100);
125+
});
126+
127+
it("should return undefined when pull_request is missing", () => {
128+
const payload = {};
129+
expect(getPRNumber(payload)).toBeUndefined();
130+
});
131+
132+
it("should return undefined when issue has no pull_request", () => {
133+
const payload = { issue: { number: 123 } };
134+
expect(getPRNumber(payload)).toBeUndefined();
135+
});
136+
137+
it("should handle null payload gracefully", () => {
138+
expect(getPRNumber(null)).toBeUndefined();
139+
});
140+
141+
it("should handle undefined payload gracefully", () => {
142+
expect(getPRNumber(undefined)).toBeUndefined();
143+
});
144+
145+
it("should return undefined when pull_request.number is missing", () => {
146+
const payload = { pull_request: {} };
147+
expect(getPRNumber(payload)).toBeUndefined();
148+
});
149+
150+
it("should return undefined when issue.number is missing", () => {
151+
const payload = {
152+
issue: {
153+
pull_request: { url: "https://api.github.com/repos/owner/repo/pulls/100" },
154+
},
155+
};
156+
expect(getPRNumber(payload)).toBeUndefined();
157+
});
158+
});
159+
160+
describe("Cross-validation", () => {
161+
it("issue_comment on PR should be PR context but not issue context", () => {
162+
const payload = {
163+
issue: {
164+
number: 100,
165+
pull_request: { url: "https://api.github.com/repos/owner/repo/pulls/100" },
166+
},
167+
};
168+
expect(isPRContext("issue_comment", payload)).toBe(true);
169+
expect(isIssueContext("issue_comment", payload)).toBe(true); // Note: issue_comment is valid for both
170+
});
171+
172+
it("issue_comment on issue should be issue context but not PR context", () => {
173+
const payload = {
174+
issue: {
175+
number: 123,
176+
},
177+
};
178+
expect(isIssueContext("issue_comment", payload)).toBe(true);
179+
expect(isPRContext("issue_comment", payload)).toBe(false);
180+
});
181+
});
182+
});

pkg/workflow/js/update_issue.cjs

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,7 @@
22
/// <reference types="@actions/github-script" />
33

44
const { runUpdateWorkflow, createRenderStagedItem, createGetSummaryLine } = require("./update_runner.cjs");
5-
6-
/**
7-
* Check if the current context is a valid issue context
8-
* @param {string} eventName - GitHub event name
9-
* @param {any} _payload - GitHub event payload (unused but kept for interface consistency)
10-
* @returns {boolean} Whether context is valid for issue updates
11-
*/
12-
function isIssueContext(eventName, _payload) {
13-
return eventName === "issues" || eventName === "issue_comment";
14-
}
15-
16-
/**
17-
* Get issue number from the context payload
18-
* @param {any} payload - GitHub event payload
19-
* @returns {number|undefined} Issue number or undefined
20-
*/
21-
function getIssueNumber(payload) {
22-
return payload.issue?.number;
23-
}
5+
const { isIssueContext, getIssueNumber } = require("./update_context_helpers.cjs");
246

257
// Use shared helper for staged preview rendering
268
const renderStagedItem = createRenderStagedItem({

0 commit comments

Comments
 (0)