Compare commits
1 Commits
feature/re
...
fix/jira-a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a179f19a1 |
@@ -276,14 +276,15 @@ function jiraDelete(urlPath, options) {
|
||||
* @param {string[]} [fields] - Jira field names to return
|
||||
*/
|
||||
async function getIssue(issueKey, fields) {
|
||||
const fieldList = (fields || DEFAULT_FIELDS).join(',');
|
||||
const res = await jiraGet(
|
||||
`/rest/api/2/issue/${encodeURIComponent(issueKey)}?fields=${encodeURIComponent(fieldList)}`
|
||||
);
|
||||
if (res.status === 200) {
|
||||
return { ok: true, data: JSON.parse(res.body) };
|
||||
const jql = `key = "${issueKey}" AND project = ${JIRA_PROJECT_KEY}`;
|
||||
const result = await searchIssues(jql, { fields: fields || DEFAULT_FIELDS, maxResults: 1, startAt: 0 });
|
||||
if (result.ok && result.data.issues && result.data.issues.length > 0) {
|
||||
return { ok: true, data: result.data.issues[0] };
|
||||
}
|
||||
return { ok: false, status: res.status, body: res.body, rateLimited: res.rateLimited };
|
||||
if (result.ok && (!result.data.issues || result.data.issues.length === 0)) {
|
||||
return { ok: false, status: 404, body: 'Issue not found' };
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -303,7 +304,7 @@ async function searchIssuesByKeys(issueKeys, opts) {
|
||||
// or similar, but key-based search is inherently scoped. We add updated
|
||||
// clause for compliance.
|
||||
const keyList = issueKeys.map(k => `"${k}"`).join(', ');
|
||||
const jql = `key in (${keyList}) AND updated >= -24h`;
|
||||
const jql = `key in (${keyList}) AND updated >= -24h AND project = ${JIRA_PROJECT_KEY}`;
|
||||
const fields = (opts && opts.fields) || DEFAULT_FIELDS;
|
||||
const maxResults = Math.min((opts && opts.maxResults) || 1000, 1000);
|
||||
|
||||
@@ -327,8 +328,10 @@ async function searchIssues(jql, opts) {
|
||||
const maxResults = Math.min((opts && opts.maxResults) || 1000, 1000);
|
||||
const fields = (opts && opts.fields) || DEFAULT_FIELDS;
|
||||
|
||||
const body = { jql, startAt, maxResults, fields };
|
||||
const res = await jiraPost('/rest/api/2/search', body);
|
||||
const fieldList = encodeURIComponent(fields.join(','));
|
||||
const encodedJql = encodeURIComponent(jql);
|
||||
const queryString = `?jql=${encodedJql}&fields=${fieldList}&maxResults=${maxResults}&startAt=${startAt}`;
|
||||
const res = await jiraGet('/rest/api/2/search' + queryString);
|
||||
if (res.status === 200) {
|
||||
return { ok: true, data: JSON.parse(res.body) };
|
||||
}
|
||||
|
||||
@@ -273,7 +273,7 @@ async function testJqlSearch() {
|
||||
assert(projectKey, 'JIRA_PROJECT_KEY must be set');
|
||||
|
||||
// Use a broad time window to ensure results even on a quiet project
|
||||
const jql = `project = ${projectKey} ORDER BY updated DESC`;
|
||||
const jql = `project = ${projectKey} AND updated >= -24h ORDER BY updated DESC`;
|
||||
logInfo('Searching with JQL:', jql);
|
||||
|
||||
const result = await jiraApi.searchIssues(jql, { maxResults: 10 });
|
||||
@@ -306,6 +306,8 @@ async function testBulkKeySearch() {
|
||||
const result = await jiraApi.searchIssuesByKeys(keys);
|
||||
assert(result.ok, 'Bulk key search should succeed. Got HTTP ' + (result.status || '') + ': ' + ((result.body || '').substring(0, 500)));
|
||||
|
||||
logInfo('Bulk search uses project-scoped JQL with project = ' + jiraApi.JIRA_PROJECT_KEY);
|
||||
|
||||
const found = (result.data.issues || []).map(i => i.key);
|
||||
logInfo('Found issues:', found);
|
||||
assert(found.includes(createdIssueKey), 'Should find the created issue');
|
||||
@@ -350,7 +352,7 @@ async function main() {
|
||||
// Run tests in order — later tests depend on the created issue
|
||||
if (await runTest('1. Connection Test (GET /myself)', testConnection)) passed++; else failed++;
|
||||
if (await runTest('2. Create Issue (POST /issue)', testCreateIssue)) passed++; else failed++;
|
||||
if (await runTest('3. Get Single Issue (GET /issue/{key})', testGetIssue)) passed++; else failed++;
|
||||
if (await runTest('3. Get Single Issue (JQL search)', testGetIssue)) passed++; else failed++;
|
||||
if (await runTest('4. Update Issue (PUT /issue/{key})', testUpdateIssue)) passed++; else failed++;
|
||||
if (await runTest('5. Add Comment (POST /issue/{key}/comment)', testAddComment)) passed++; else failed++;
|
||||
|
||||
@@ -362,7 +364,7 @@ async function main() {
|
||||
await testTransitionIssue(transitions);
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (await runTest('8. JQL Search (POST /search)', testJqlSearch)) passed++; else failed++;
|
||||
if (await runTest('8. JQL Search (GET /search)', testJqlSearch)) passed++; else failed++;
|
||||
if (await runTest('9. Bulk Key Search (searchIssuesByKeys)', testBulkKeySearch)) passed++; else failed++;
|
||||
if (await runTest('10. Rate Limit Status', testRateLimitStatus)) passed++; else failed++;
|
||||
|
||||
|
||||
@@ -19,8 +19,9 @@ All API calls are made from a single Node.js backend process. The integration us
|
||||
| Inter-request delay — writes | 2 seconds minimum between PUT/POST/DELETE requests |
|
||||
| Explicit field lists | Every GET includes `?fields=` parameter; `/rest/api/2/field` is blocked |
|
||||
| No bulk updates | Issues are updated one at a time; `/rest/api/2/issue/bulk` is blocked |
|
||||
| Bulk reads via JQL | Multi-ticket sync uses a single `POST /rest/api/2/search` with JQL, not per-issue GETs |
|
||||
| JQL scoping | All recurring JQL queries include `updated >= -Xh` clause |
|
||||
| Bulk reads via JQL | Multi-ticket sync uses a single `GET /rest/api/2/search` with JQL query parameters, not per-issue GETs |
|
||||
| Single-issue fetch via JQL | `GET /rest/api/2/search?jql=key="KEY" AND project=<KEY>&fields=...&maxResults=1` |
|
||||
| JQL scoping | All recurring JQL queries include `updated >= -Xh` clause and `project = <KEY>` scoping |
|
||||
| `maxResults` cap | Search queries capped at 1 000 results per page |
|
||||
|
||||
---
|
||||
@@ -52,11 +53,11 @@ All API calls are made from a single Node.js backend process. The integration us
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Endpoint** | `GET /rest/api/2/issue/{issueKey}?fields=summary,status,assignee,created,updated,priority,issuetype,project,resolution` |
|
||||
| **Endpoint** | `GET /rest/api/2/search?jql=key="ISSUE-KEY" AND project=<KEY>&fields=summary,status,assignee,created,updated,priority,issuetype,project,resolution&maxResults=1` |
|
||||
| **Trigger** | User clicks "Sync" on a single Jira ticket row |
|
||||
| **Frequency** | Manual, estimated 10–30 per day |
|
||||
| **Purpose** | Refresh a single ticket's status and summary from Jira |
|
||||
| **Notes** | Fields are always specified explicitly per Charter requirement |
|
||||
| **Purpose** | Refresh a single ticket's status and summary from Jira via JQL search |
|
||||
| **Notes** | Uses JQL-based lookup instead of single-issue GET per Charter compliance. Fields are always specified explicitly. |
|
||||
|
||||
### 4. Update Issue
|
||||
|
||||
@@ -99,23 +100,23 @@ All API calls are made from a single Node.js backend process. The integration us
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Endpoint** | `POST /rest/api/2/search` |
|
||||
| **Endpoint** | `GET /rest/api/2/search?jql=...&fields=...&maxResults=...&startAt=...` |
|
||||
| **Trigger** | Admin clicks "Sync All" on the Jira tickets panel |
|
||||
| **Frequency** | Manual, estimated 1–3 times per day |
|
||||
| **Purpose** | Bulk-refresh all tracked tickets in a single request instead of per-issue GETs |
|
||||
| **JQL pattern** | `key in ("KEY-1", "KEY-2", ...) AND updated >= -24h` |
|
||||
| **JQL pattern** | `key in ("KEY-1", "KEY-2", ...) AND updated >= -24h AND project = <KEY>` |
|
||||
| **Fields requested** | `summary, status, assignee, created, updated, priority, issuetype, project, resolution` |
|
||||
| **Batch size** | 100 keys per JQL query; multiple batches if needed |
|
||||
| **Notes** | Stops early if rate limit budget is running low (burst remaining <= 5 or daily remaining <= 10) |
|
||||
| **Notes** | Uses GET with URL-encoded query parameters per Charter compliance. Stops early if rate limit budget is running low (burst remaining <= 5 or daily remaining <= 10) |
|
||||
|
||||
### 9. Issue Lookup
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Endpoint** | `GET /rest/api/2/issue/{issueKey}?fields=...` |
|
||||
| **Endpoint** | `GET /rest/api/2/search?jql=key="ISSUE-KEY" AND project=<KEY>&fields=...&maxResults=1` |
|
||||
| **Trigger** | User looks up a Jira issue by key from the dashboard search |
|
||||
| **Frequency** | Manual, estimated 5–15 per day |
|
||||
| **Purpose** | Quick lookup of any Jira issue to view its current state |
|
||||
| **Purpose** | Quick lookup of any Jira issue to view its current state via JQL search |
|
||||
|
||||
---
|
||||
|
||||
@@ -130,7 +131,7 @@ All API calls are made from a single Node.js backend process. The integration us
|
||||
| Add comment | 5–15 | POST | 2s |
|
||||
| Get transitions | 5–10 | GET | 1s |
|
||||
| Transition issue | 5–10 | POST | 2s |
|
||||
| JQL search (sync) | 1–5 | POST | 2s |
|
||||
| JQL search (sync) | 1–5 | GET | 1s |
|
||||
| Issue lookup | 5–15 | GET | 1s |
|
||||
| **Total estimated** | **43–120** | | |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user