Skip to content

Commit 1305ce8

Browse files
authored
fix: read current Support closed-ticket status (#12)
* fix(support): read current closed-ticket status * test: anonymize public GitHub examples
1 parent 8d97730 commit 1305ce8

6 files changed

Lines changed: 53 additions & 35 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,13 +218,13 @@ gh2 support view 1234567
218218
gh2 support view 1234567 --scope personal/0 --output json
219219

220220
gh2 support create \
221-
--account "Circles Inc." \
221+
--account "Example Corp." \
222222
--subject "Remove sensitive data from repository history" \
223223
--body-file ./ticket.md
224224

225225
# Submit the reviewed ticket
226226
gh2 support create \
227-
--account "Circles Inc." \
227+
--account "Example Corp." \
228228
--subject "Remove sensitive data from repository history" \
229229
--body-file ./ticket.md \
230230
--yes

skills/guide/SKILL.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ All config files are `github.<stage>.json` where `stage ∈ {local, prod}`:
2222
```json
2323
{
2424
"appId": 2864083,
25-
"name": "circlesac-yg2",
25+
"name": "example-app",
2626
"webhookSecret": "...",
2727
"privateKey": "<base64-encoded PEM>"
2828
}
@@ -198,13 +198,13 @@ gh2 support view 1234567 --scope personal/0 --output json
198198

199199
# Dry run: authenticates and prints the exact ticket without creating it
200200
gh2 support create \
201-
--account "Circles Inc." \
201+
--account "Example Corp." \
202202
--subject "Remove sensitive data from repository history" \
203203
--body-file ./ticket.md
204204

205205
# Submit only after reviewing the dry run
206206
gh2 support create \
207-
--account "Circles Inc." \
207+
--account "Example Corp." \
208208
--subject "Remove sensitive data from repository history" \
209209
--body-file ./ticket.md \
210210
--yes

src/lib/support.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,9 +319,17 @@ export function parseSupportTicketDetails(html: string): SupportTicketDetails {
319319
const ticketId = attribute(ticketTag, "data-ticket-id");
320320
const orgType = attribute(ticketTag, "data-org-type");
321321
const orgId = attribute(ticketTag, "data-org-id");
322-
const status = html
322+
const stateTag = html.match(
323+
/<([a-z][\w-]*)\b[^>]*class="[^"]*\bState\b[^"]*"[^>]*>([\s\S]*?)<\/\1>/i,
324+
);
325+
const stateLabel = stateTag ? htmlToText(stateTag[2] ?? "").toLowerCase() : "";
326+
const legacyState = stateTag?.[0]
323327
.match(/class="[^"]*\bState--(open|closed)\b/i)?.[1]
324328
?.toLowerCase();
329+
const status =
330+
stateLabel === "open" || stateLabel === "closed"
331+
? stateLabel
332+
: legacyState;
325333
if (
326334
!ticketId ||
327335
!orgType ||

tests/delete-parser.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ describe("parseDeleteForm", () => {
6161
describe("confirmValueFromPattern", () => {
6262
it("reconstructs the app name from a case-insensitive char-class pattern", () => {
6363
expect(
64-
confirmValueFromPattern("[cC][iI][rR][cC][lL][eE][sS][aA][cC]-[yY][gG]2"),
65-
).toBe("circlesac-yg2");
64+
confirmValueFromPattern("[eE][xX][aA][mM][pP][lL][eE]-[aA][pP][pP]"),
65+
).toBe("example-app");
6666
});
6767

6868
it("keeps literal characters (digits, hyphens) as-is", () => {

tests/pat.test.ts

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@ const PAT_FORM_HTML = `
2727
<form id="new_user_programmatic_access" action="/settings/personal-access-tokens" method="post">
2828
<input type="hidden" name="authenticity_token" value="a&amp;b">
2929
<input type="text" name="user_programmatic_access[name]" value="Priority reconciler">
30-
<input type="hidden" name="target_name" value="melten-ai">
30+
<input type="hidden" name="target_name" value="example-org">
3131
<template>
3232
<input type="hidden" name="user_programmatic_access[default_expires_at]" value="30">
3333
</template>
3434
<input type="hidden" name="user_programmatic_access[default_expires_at]" value="custom">
3535
<input type="date" name="user_programmatic_access[custom_expires_at]" value="2026-09-01">
3636
<textarea name="user_programmatic_access[description]">Policy automation</textarea>
37-
<include-fragment src="/settings/personal-access-tokens/select-access?target_name=melten-ai&amp;issues=write"></include-fragment>
37+
<include-fragment src="/settings/personal-access-tokens/select-access?target_name=example-org&amp;issues=write"></include-fragment>
3838
</form>
3939
</body>
4040
</html>`;
@@ -47,7 +47,7 @@ const ACCESS_HTML = `
4747
<input type="hidden" name="integration[default_permissions][issues]" value="write">
4848
<input type="hidden" name="integration[default_permissions][metadata]" value="read">
4949
<input type="hidden" name="integration[default_permissions][contents]" value="none">
50-
<remote-input src="/settings/personal-access-tokens/suggestions?target_name=melten-ai&amp;experimental=1"></remote-input>
50+
<remote-input src="/settings/personal-access-tokens/suggestions?target_name=example-org&amp;experimental=1"></remote-input>
5151
<script type="application/json" data-target="react-partial.embeddedData">${JSON.stringify(
5252
{
5353
props: {
@@ -70,19 +70,19 @@ const OWNER_HTML = `
7070
<button data-value="work-admin">work-admin</button>
7171
</li>
7272
<li data-actor-is-organization="true" data-fg-limit="366" data-fg-limit-label="366 days" data-fg-limit-exempt="false">
73-
<button data-value="melten-ai">melten-ai</button>
73+
<button data-value="example-org">example-org</button>
7474
</li>`;
7575

7676
const REPOSITORY_HTML = `
7777
<li>
78-
<button data-value="1245612113">melten-ai/silicon-workbench</button>
79-
<input type="hidden" name="repository_ids[]" value="1245612113">
80-
<span class="owner css-truncate-target">melten-ai</span>/<span class="repo">silicon-workbench</span>
78+
<button data-value="101">example-org/sample-repo</button>
79+
<input type="hidden" name="repository_ids[]" value="101">
80+
<span class="owner css-truncate-target">example-org</span>/<span class="repo">sample-repo</span>
8181
</li>
8282
<li>
83-
<button data-value="1263434240">melten-ai/pcie_gen4_pipe_axis_tl</button>
84-
<input type="hidden" name="repository_ids[]" value="1263434240">
85-
<span class="owner">melten-ai</span>/<span class="repo css-truncate-target">pcie_gen4_pipe_axis_tl</span>
83+
<button data-value="102">example-org/another-repo</button>
84+
<input type="hidden" name="repository_ids[]" value="102">
85+
<span class="owner">example-org</span>/<span class="repo css-truncate-target">another-repo</span>
8686
</li>`;
8787

8888
describe("fine-grained PAT form parsing", () => {
@@ -92,7 +92,7 @@ describe("fine-grained PAT form parsing", () => {
9292
expect(form!.account).toBe("work-admin");
9393
expect(form!.action).toBe("/settings/personal-access-tokens");
9494
expect(form!.accessPath).toBe(
95-
"/settings/personal-access-tokens/select-access?target_name=melten-ai&issues=write",
95+
"/settings/personal-access-tokens/select-access?target_name=example-org&issues=write",
9696
);
9797
expect(form!.fields).toContainEqual(["authenticity_token", "a&b"]);
9898
expect(
@@ -119,7 +119,7 @@ describe("fine-grained PAT form parsing", () => {
119119
maxExpirationLabel: undefined,
120120
},
121121
{
122-
login: "melten-ai",
122+
login: "example-org",
123123
organization: true,
124124
expirationExempt: false,
125125
maxExpirationDays: 366,
@@ -130,11 +130,11 @@ describe("fine-grained PAT form parsing", () => {
130130

131131
it("reads live repository IDs without accepting other owners", () => {
132132
expect(parseRepositoryOptions(REPOSITORY_HTML)).toEqual([
133-
{ id: "1245612113", owner: "melten-ai", name: "silicon-workbench" },
133+
{ id: "101", owner: "example-org", name: "sample-repo" },
134134
{
135-
id: "1263434240",
136-
owner: "melten-ai",
137-
name: "pcie_gen4_pipe_axis_tl",
135+
id: "102",
136+
owner: "example-org",
137+
name: "another-repo",
138138
},
139139
]);
140140
});
@@ -178,15 +178,15 @@ describe("fine-grained PAT request validation", () => {
178178
it("normalizes selected repositories and validates their owner", () => {
179179
expect(
180180
parseRepositorySelection(
181-
"silicon-workbench,melten-ai/pcie_gen4_pipe_axis_tl",
182-
"melten-ai",
181+
"sample-repo,example-org/another-repo",
182+
"example-org",
183183
),
184184
).toEqual({
185185
mode: "selected",
186-
names: ["silicon-workbench", "pcie_gen4_pipe_axis_tl"],
186+
names: ["sample-repo", "another-repo"],
187187
});
188188
expect(() =>
189-
parseRepositorySelection("other/repo", "melten-ai"),
189+
parseRepositorySelection("other/repo", "example-org"),
190190
).toThrow(/does not belong/);
191191
});
192192

@@ -204,18 +204,18 @@ describe("fine-grained PAT request validation", () => {
204204
name: "Priority reconciler",
205205
description: "Policy automation",
206206
reason: "Needed for issue reconciliation",
207-
owner: "melten-ai",
207+
owner: "example-org",
208208
repositories: {
209209
mode: "selected",
210-
names: ["silicon-workbench", "pcie_gen4_pipe_axis_tl"],
210+
names: ["sample-repo", "another-repo"],
211211
},
212212
repositoryOptions: parseRepositoryOptions(REPOSITORY_HTML),
213213
permissions: { issues: "write", metadata: "read" },
214214
});
215215
expect(body.get("install_target")).toBe("selected");
216216
expect(body.getAll("repository_ids[]")).toEqual([
217-
"1245612113",
218-
"1263434240",
217+
"101",
218+
"102",
219219
]);
220220
expect(body.get("integration[default_permissions][issues]")).toBe("write");
221221
expect(body.get("integration[default_permissions][metadata]")).toBe("read");

tests/support.test.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const personal: SupportAccount = {
4040
const organization: SupportAccount = {
4141
...personal,
4242
id: "org-id",
43-
identifier: "Circles Inc.",
43+
identifier: "Example Corp.",
4444
type: "Organization",
4545
};
4646

@@ -83,7 +83,7 @@ describe("Support ticket selection", () => {
8383
});
8484

8585
it("matches an explicit account case-insensitively", () => {
86-
expect(selectSupportAccount(bootstrap.accounts, "circles inc.")).toBe(
86+
expect(selectSupportAccount(bootstrap.accounts, "example corp.")).toBe(
8787
organization,
8888
);
8989
});
@@ -221,8 +221,18 @@ describe("support ticket view", () => {
221221

222222
it("reads a closed ticket without requiring a comment form", () => {
223223
const closed = TICKET_DETAILS_HTML
224-
.replace("State--open", "State--closed")
224+
.replace(
225+
'class="State State--open">Open',
226+
'class="State bgColor-done-emphasis">Closed',
227+
)
225228
.replace(/<form id="js-ticket-comment-form"[\s\S]*?<\/form>/, "");
226229
expect(parseSupportTicketDetails(closed).status).toBe("closed");
227230
});
231+
232+
it("keeps compatibility with the legacy status class", () => {
233+
const legacyClosed = TICKET_DETAILS_HTML
234+
.replace("State--open", "State--closed")
235+
.replace(">Open</span>", "></span>");
236+
expect(parseSupportTicketDetails(legacyClosed).status).toBe("closed");
237+
});
228238
});

0 commit comments

Comments
 (0)