@aholbreich/agent-skills 0.2.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +18 -1
- package/package.json +1 -1
- package/skills/jira-browser-fetch/SKILL.md +25 -2
- package/skills/jira-browser-fetch/references/usage.md +31 -1
- package/skills/jira-browser-fetch/scripts/jira-browser-fetch.js +92 -5
- package/skills/jira-browser-fetch/scripts/lib.js +43 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
Added:
|
|
6
|
+
|
|
7
|
+
- `jira-browser-fetch --backlog URL|BOARD_ID` to fetch all issues from a Jira Software board backlog through the authenticated browser session.
|
|
8
|
+
- Backlog manifests at `raw/jira-board-<board-id>-backlog.json` and a `backlogs` section in `raw/jira-browser-fetch-run.json`.
|
|
9
|
+
- Documentation examples for natural-language user requests that should invoke the skills.
|
|
10
|
+
|
|
3
11
|
## 0.1.0 - 2026-05-06
|
|
4
12
|
|
|
5
13
|
Initial public package structure.
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ This repository is a pure skills package. It currently contains browser-authenti
|
|
|
8
8
|
|
|
9
9
|
| Skill | Purpose |
|
|
10
10
|
|---|---|
|
|
11
|
-
| [`jira-browser-fetch`](skills/jira-browser-fetch/) | Fetch Jira issue JSON, rendered HTML/XML, linked/referenced issues, JQL result sets, and attachments through an authenticated Chrome session. |
|
|
11
|
+
| [`jira-browser-fetch`](skills/jira-browser-fetch/) | Fetch Jira issue JSON, rendered HTML/XML, linked/referenced issues, Jira Software board backlogs, JQL result sets, and attachments through an authenticated Chrome session. |
|
|
12
12
|
| [`confluence-browser-fetch`](skills/confluence-browser-fetch/) | Fetch Confluence page JSON, storage/view HTML, browser HTML, descendants, CQL result sets, and attachments through an authenticated Chrome session. |
|
|
13
13
|
|
|
14
14
|
## Compatibility
|
|
@@ -170,6 +170,23 @@ jira-browser-fetch \
|
|
|
170
170
|
--jql 'assignee = currentUser() ORDER BY updated DESC'
|
|
171
171
|
```
|
|
172
172
|
|
|
173
|
+
Fetch a Jira Software board backlog:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
jira-browser-fetch \
|
|
177
|
+
--server https://example.atlassian.net \
|
|
178
|
+
--raw-dir ./raw \
|
|
179
|
+
--backlog 'https://example.atlassian.net/jira/software/c/projects/ABC/boards/42/backlog?epics=visible'
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Example user requests that should invoke this skill:
|
|
183
|
+
|
|
184
|
+
- "Fetch all Jira issues from this backlog URL into `raw/`."
|
|
185
|
+
- "Archive board 42's Jira backlog for my LLM wiki."
|
|
186
|
+
- "Fetch `PROJ-123` through my browser session and include linked issues."
|
|
187
|
+
- "Pull my assigned Jira issues without asking me for an API token."
|
|
188
|
+
- "Use this JQL and store the raw Jira evidence under the wiki raw folder."
|
|
189
|
+
|
|
173
190
|
## Confluence examples
|
|
174
191
|
|
|
175
192
|
Fetch one page by URL:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aholbreich/agent-skills",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Handcrafted Agent Skills for browser-authenticated Jira and Confluence ingestion, LLM wiki workflows, and developer automation.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: jira-browser-fetch
|
|
3
|
-
description: Fetch Jira issue raw data through an authenticated Chrome browser session when jira-cli/API tokens do not work, especially with Microsoft/SSO. Use to archive Jira issues, linked tickets, rendered HTML/XML, remote links, and attachments into a raw wiki folder.
|
|
3
|
+
description: Fetch Jira issue raw data through an authenticated Chrome browser session when jira-cli/API tokens do not work, especially with Microsoft/SSO. Use to archive Jira issues, Jira Software board backlogs, JQL result sets, linked tickets, rendered HTML/XML, remote links, and attachments into a raw wiki folder.
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: Agent Skills standard. Tested with Pi; installable into Claude Code, Codex, OpenClaw/generic .agents skills directories. Requires Node.js 22+ with built-in fetch/WebSocket and Google Chrome/Chromium with remote debugging. No npm dependencies.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Jira Browser Fetch
|
|
9
9
|
|
|
10
|
-
Use this skill when Jira API-token authentication fails or the organization uses Microsoft/SSO and the user wants Jira issues archived into a local raw/wiki folder.
|
|
10
|
+
Use this skill when Jira API-token authentication fails or the organization uses Microsoft/SSO and the user wants Jira issues, Jira Software board backlogs, or JQL result sets archived into a local raw/wiki folder.
|
|
11
11
|
|
|
12
12
|
The bundled script opens/reuses Chrome with a dedicated profile, lets the user complete SSO once, extracts Jira cookies via Chrome DevTools, and fetches Jira REST/HTML/XML/attachments into a raw directory.
|
|
13
13
|
|
|
@@ -33,12 +33,23 @@ Important options:
|
|
|
33
33
|
--depth N recursion depth for connected tickets
|
|
34
34
|
--scan-text find issue keys in JSON/XML/HTML text, not only formal Jira links
|
|
35
35
|
--jql JQL search Jira with JQL and fetch all matching issues
|
|
36
|
+
--backlog URL|ID fetch all issues from a Jira Software board backlog URL or board id
|
|
36
37
|
--assignee-me fetch all issues assigned to current Jira user
|
|
37
38
|
--max-attachment-size S skip attachment files larger than S (default 5mb; use unlimited to disable)
|
|
38
39
|
--prefix A,B,C only follow keys with these project prefixes
|
|
39
40
|
--wait SEC SSO/session wait timeout per issue
|
|
40
41
|
```
|
|
41
42
|
|
|
43
|
+
## Example User Requests
|
|
44
|
+
|
|
45
|
+
Use this skill for user requests like:
|
|
46
|
+
|
|
47
|
+
- "Fetch Jira issue `PROJ-123` into `raw/` through my browser session."
|
|
48
|
+
- "Archive this Jira backlog for my LLM wiki: `https://example.atlassian.net/jira/software/c/projects/ABC/boards/42/backlog?epics=visible`."
|
|
49
|
+
- "Fetch all Jira issues matching this JQL into the wiki raw folder."
|
|
50
|
+
- "Pull my assigned Jira issues without asking me for an API token."
|
|
51
|
+
- "Fetch this ticket and all linked tickets, including attachments under the default size limit."
|
|
52
|
+
|
|
42
53
|
## Typical Workflow
|
|
43
54
|
|
|
44
55
|
1. Identify raw directory.
|
|
@@ -64,6 +75,12 @@ scripts/jira-browser-fetch.js \
|
|
|
64
75
|
--server https://example.atlassian.net \
|
|
65
76
|
--raw-dir ./raw \
|
|
66
77
|
--assignee-me
|
|
78
|
+
|
|
79
|
+
# Fetch every issue currently visible in a Jira Software board backlog:
|
|
80
|
+
scripts/jira-browser-fetch.js \
|
|
81
|
+
--server https://example.atlassian.net \
|
|
82
|
+
--raw-dir ./raw \
|
|
83
|
+
--backlog 'https://example.atlassian.net/jira/software/c/projects/ABC/boards/42/backlog?epics=visible'
|
|
67
84
|
```
|
|
68
85
|
|
|
69
86
|
## Output Layout
|
|
@@ -88,6 +105,12 @@ A run manifest is written to:
|
|
|
88
105
|
raw/jira-browser-fetch-run.json
|
|
89
106
|
```
|
|
90
107
|
|
|
108
|
+
Backlog fetches also write:
|
|
109
|
+
|
|
110
|
+
```text
|
|
111
|
+
raw/jira-board-<board-id>-backlog.json
|
|
112
|
+
```
|
|
113
|
+
|
|
91
114
|
## Installation / PATH
|
|
92
115
|
|
|
93
116
|
The skill can be used directly by path. Optionally install a convenience symlink:
|
|
@@ -80,6 +80,26 @@ scripts/jira-browser-fetch.js \
|
|
|
80
80
|
--jql "assignee = currentUser() AND statusCategory != Done ORDER BY updated DESC"
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
+
Fetch every issue currently visible in a Jira Software board backlog:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
scripts/jira-browser-fetch.js \
|
|
87
|
+
--server https://example.atlassian.net \
|
|
88
|
+
--raw-dir /path/to/wiki/raw \
|
|
89
|
+
--backlog 'https://example.atlassian.net/jira/software/c/projects/ABC/boards/42/backlog?epics=visible'
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
If you already know the board id, this is equivalent:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
scripts/jira-browser-fetch.js \
|
|
96
|
+
--server https://example.atlassian.net \
|
|
97
|
+
--raw-dir /path/to/wiki/raw \
|
|
98
|
+
--backlog 42
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
A backlog fetch writes `raw/jira-board-<board-id>-backlog.json` with the ordered backlog issue keys and adds a `backlogs` section to `raw/jira-browser-fetch-run.json`.
|
|
102
|
+
|
|
83
103
|
Use a shorter wait when the browser session is already logged in:
|
|
84
104
|
|
|
85
105
|
```bash
|
|
@@ -102,11 +122,21 @@ Default max attachment download size is `5mb`. Use `--max-attachment-size unlimi
|
|
|
102
122
|
| `JIRA_RAW_DIR` | Default output raw directory |
|
|
103
123
|
| `JIRA_CHROME_DEBUG_PORT` | Chrome DevTools port, default `9223` |
|
|
104
124
|
| `JIRA_FETCH_WAIT_SEC` | Wait timeout per issue, default `900` |
|
|
105
|
-
| `JIRA_MAX_SEARCH_RESULTS` | Max issues added per JQL search, default `1000` |
|
|
125
|
+
| `JIRA_MAX_SEARCH_RESULTS` | Max issues added per JQL or backlog search, default `1000` |
|
|
106
126
|
| `JIRA_MAX_ATTACHMENT_SIZE` / `JIRA_MAX_ATTACHMENT_BYTES` | Max attachment download size, default `5mb`; skipped files are listed in `attachments.json` |
|
|
107
127
|
| `JIRA_CHROME_PROFILE` | Dedicated Chrome profile dir |
|
|
108
128
|
| `CHROME` | Chrome executable path |
|
|
109
129
|
|
|
130
|
+
## Example user requests
|
|
131
|
+
|
|
132
|
+
Agents should invoke this skill for requests such as:
|
|
133
|
+
|
|
134
|
+
- "Fetch all Jira issues from this backlog URL into `/raw`."
|
|
135
|
+
- "Archive board 42's Jira backlog for my LLM wiki."
|
|
136
|
+
- "Fetch my assigned Jira issues through the browser because API tokens do not work."
|
|
137
|
+
- "Fetch `PROJ-123` and all connected tickets with attachments."
|
|
138
|
+
- "Use this JQL and store the raw Jira evidence under the wiki raw folder."
|
|
139
|
+
|
|
110
140
|
## Troubleshooting
|
|
111
141
|
|
|
112
142
|
### `no Jira cookies yet`
|
|
@@ -6,7 +6,15 @@ const fsp = require('fs/promises');
|
|
|
6
6
|
const os = require('os');
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const { spawn } = require('child_process');
|
|
9
|
-
const {
|
|
9
|
+
const {
|
|
10
|
+
parseSize,
|
|
11
|
+
formatBytes,
|
|
12
|
+
safeName,
|
|
13
|
+
issueKeysFromText,
|
|
14
|
+
parseBacklogInput,
|
|
15
|
+
backlogApiUrl,
|
|
16
|
+
issueKeysFromAgilePage,
|
|
17
|
+
} = require('./lib');
|
|
10
18
|
|
|
11
19
|
function usage() {
|
|
12
20
|
console.log(`Usage: jira-browser-fetch [ISSUE-KEY ...] [options]
|
|
@@ -21,8 +29,9 @@ Options:
|
|
|
21
29
|
--depth N Connected fetch depth (default: 1 with --connected, otherwise 0)
|
|
22
30
|
--scan-text Include issue keys found anywhere in issue JSON/XML/HTML text
|
|
23
31
|
--jql JQL Search Jira with JQL and fetch all matching issues
|
|
32
|
+
--backlog URL|BOARD_ID Fetch all issues from a Jira Software board backlog URL or board id
|
|
24
33
|
--assignee-me Fetch all issues assigned to current Jira user
|
|
25
|
-
--max-search-results N Max issues to add per JQL search (default: 1000)
|
|
34
|
+
--max-search-results N Max issues to add per JQL/backlog search (default: 1000)
|
|
26
35
|
--max-attachment-size S Skip attachment downloads larger than S (default: 5mb; use unlimited to disable)
|
|
27
36
|
--prefix A,B,C Only fetch referenced keys with these project prefixes
|
|
28
37
|
--wait SEC Wait time for SSO/session per issue (default: 900)
|
|
@@ -37,7 +46,8 @@ Examples:
|
|
|
37
46
|
jira-browser-fetch SWING-4770 --raw-dir /path/wiki/raw
|
|
38
47
|
jira-browser-fetch SWING-4770 --connected --prefix SWING,SSD,EC --raw-dir ./raw
|
|
39
48
|
jira-browser-fetch --assignee-me --raw-dir ./raw
|
|
40
|
-
|
|
49
|
+
jira-browser-fetch --backlog 'https://example.atlassian.net/jira/software/c/projects/ABC/boards/42/backlog?epics=visible' --raw-dir ./raw
|
|
50
|
+
JIRA_SERVER=https://example.atlassian.net jira-browser-fetch --backlog 42 --connected
|
|
41
51
|
`);
|
|
42
52
|
}
|
|
43
53
|
|
|
@@ -51,6 +61,7 @@ const opts = {
|
|
|
51
61
|
depth: undefined,
|
|
52
62
|
scanText: false,
|
|
53
63
|
jqls: [],
|
|
64
|
+
backlogs: [],
|
|
54
65
|
assigneeMe: false,
|
|
55
66
|
maxSearchResults: Number(process.env.JIRA_MAX_SEARCH_RESULTS || 1000),
|
|
56
67
|
maxAttachmentBytes: parseSize(process.env.JIRA_MAX_ATTACHMENT_SIZE || process.env.JIRA_MAX_ATTACHMENT_BYTES || '5mb'),
|
|
@@ -70,6 +81,7 @@ for (let i = 2; i < process.argv.length; i++) {
|
|
|
70
81
|
else if (a === '--depth') opts.depth = Number(process.argv[++i]);
|
|
71
82
|
else if (a === '--scan-text') opts.scanText = true;
|
|
72
83
|
else if (a === '--jql') opts.jqls.push(process.argv[++i]);
|
|
84
|
+
else if (a === '--backlog') opts.backlogs.push(process.argv[++i]);
|
|
73
85
|
else if (a === '--assignee-me') opts.assigneeMe = true;
|
|
74
86
|
else if (a === '--max-search-results') opts.maxSearchResults = Number(process.argv[++i]);
|
|
75
87
|
else if (a === '--max-attachment-size') opts.maxAttachmentBytes = parseSize(process.argv[++i]);
|
|
@@ -84,7 +96,7 @@ for (let i = 2; i < process.argv.length; i++) {
|
|
|
84
96
|
else { console.error(`Unknown argument: ${a}`); process.exit(2); }
|
|
85
97
|
}
|
|
86
98
|
|
|
87
|
-
if (!issues.length && !opts.jqls.length && !opts.assigneeMe) { usage(); process.exit(2); }
|
|
99
|
+
if (!issues.length && !opts.jqls.length && !opts.backlogs.length && !opts.assigneeMe) { usage(); process.exit(2); }
|
|
88
100
|
if (opts.assigneeMe) opts.jqls.push('assignee = currentUser() ORDER BY updated DESC');
|
|
89
101
|
if (opts.depth === undefined) opts.depth = opts.connected ? 1 : 0;
|
|
90
102
|
if (opts.depth > 0) opts.connected = true;
|
|
@@ -259,6 +271,65 @@ async function searchJql(jql) {
|
|
|
259
271
|
return [...new Set(found)];
|
|
260
272
|
}
|
|
261
273
|
|
|
274
|
+
async function fetchBacklogPageWithWait(url) {
|
|
275
|
+
const deadline = Date.now() + opts.waitSec * 1000;
|
|
276
|
+
let last = '';
|
|
277
|
+
while (Date.now() < deadline) {
|
|
278
|
+
try {
|
|
279
|
+
const cookie = await getCookieHeader();
|
|
280
|
+
if (!cookie) {
|
|
281
|
+
last = 'no Jira cookies yet';
|
|
282
|
+
} else {
|
|
283
|
+
const result = await fetchJson(url, cookie, 'application/json');
|
|
284
|
+
if (result.status === 200 && result.json && Array.isArray(result.json.issues)) return result.json;
|
|
285
|
+
last = `HTTP ${result.status} ${(result.text || '').slice(0, 180).replace(/\s+/g, ' ')}`;
|
|
286
|
+
}
|
|
287
|
+
} catch (e) { last = e.message; }
|
|
288
|
+
process.stdout.write(`\r${new Date().toLocaleTimeString()} waiting for authenticated Jira backlog session: ${last.padEnd(120).slice(0, 120)}`);
|
|
289
|
+
await sleep(3000);
|
|
290
|
+
}
|
|
291
|
+
process.stdout.write('\n');
|
|
292
|
+
throw new Error(`Could not fetch Jira backlog. Last result: ${last}`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async function searchBacklog(input) {
|
|
296
|
+
const backlog = parseBacklogInput(input, opts.server);
|
|
297
|
+
await ensureBrowser(backlog.browseUrl);
|
|
298
|
+
console.log(`If prompted in Chrome, complete SSO for: ${backlog.browseUrl}`);
|
|
299
|
+
console.log(`Waiting up to ${opts.waitSec}s for Jira backlog access...`);
|
|
300
|
+
|
|
301
|
+
const found = [];
|
|
302
|
+
let startAt = 0;
|
|
303
|
+
const pageSize = Math.min(100, Math.max(1, opts.maxSearchResults || 1000));
|
|
304
|
+
|
|
305
|
+
while (found.length < opts.maxSearchResults) {
|
|
306
|
+
const limit = Math.min(pageSize, opts.maxSearchResults - found.length);
|
|
307
|
+
const url = backlogApiUrl(opts.server, backlog.boardId, startAt, limit);
|
|
308
|
+
const page = await fetchBacklogPageWithWait(url);
|
|
309
|
+
const keys = issueKeysFromAgilePage(page);
|
|
310
|
+
for (const key of keys) found.push(key);
|
|
311
|
+
console.log(`Fetched backlog page board=${backlog.boardId} startAt=${startAt}, issues=${keys.length}${typeof page.total === 'number' ? `, total=${page.total}` : ''}`);
|
|
312
|
+
if (page.isLast === true) break;
|
|
313
|
+
if (typeof page.total === 'number' && startAt + keys.length >= page.total) break;
|
|
314
|
+
if (!keys.length) break;
|
|
315
|
+
startAt += keys.length;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const keys = [...new Set(found)];
|
|
319
|
+
const manifest = {
|
|
320
|
+
fetchedAt: new Date().toISOString(),
|
|
321
|
+
server: opts.server,
|
|
322
|
+
boardId: backlog.boardId,
|
|
323
|
+
source: backlog.source,
|
|
324
|
+
browseUrl: backlog.browseUrl,
|
|
325
|
+
endpoint: `/rest/agile/1.0/board/${backlog.boardId}/backlog`,
|
|
326
|
+
issueCount: keys.length,
|
|
327
|
+
issues: keys,
|
|
328
|
+
};
|
|
329
|
+
await fsp.writeFile(path.join(opts.rawDir, `jira-board-${backlog.boardId}-backlog.json`), JSON.stringify(manifest, null, 2));
|
|
330
|
+
return manifest;
|
|
331
|
+
}
|
|
332
|
+
|
|
262
333
|
function addKey(set, key) {
|
|
263
334
|
if (!key) return;
|
|
264
335
|
key = String(key).toUpperCase();
|
|
@@ -455,6 +526,22 @@ async function main() {
|
|
|
455
526
|
const seen = new Set();
|
|
456
527
|
const failed = [];
|
|
457
528
|
const searches = [];
|
|
529
|
+
const backlogs = [];
|
|
530
|
+
|
|
531
|
+
for (const backlogInput of opts.backlogs) {
|
|
532
|
+
console.log(`\n===== Fetching Jira backlog: ${backlogInput} =====`);
|
|
533
|
+
try {
|
|
534
|
+
const backlog = await searchBacklog(backlogInput);
|
|
535
|
+
backlogs.push(backlog);
|
|
536
|
+
console.log(`Backlog board ${backlog.boardId} matched ${backlog.issueCount} issue(s): ${backlog.issues.join(' ') || '(none)'}`);
|
|
537
|
+
for (const key of backlog.issues) {
|
|
538
|
+
if (!queue.some(q => q.key === key)) queue.push({ key, depth: 0, from: `Backlog board ${backlog.boardId}` });
|
|
539
|
+
}
|
|
540
|
+
} catch (e) {
|
|
541
|
+
failed.push({ key: `BACKLOG: ${backlogInput}`, error: e.message });
|
|
542
|
+
console.error(`BACKLOG FAILED: ${e.message}`);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
458
545
|
|
|
459
546
|
for (const jql of opts.jqls) {
|
|
460
547
|
console.log(`\n===== Searching JQL: ${jql} =====`);
|
|
@@ -489,7 +576,7 @@ async function main() {
|
|
|
489
576
|
}
|
|
490
577
|
}
|
|
491
578
|
|
|
492
|
-
const runMeta = { fetchedAt: new Date().toISOString(), server: opts.server, rawDir: opts.rawDir, requested: issues, searches, fetched: [...seen].filter(k => !failed.some(f => f.key === k)), failed };
|
|
579
|
+
const runMeta = { fetchedAt: new Date().toISOString(), server: opts.server, rawDir: opts.rawDir, requested: issues, searches, backlogs, fetched: [...seen].filter(k => !failed.some(f => f.key === k)), failed };
|
|
493
580
|
await fsp.writeFile(path.join(opts.rawDir, 'jira-browser-fetch-run.json'), JSON.stringify(runMeta, null, 2));
|
|
494
581
|
|
|
495
582
|
if (failed.length) {
|
|
@@ -40,6 +40,46 @@ function shouldSkipAttachment(size, maxAttachmentBytes) {
|
|
|
40
40
|
return Number.isFinite(n) && n > maxAttachmentBytes;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
function parseBacklogInput(input, server = '') {
|
|
44
|
+
if (!input) throw new Error('Missing Jira backlog URL or board id');
|
|
45
|
+
const value = String(input).trim();
|
|
46
|
+
const numeric = value.match(/^\d+$/);
|
|
47
|
+
if (numeric) {
|
|
48
|
+
return {
|
|
49
|
+
boardId: Number(value),
|
|
50
|
+
source: value,
|
|
51
|
+
browseUrl: server ? `${String(server).replace(/\/$/, '')}/jira/software/c/boards/${value}/backlog` : value,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let url;
|
|
56
|
+
try {
|
|
57
|
+
url = new URL(value);
|
|
58
|
+
} catch {
|
|
59
|
+
throw new Error(`Invalid Jira backlog URL or board id: ${input}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const m = url.pathname.match(/\/boards\/(\d+)(?:\/backlog)?\b/);
|
|
63
|
+
if (!m) throw new Error(`Could not parse Jira board id from backlog URL: ${input}`);
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
boardId: Number(m[1]),
|
|
67
|
+
source: value,
|
|
68
|
+
browseUrl: value,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function backlogApiUrl(server, boardId, startAt, maxResults) {
|
|
73
|
+
const base = String(server || '').replace(/\/$/, '');
|
|
74
|
+
const params = new URLSearchParams({ startAt: String(startAt), maxResults: String(maxResults) });
|
|
75
|
+
return `${base}/rest/agile/1.0/board/${boardId}/backlog?${params}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function issueKeysFromAgilePage(page) {
|
|
79
|
+
const issues = page && Array.isArray(page.issues) ? page.issues : [];
|
|
80
|
+
return issues.map(issue => issue && issue.key).filter(Boolean);
|
|
81
|
+
}
|
|
82
|
+
|
|
43
83
|
module.exports = {
|
|
44
84
|
DEFAULT_MAX_ATTACHMENT_BYTES,
|
|
45
85
|
parseSize,
|
|
@@ -47,4 +87,7 @@ module.exports = {
|
|
|
47
87
|
safeName,
|
|
48
88
|
issueKeysFromText,
|
|
49
89
|
shouldSkipAttachment,
|
|
90
|
+
parseBacklogInput,
|
|
91
|
+
backlogApiUrl,
|
|
92
|
+
issueKeysFromAgilePage,
|
|
50
93
|
};
|