@jagadeesh52423/atlassian-mcp-server 1.0.2 → 1.1.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/dist/clients/base-client.d.ts +26 -0
- package/dist/clients/base-client.d.ts.map +1 -0
- package/dist/clients/base-client.js +58 -0
- package/dist/clients/base-client.js.map +1 -0
- package/dist/clients/bitbucket-client.d.ts +17 -0
- package/dist/clients/bitbucket-client.d.ts.map +1 -0
- package/dist/clients/bitbucket-client.js +36 -0
- package/dist/clients/bitbucket-client.js.map +1 -0
- package/dist/clients/confluence-client.d.ts +8 -0
- package/dist/clients/confluence-client.d.ts.map +1 -0
- package/dist/clients/confluence-client.js +16 -0
- package/dist/clients/confluence-client.js.map +1 -0
- package/dist/clients/index.d.ts +5 -0
- package/dist/clients/index.d.ts.map +1 -0
- package/dist/clients/index.js +5 -0
- package/dist/clients/index.js.map +1 -0
- package/dist/clients/jira-client.d.ts +8 -0
- package/dist/clients/jira-client.d.ts.map +1 -0
- package/dist/clients/jira-client.js +16 -0
- package/dist/clients/jira-client.js.map +1 -0
- package/dist/constants.d.ts +67 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +78 -0
- package/dist/constants.js.map +1 -0
- package/dist/error-handler.d.ts +83 -0
- package/dist/error-handler.d.ts.map +1 -0
- package/dist/error-handler.js +141 -0
- package/dist/error-handler.js.map +1 -0
- package/dist/formatters/adf-formatter.d.ts +25 -0
- package/dist/formatters/adf-formatter.d.ts.map +1 -0
- package/dist/formatters/adf-formatter.js +609 -0
- package/dist/formatters/adf-formatter.js.map +1 -0
- package/dist/formatters/index.d.ts +2 -0
- package/dist/formatters/index.d.ts.map +1 -0
- package/dist/formatters/index.js +2 -0
- package/dist/formatters/index.js.map +1 -0
- package/dist/handlers/bitbucket-handlers.d.ts +94 -0
- package/dist/handlers/bitbucket-handlers.d.ts.map +1 -0
- package/dist/handlers/bitbucket-handlers.js +251 -0
- package/dist/handlers/bitbucket-handlers.js.map +1 -0
- package/dist/handlers/confluence-handlers.d.ts +106 -0
- package/dist/handlers/confluence-handlers.d.ts.map +1 -0
- package/dist/handlers/confluence-handlers.js +340 -0
- package/dist/handlers/confluence-handlers.js.map +1 -0
- package/dist/handlers/index.d.ts +4 -0
- package/dist/handlers/index.d.ts.map +1 -0
- package/dist/handlers/index.js +4 -0
- package/dist/handlers/index.js.map +1 -0
- package/dist/handlers/jira-handlers.d.ts +237 -0
- package/dist/handlers/jira-handlers.d.ts.map +1 -0
- package/dist/handlers/jira-handlers.js +816 -0
- package/dist/handlers/jira-handlers.js.map +1 -0
- package/dist/index.js +189 -2536
- package/dist/index.js.map +1 -1
- package/dist/services/bitbucket.d.ts +11 -1
- package/dist/services/bitbucket.d.ts.map +1 -1
- package/dist/services/bitbucket.js +78 -0
- package/dist/services/bitbucket.js.map +1 -1
- package/dist/services/confluence.d.ts +5 -0
- package/dist/services/confluence.d.ts.map +1 -1
- package/dist/services/confluence.js +59 -4
- package/dist/services/confluence.js.map +1 -1
- package/dist/services/jira.d.ts +57 -0
- package/dist/services/jira.d.ts.map +1 -1
- package/dist/services/jira.js +395 -24
- package/dist/services/jira.js.map +1 -1
- package/dist/tools/bitbucket-tools.d.ts +6 -0
- package/dist/tools/bitbucket-tools.d.ts.map +1 -0
- package/dist/tools/bitbucket-tools.js +386 -0
- package/dist/tools/bitbucket-tools.js.map +1 -0
- package/dist/tools/confluence-tools.d.ts +17 -0
- package/dist/tools/confluence-tools.d.ts.map +1 -0
- package/dist/tools/confluence-tools.js +440 -0
- package/dist/tools/confluence-tools.js.map +1 -0
- package/dist/tools/index.d.ts +8 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +15 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/jira-tools.d.ts +6 -0
- package/dist/tools/jira-tools.d.ts.map +1 -0
- package/dist/tools/jira-tools.js +1032 -0
- package/dist/tools/jira-tools.js.map +1 -0
- package/dist/types.d.ts +23 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/services/jira.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { JiraClient } from '../jira-client.js';
|
|
1
|
+
import { JiraClient } from '../clients/jira-client.js';
|
|
2
2
|
export class JiraService {
|
|
3
3
|
client;
|
|
4
4
|
// Smart suggestion rules for common field patterns
|
|
@@ -30,33 +30,87 @@ export class JiraService {
|
|
|
30
30
|
this.client = new JiraClient();
|
|
31
31
|
}
|
|
32
32
|
async searchIssues(options = {}) {
|
|
33
|
-
const { query, limit = 50, startAt = 0 } = options;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
jql =
|
|
33
|
+
const { query, assignee, status, project, labels, reporter, createdAfter, updatedAfter, jql: rawJql, fields: extraFields, limit = 50, startAt = 0 } = options;
|
|
34
|
+
let jql;
|
|
35
|
+
if (rawJql) {
|
|
36
|
+
// Use raw JQL directly when provided
|
|
37
|
+
jql = rawJql;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
// Build JQL from individual filters
|
|
41
|
+
const conditions = [];
|
|
42
|
+
if (query) {
|
|
43
|
+
conditions.push(`text ~ "${query}"`);
|
|
44
|
+
}
|
|
45
|
+
if (assignee) {
|
|
46
|
+
conditions.push(`assignee = ${assignee}`);
|
|
47
|
+
}
|
|
48
|
+
if (status) {
|
|
49
|
+
conditions.push(`status = "${status}"`);
|
|
50
|
+
}
|
|
51
|
+
if (project) {
|
|
52
|
+
conditions.push(`project = "${project}"`);
|
|
53
|
+
}
|
|
54
|
+
if (labels && labels.length > 0) {
|
|
55
|
+
conditions.push(labels.map(l => `labels = "${l}"`).join(' AND '));
|
|
56
|
+
}
|
|
57
|
+
if (reporter) {
|
|
58
|
+
conditions.push(`reporter = ${reporter}`);
|
|
59
|
+
}
|
|
60
|
+
if (createdAfter) {
|
|
61
|
+
// Support relative days (e.g., "10d") or absolute dates (e.g., "2026-01-15")
|
|
62
|
+
const dateValue = /^\d+d$/.test(createdAfter) ? `-${createdAfter}` : createdAfter;
|
|
63
|
+
conditions.push(`created >= "${dateValue}"`);
|
|
64
|
+
}
|
|
65
|
+
if (updatedAfter) {
|
|
66
|
+
const dateValue = /^\d+d$/.test(updatedAfter) ? `-${updatedAfter}` : updatedAfter;
|
|
67
|
+
conditions.push(`updated >= "${dateValue}"`);
|
|
68
|
+
}
|
|
69
|
+
// Only apply 90-day bound when no specific filters are provided
|
|
70
|
+
if (conditions.length === 0) {
|
|
71
|
+
conditions.push('created >= -90d');
|
|
72
|
+
}
|
|
73
|
+
jql = `${conditions.join(' AND ')} ORDER BY updated DESC`;
|
|
38
74
|
}
|
|
75
|
+
const defaultFields = ['summary', 'description', 'status', 'assignee', 'labels', 'created', 'updated'];
|
|
76
|
+
const requestFields = extraFields && extraFields.length > 0
|
|
77
|
+
? [...new Set(extraFields)]
|
|
78
|
+
: defaultFields;
|
|
39
79
|
const params = {
|
|
40
80
|
jql,
|
|
41
81
|
maxResults: limit,
|
|
42
82
|
startAt,
|
|
43
|
-
fields: '
|
|
83
|
+
fields: requestFields.join(',')
|
|
44
84
|
};
|
|
45
85
|
const response = await this.client.get('/rest/api/3/search/jql', params);
|
|
46
|
-
return response.issues.map((issue) =>
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
86
|
+
return response.issues.map((issue) => {
|
|
87
|
+
const result = {
|
|
88
|
+
key: issue.key,
|
|
89
|
+
};
|
|
90
|
+
for (const field of requestFields) {
|
|
91
|
+
const value = issue.fields[field];
|
|
92
|
+
if (value === undefined || value === null) {
|
|
93
|
+
result[field] = null;
|
|
94
|
+
}
|
|
95
|
+
else if (field === 'description') {
|
|
96
|
+
result[field] = this.extractTextFromADF(value);
|
|
97
|
+
}
|
|
98
|
+
else if (typeof value === 'object' && !Array.isArray(value)) {
|
|
99
|
+
result[field] = value.name || value.displayName || value.value || value;
|
|
100
|
+
}
|
|
101
|
+
else if (Array.isArray(value)) {
|
|
102
|
+
result[field] = value.map((v) => typeof v === 'object' ? (v.name || v.displayName || v.value || v) : v);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
result[field] = value;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
});
|
|
56
110
|
}
|
|
57
111
|
async getIssue(issueKey) {
|
|
58
112
|
const params = {
|
|
59
|
-
fields: ['summary', 'description', 'status', 'assignee', 'created', 'updated']
|
|
113
|
+
fields: ['summary', 'description', 'status', 'assignee', 'labels', 'created', 'updated']
|
|
60
114
|
};
|
|
61
115
|
const issue = await this.client.get(`/rest/api/3/issue/${issueKey}`, params);
|
|
62
116
|
return {
|
|
@@ -66,6 +120,7 @@ export class JiraService {
|
|
|
66
120
|
description: this.extractTextFromADF(issue.fields.description),
|
|
67
121
|
status: issue.fields.status?.name || '',
|
|
68
122
|
assignee: issue.fields.assignee?.displayName || '',
|
|
123
|
+
labels: issue.fields.labels || [],
|
|
69
124
|
created: issue.fields.created || '',
|
|
70
125
|
updated: issue.fields.updated || ''
|
|
71
126
|
};
|
|
@@ -472,6 +527,48 @@ export class JiraService {
|
|
|
472
527
|
async removeWatcher(issueKey, accountId) {
|
|
473
528
|
await this.client.delete(`/rest/api/3/issue/${issueKey}/watchers?accountId=${accountId}`);
|
|
474
529
|
}
|
|
530
|
+
// Field Discovery Methods
|
|
531
|
+
async getFields(type) {
|
|
532
|
+
const response = await this.client.get('/rest/api/3/field');
|
|
533
|
+
let fields = response.map((f) => ({
|
|
534
|
+
id: f.id,
|
|
535
|
+
name: f.name,
|
|
536
|
+
custom: f.custom || false,
|
|
537
|
+
schema: f.schema?.type || null,
|
|
538
|
+
}));
|
|
539
|
+
if (type === 'standard') {
|
|
540
|
+
fields = fields.filter((f) => !f.custom);
|
|
541
|
+
}
|
|
542
|
+
else if (type === 'custom') {
|
|
543
|
+
fields = fields.filter((f) => f.custom);
|
|
544
|
+
}
|
|
545
|
+
return fields.sort((a, b) => a.name.localeCompare(b.name));
|
|
546
|
+
}
|
|
547
|
+
// Label Methods
|
|
548
|
+
async getLabels(query, maxResults = 50) {
|
|
549
|
+
const params = { maxResults };
|
|
550
|
+
if (query) {
|
|
551
|
+
params.query = query;
|
|
552
|
+
}
|
|
553
|
+
const response = await this.client.get('/rest/api/3/label', params);
|
|
554
|
+
return response.values || [];
|
|
555
|
+
}
|
|
556
|
+
async addLabels(issueKey, labels) {
|
|
557
|
+
const data = {
|
|
558
|
+
update: {
|
|
559
|
+
labels: labels.map(label => ({ add: label }))
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
await this.client.put(`/rest/api/3/issue/${issueKey}`, data);
|
|
563
|
+
}
|
|
564
|
+
async removeLabels(issueKey, labels) {
|
|
565
|
+
const data = {
|
|
566
|
+
update: {
|
|
567
|
+
labels: labels.map(label => ({ remove: label }))
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
await this.client.put(`/rest/api/3/issue/${issueKey}`, data);
|
|
571
|
+
}
|
|
475
572
|
// Sub-task Methods
|
|
476
573
|
async getSubTasks(issueKey) {
|
|
477
574
|
const issue = await this.client.get(`/rest/api/3/issue/${issueKey}`, {
|
|
@@ -514,6 +611,242 @@ export class JiraService {
|
|
|
514
611
|
}))
|
|
515
612
|
})) || [];
|
|
516
613
|
}
|
|
614
|
+
// Agile Methods
|
|
615
|
+
async getAgileBoards(options) {
|
|
616
|
+
const params = { maxResults: 50 };
|
|
617
|
+
if (options?.name)
|
|
618
|
+
params.name = options.name;
|
|
619
|
+
if (options?.type)
|
|
620
|
+
params.type = options.type;
|
|
621
|
+
if (options?.projectKeyOrId)
|
|
622
|
+
params.projectKeyOrId = options.projectKeyOrId;
|
|
623
|
+
const response = await this.client.get('/rest/agile/1.0/board', params);
|
|
624
|
+
return response.values?.map((board) => ({
|
|
625
|
+
id: board.id,
|
|
626
|
+
name: board.name,
|
|
627
|
+
type: board.type,
|
|
628
|
+
location: board.location ? {
|
|
629
|
+
projectId: board.location.projectId,
|
|
630
|
+
projectKey: board.location.projectKey,
|
|
631
|
+
projectName: board.location.projectName,
|
|
632
|
+
} : null,
|
|
633
|
+
})) || [];
|
|
634
|
+
}
|
|
635
|
+
async getBoardIssues(boardId, options) {
|
|
636
|
+
const params = { maxResults: options?.maxResults || 50 };
|
|
637
|
+
if (options?.jql)
|
|
638
|
+
params.jql = options.jql;
|
|
639
|
+
if (options?.startAt)
|
|
640
|
+
params.startAt = options.startAt;
|
|
641
|
+
const response = await this.client.get(`/rest/agile/1.0/board/${boardId}/issue`, params);
|
|
642
|
+
return {
|
|
643
|
+
total: response.total,
|
|
644
|
+
issues: response.issues?.map((issue) => ({
|
|
645
|
+
key: issue.key,
|
|
646
|
+
summary: issue.fields?.summary || '',
|
|
647
|
+
status: issue.fields?.status?.name || '',
|
|
648
|
+
assignee: issue.fields?.assignee?.displayName || '',
|
|
649
|
+
priority: issue.fields?.priority?.name || '',
|
|
650
|
+
})) || [],
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
async getSprints(boardId, state) {
|
|
654
|
+
const params = { maxResults: 50 };
|
|
655
|
+
if (state)
|
|
656
|
+
params.state = state;
|
|
657
|
+
const response = await this.client.get(`/rest/agile/1.0/board/${boardId}/sprint`, params);
|
|
658
|
+
return response.values?.map((sprint) => ({
|
|
659
|
+
id: sprint.id,
|
|
660
|
+
name: sprint.name,
|
|
661
|
+
state: sprint.state,
|
|
662
|
+
startDate: sprint.startDate || null,
|
|
663
|
+
endDate: sprint.endDate || null,
|
|
664
|
+
completeDate: sprint.completeDate || null,
|
|
665
|
+
goal: sprint.goal || '',
|
|
666
|
+
})) || [];
|
|
667
|
+
}
|
|
668
|
+
async getSprintIssues(sprintId, options) {
|
|
669
|
+
const params = { maxResults: options?.maxResults || 50 };
|
|
670
|
+
if (options?.jql)
|
|
671
|
+
params.jql = options.jql;
|
|
672
|
+
if (options?.startAt)
|
|
673
|
+
params.startAt = options.startAt;
|
|
674
|
+
const response = await this.client.get(`/rest/agile/1.0/sprint/${sprintId}/issue`, params);
|
|
675
|
+
return {
|
|
676
|
+
total: response.total,
|
|
677
|
+
issues: response.issues?.map((issue) => ({
|
|
678
|
+
key: issue.key,
|
|
679
|
+
summary: issue.fields?.summary || '',
|
|
680
|
+
status: issue.fields?.status?.name || '',
|
|
681
|
+
assignee: issue.fields?.assignee?.displayName || '',
|
|
682
|
+
priority: issue.fields?.priority?.name || '',
|
|
683
|
+
})) || [],
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
// Batch Create
|
|
687
|
+
async batchCreateIssues(issues) {
|
|
688
|
+
if (issues.length > 50) {
|
|
689
|
+
throw new Error('Batch create supports a maximum of 50 issues at a time');
|
|
690
|
+
}
|
|
691
|
+
const issueUpdates = issues.map(issue => ({
|
|
692
|
+
fields: {
|
|
693
|
+
project: { key: issue.projectKey },
|
|
694
|
+
summary: issue.summary,
|
|
695
|
+
description: this.parseDescriptionToADF(issue.description),
|
|
696
|
+
issuetype: { name: issue.issueType || 'Task' },
|
|
697
|
+
...issue.additionalFields,
|
|
698
|
+
},
|
|
699
|
+
}));
|
|
700
|
+
const response = await this.client.post('/rest/api/3/issue/bulk', { issueUpdates });
|
|
701
|
+
return {
|
|
702
|
+
issues: response.issues?.map((i) => ({ id: i.id, key: i.key, self: i.self })) || [],
|
|
703
|
+
errors: response.errors || [],
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
// Dev Status
|
|
707
|
+
async getDevStatus(issueId, applicationType, dataType) {
|
|
708
|
+
const params = { issueId };
|
|
709
|
+
if (applicationType)
|
|
710
|
+
params.applicationType = applicationType;
|
|
711
|
+
if (dataType)
|
|
712
|
+
params.dataType = dataType;
|
|
713
|
+
const response = await this.client.get('/rest/dev-status/latest/issue/detail', params);
|
|
714
|
+
return response;
|
|
715
|
+
}
|
|
716
|
+
// Delete Issue
|
|
717
|
+
async deleteIssue(issueKey, deleteSubtasks = false) {
|
|
718
|
+
const query = deleteSubtasks ? '?deleteSubtasks=true' : '';
|
|
719
|
+
await this.client.delete(`/rest/api/3/issue/${issueKey}${query}`);
|
|
720
|
+
}
|
|
721
|
+
// Sprint Management
|
|
722
|
+
async createSprint(boardId, name, options) {
|
|
723
|
+
const data = {
|
|
724
|
+
name,
|
|
725
|
+
originBoardId: boardId,
|
|
726
|
+
};
|
|
727
|
+
if (options?.startDate)
|
|
728
|
+
data.startDate = options.startDate;
|
|
729
|
+
if (options?.endDate)
|
|
730
|
+
data.endDate = options.endDate;
|
|
731
|
+
if (options?.goal)
|
|
732
|
+
data.goal = options.goal;
|
|
733
|
+
const response = await this.client.post('/rest/agile/1.0/sprint', data);
|
|
734
|
+
return {
|
|
735
|
+
id: response.id,
|
|
736
|
+
name: response.name,
|
|
737
|
+
state: response.state,
|
|
738
|
+
startDate: response.startDate || null,
|
|
739
|
+
endDate: response.endDate || null,
|
|
740
|
+
goal: response.goal || '',
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
async updateSprint(sprintId, updates) {
|
|
744
|
+
const data = {};
|
|
745
|
+
if (updates.name)
|
|
746
|
+
data.name = updates.name;
|
|
747
|
+
if (updates.state)
|
|
748
|
+
data.state = updates.state;
|
|
749
|
+
if (updates.startDate)
|
|
750
|
+
data.startDate = updates.startDate;
|
|
751
|
+
if (updates.endDate)
|
|
752
|
+
data.endDate = updates.endDate;
|
|
753
|
+
if (updates.goal !== undefined)
|
|
754
|
+
data.goal = updates.goal;
|
|
755
|
+
const response = await this.client.post(`/rest/agile/1.0/sprint/${sprintId}`, data);
|
|
756
|
+
return {
|
|
757
|
+
id: response.id,
|
|
758
|
+
name: response.name,
|
|
759
|
+
state: response.state,
|
|
760
|
+
startDate: response.startDate || null,
|
|
761
|
+
endDate: response.endDate || null,
|
|
762
|
+
goal: response.goal || '',
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
// Version/Release Management
|
|
766
|
+
async getProjectVersions(projectKey) {
|
|
767
|
+
const response = await this.client.get(`/rest/api/3/project/${projectKey}/versions`);
|
|
768
|
+
return response.map((v) => ({
|
|
769
|
+
id: v.id,
|
|
770
|
+
name: v.name,
|
|
771
|
+
description: v.description || '',
|
|
772
|
+
released: v.released || false,
|
|
773
|
+
archived: v.archived || false,
|
|
774
|
+
startDate: v.startDate || null,
|
|
775
|
+
releaseDate: v.releaseDate || null,
|
|
776
|
+
}));
|
|
777
|
+
}
|
|
778
|
+
async createVersion(projectKey, name, options) {
|
|
779
|
+
const data = {
|
|
780
|
+
project: projectKey,
|
|
781
|
+
name,
|
|
782
|
+
};
|
|
783
|
+
if (options?.description)
|
|
784
|
+
data.description = options.description;
|
|
785
|
+
if (options?.startDate)
|
|
786
|
+
data.startDate = options.startDate;
|
|
787
|
+
if (options?.releaseDate)
|
|
788
|
+
data.releaseDate = options.releaseDate;
|
|
789
|
+
if (options?.released !== undefined)
|
|
790
|
+
data.released = options.released;
|
|
791
|
+
const response = await this.client.post('/rest/api/3/version', data);
|
|
792
|
+
return {
|
|
793
|
+
id: response.id,
|
|
794
|
+
name: response.name,
|
|
795
|
+
description: response.description || '',
|
|
796
|
+
released: response.released || false,
|
|
797
|
+
archived: response.archived || false,
|
|
798
|
+
startDate: response.startDate || null,
|
|
799
|
+
releaseDate: response.releaseDate || null,
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
async updateVersion(versionId, updates) {
|
|
803
|
+
const data = {};
|
|
804
|
+
if (updates.name)
|
|
805
|
+
data.name = updates.name;
|
|
806
|
+
if (updates.description !== undefined)
|
|
807
|
+
data.description = updates.description;
|
|
808
|
+
if (updates.startDate)
|
|
809
|
+
data.startDate = updates.startDate;
|
|
810
|
+
if (updates.releaseDate)
|
|
811
|
+
data.releaseDate = updates.releaseDate;
|
|
812
|
+
if (updates.released !== undefined)
|
|
813
|
+
data.released = updates.released;
|
|
814
|
+
if (updates.archived !== undefined)
|
|
815
|
+
data.archived = updates.archived;
|
|
816
|
+
const response = await this.client.put(`/rest/api/3/version/${versionId}`, data);
|
|
817
|
+
return {
|
|
818
|
+
id: response.id,
|
|
819
|
+
name: response.name,
|
|
820
|
+
description: response.description || '',
|
|
821
|
+
released: response.released || false,
|
|
822
|
+
archived: response.archived || false,
|
|
823
|
+
startDate: response.startDate || null,
|
|
824
|
+
releaseDate: response.releaseDate || null,
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
// Epic Linking
|
|
828
|
+
async linkToEpic(issueKey, epicKey) {
|
|
829
|
+
// In next-gen projects, Epic is the parent. In classic, it's the Epic Link custom field.
|
|
830
|
+
// Try parent field first (works for next-gen/team-managed), fall back to Epic Link.
|
|
831
|
+
try {
|
|
832
|
+
await this.client.put(`/rest/api/3/issue/${issueKey}`, {
|
|
833
|
+
fields: { parent: { key: epicKey } }
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
catch {
|
|
837
|
+
// Fallback: find the Epic Link custom field ID and use that
|
|
838
|
+
const fields = await this.client.get('/rest/api/3/field');
|
|
839
|
+
const epicLinkField = fields.find((f) => f.name === 'Epic Link' || f.clauseNames?.includes('cf[10014]'));
|
|
840
|
+
if (epicLinkField) {
|
|
841
|
+
await this.client.put(`/rest/api/3/issue/${issueKey}`, {
|
|
842
|
+
fields: { [epicLinkField.id]: epicKey }
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
else {
|
|
846
|
+
throw new Error('Could not find Epic Link field. Ensure the issue type supports epic linking.');
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
517
850
|
// Smart Field Handling Methods
|
|
518
851
|
async handleTransitionError(issueKey, transitionId, error) {
|
|
519
852
|
try {
|
|
@@ -734,7 +1067,16 @@ export class JiraService {
|
|
|
734
1067
|
for (let i = 0; i < lines.length; i++) {
|
|
735
1068
|
const line = lines[i].trim();
|
|
736
1069
|
if (line === '') {
|
|
737
|
-
//
|
|
1070
|
+
// Only add an empty paragraph for double blank lines (true paragraph breaks)
|
|
1071
|
+
// Single blank lines are natural separators between block elements and don't need extra spacing
|
|
1072
|
+
const nextLine = i + 1 < lines.length ? lines[i + 1].trim() : '';
|
|
1073
|
+
if (nextLine === '') {
|
|
1074
|
+
const lastNode = content[content.length - 1];
|
|
1075
|
+
const isLastEmpty = lastNode && lastNode.type === 'paragraph' && (!lastNode.content || lastNode.content.length === 0);
|
|
1076
|
+
if (!isLastEmpty) {
|
|
1077
|
+
content.push({ type: 'paragraph', content: [] });
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
738
1080
|
continue;
|
|
739
1081
|
}
|
|
740
1082
|
// Handle Confluence wiki markup headers (h1., h2., h3., etc.)
|
|
@@ -1023,6 +1365,11 @@ export class JiraService {
|
|
|
1023
1365
|
{ regex: /`([^`]+?)`/g, type: 'code' }, // `code` (markdown style)
|
|
1024
1366
|
{ regex: /\{\{(.+?)\}\}/g, type: 'code' }, // {{code}} (Confluence/Jira wiki style)
|
|
1025
1367
|
];
|
|
1368
|
+
// Link patterns (markdown [text](url) and Jira wiki [text|url])
|
|
1369
|
+
const linkPatterns = [
|
|
1370
|
+
{ regex: /\[([^\]]+?)\]\(([^)]+?)\)/g }, // [text](url) - markdown style
|
|
1371
|
+
{ regex: /\[([^|\]]+?)\|([^\]]+?)\]/g }, // [text|url] - Jira wiki style
|
|
1372
|
+
];
|
|
1026
1373
|
let matches = [];
|
|
1027
1374
|
// Find all formatting matches
|
|
1028
1375
|
patterns.forEach(pattern => {
|
|
@@ -1038,6 +1385,21 @@ export class JiraService {
|
|
|
1038
1385
|
});
|
|
1039
1386
|
}
|
|
1040
1387
|
});
|
|
1388
|
+
// Find all link matches
|
|
1389
|
+
linkPatterns.forEach(pattern => {
|
|
1390
|
+
let match;
|
|
1391
|
+
const regex = new RegExp(pattern.regex.source, 'g');
|
|
1392
|
+
while ((match = regex.exec(text)) !== null) {
|
|
1393
|
+
matches.push({
|
|
1394
|
+
start: match.index,
|
|
1395
|
+
end: match.index + match[0].length,
|
|
1396
|
+
text: match[1],
|
|
1397
|
+
type: 'link',
|
|
1398
|
+
href: match[2],
|
|
1399
|
+
fullMatch: match[0]
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
});
|
|
1041
1403
|
// Sort matches by position and remove overlapping matches
|
|
1042
1404
|
matches.sort((a, b) => a.start - b.start);
|
|
1043
1405
|
// After processing bold and code, handle italic patterns that don't overlap
|
|
@@ -1085,11 +1447,20 @@ export class JiraService {
|
|
|
1085
1447
|
}
|
|
1086
1448
|
}
|
|
1087
1449
|
// Add formatted text
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1450
|
+
if (match.type === 'link') {
|
|
1451
|
+
result.push({
|
|
1452
|
+
type: 'text',
|
|
1453
|
+
text: match.text,
|
|
1454
|
+
marks: [{ type: 'link', attrs: { href: match.href } }]
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
else {
|
|
1458
|
+
result.push({
|
|
1459
|
+
type: 'text',
|
|
1460
|
+
text: match.text,
|
|
1461
|
+
marks: [{ type: match.type }]
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1093
1464
|
currentPos = match.end;
|
|
1094
1465
|
});
|
|
1095
1466
|
// Add remaining text
|