Skip to content

Commit 314124a

Browse files
authored
fix(jira): fix type for Jira issue descriptions (#8559)
* fix(jira): update epic collector to use new API endpoint and include all fields * fix(jira): enhance epic collector to dynamically select API endpoint based on JIRA version * fix(jira): update epic collector to use correct API endpoint for JIRA Cloud and Server versions * fix(jira): refactor epic collector to streamline API endpoint selection and enhance error handling * fix(jira): fix type for Jira issue descriptions * refactor(jira): update comment and worklog models to use FlexibleDescription type for comments * docs(jira): add ADF reference for FlexibleDescription type in issue model
1 parent c5a4fbf commit 314124a

3 files changed

Lines changed: 152 additions & 23 deletions

File tree

backend/plugins/jira/tasks/apiv2models/comment.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,14 @@ import (
2525
)
2626

2727
type Comment struct {
28-
Self string `json:"self"`
29-
Id string `json:"id"`
30-
Author *Account `json:"author"`
31-
Body string `json:"body"`
32-
UpdateAuthor *Account `json:"updateAuthor"`
33-
Created common.Iso8601Time `json:"created"`
34-
Updated common.Iso8601Time `json:"updated"`
35-
JsdPublic bool `json:"jsdPublic"`
28+
Self string `json:"self"`
29+
Id string `json:"id"`
30+
Author *Account `json:"author"`
31+
Body FlexibleDescription `json:"body"`
32+
UpdateAuthor *Account `json:"updateAuthor"`
33+
Created common.Iso8601Time `json:"created"`
34+
Updated common.Iso8601Time `json:"updated"`
35+
JsdPublic bool `json:"jsdPublic"`
3636
}
3737

3838
func (c Comment) ToToolLayer(connectionId uint64, issueId uint64, issueUpdated *time.Time) *models.JiraIssueComment {
@@ -41,7 +41,7 @@ func (c Comment) ToToolLayer(connectionId uint64, issueId uint64, issueUpdated *
4141
IssueId: issueId,
4242
ComentId: c.Id,
4343
Self: c.Self,
44-
Body: c.Body,
44+
Body: c.Body.Value,
4545
Created: c.Updated.ToTime(),
4646
Updated: c.Updated.ToTime(),
4747
IssueUpdated: issueUpdated,

backend/plugins/jira/tasks/apiv2models/issue.go

Lines changed: 132 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,142 @@ package apiv2models
1919

2020
import (
2121
"encoding/json"
22+
"strings"
2223
"time"
2324

2425
"github.com/apache/incubator-devlake/core/errors"
2526
"github.com/apache/incubator-devlake/core/models/common"
2627
"github.com/apache/incubator-devlake/plugins/jira/models"
2728
)
2829

30+
// FlexibleDescription supports both plain text and ADF (Atlassian Document Format) for Jira description field
31+
// ADF reference: https://developer.atlassian.com/cloud/jira/platform/apis/document/structure/
32+
type FlexibleDescription struct {
33+
Value string
34+
}
35+
36+
// ADFNode represents a node in Atlassian Document Format
37+
type ADFNode struct {
38+
Type string `json:"type"`
39+
Text string `json:"text,omitempty"`
40+
Content []ADFNode `json:"content,omitempty"`
41+
Attrs map[string]interface{} `json:"attrs,omitempty"`
42+
}
43+
44+
// UnmarshalJSON implements custom JSON unmarshaling for FlexibleDescription
45+
func (fd *FlexibleDescription) UnmarshalJSON(data []byte) error {
46+
// Handle null values
47+
if string(data) == "null" {
48+
fd.Value = ""
49+
return nil
50+
}
51+
52+
// Try to unmarshal as string first
53+
var str string
54+
if err := json.Unmarshal(data, &str); err == nil {
55+
fd.Value = str
56+
return nil
57+
}
58+
59+
// Try to unmarshal as ADF document
60+
var adfDoc ADFNode
61+
if err := json.Unmarshal(data, &adfDoc); err == nil {
62+
fd.Value = extractTextFromADF(adfDoc)
63+
return nil
64+
}
65+
66+
// Fallback: keep raw JSON as string for debugging
67+
fd.Value = string(data)
68+
return nil
69+
}
70+
71+
// extractTextFromADF recursively extracts plain text from ADF document
72+
func extractTextFromADF(node ADFNode) string {
73+
var result strings.Builder
74+
75+
switch node.Type {
76+
case "text":
77+
result.WriteString(node.Text)
78+
case "hardBreak":
79+
result.WriteString("\n")
80+
case "paragraph":
81+
for _, child := range node.Content {
82+
result.WriteString(extractTextFromADF(child))
83+
}
84+
result.WriteString("\n")
85+
case "heading":
86+
for _, child := range node.Content {
87+
result.WriteString(extractTextFromADF(child))
88+
}
89+
result.WriteString("\n")
90+
case "listItem":
91+
for _, child := range node.Content {
92+
result.WriteString(extractTextFromADF(child))
93+
}
94+
case "bulletList", "orderedList":
95+
for _, child := range node.Content {
96+
result.WriteString("• ")
97+
result.WriteString(extractTextFromADF(child))
98+
result.WriteString("\n")
99+
}
100+
case "table":
101+
for _, row := range node.Content {
102+
if row.Type == "tableRow" {
103+
for j, cell := range row.Content {
104+
if j > 0 {
105+
result.WriteString(" | ")
106+
}
107+
result.WriteString(extractTextFromADF(cell))
108+
}
109+
result.WriteString("\n")
110+
}
111+
}
112+
case "tableCell", "tableHeader":
113+
for _, child := range node.Content {
114+
result.WriteString(extractTextFromADF(child))
115+
}
116+
case "codeBlock":
117+
result.WriteString("```\n")
118+
for _, child := range node.Content {
119+
result.WriteString(extractTextFromADF(child))
120+
}
121+
result.WriteString("\n```\n")
122+
case "blockquote":
123+
result.WriteString("> ")
124+
for _, child := range node.Content {
125+
result.WriteString(extractTextFromADF(child))
126+
}
127+
result.WriteString("\n")
128+
case "doc":
129+
for _, child := range node.Content {
130+
result.WriteString(extractTextFromADF(child))
131+
}
132+
case "inlineCard", "mention":
133+
// Extract text from attrs or content for links and mentions
134+
if attrs, ok := node.Attrs["text"]; ok {
135+
if text, ok := attrs.(string); ok {
136+
result.WriteString(text)
137+
}
138+
} else {
139+
for _, child := range node.Content {
140+
result.WriteString(extractTextFromADF(child))
141+
}
142+
}
143+
default:
144+
// For unknown types, extract content recursively
145+
for _, child := range node.Content {
146+
result.WriteString(extractTextFromADF(child))
147+
}
148+
}
149+
150+
return result.String()
151+
}
152+
153+
// String returns the string value
154+
func (fd FlexibleDescription) String() string {
155+
return fd.Value
156+
}
157+
29158
type Issue struct {
30159
Expand string `json:"expand"`
31160
ID uint64 `json:"id,string"`
@@ -121,8 +250,8 @@ type Issue struct {
121250
ID string `json:"id"`
122251
Name string `json:"name"`
123252
} `json:"components"`
124-
Timeoriginalestimate *int64 `json:"timeoriginalestimate"`
125-
Description string `json:"description"`
253+
Timeoriginalestimate *int64 `json:"timeoriginalestimate"`
254+
Description FlexibleDescription `json:"description"`
126255
Timetracking *struct {
127256
RemainingEstimate string `json:"remainingEstimate"`
128257
TimeSpent string `json:"timeSpent"`
@@ -233,7 +362,7 @@ func (i Issue) toToolLayer(connectionId uint64) *models.JiraIssue {
233362
IssueKey: i.Key,
234363
StoryPoint: &workload,
235364
Summary: i.Fields.Summary,
236-
Description: i.Fields.Description,
365+
Description: i.Fields.Description.Value,
237366
Type: i.Fields.Issuetype.ID,
238367
StatusName: i.Fields.Status.Name,
239368
StatusKey: i.Fields.Status.StatusCategory.Key,

backend/plugins/jira/tasks/apiv2models/worklog.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,17 @@ import (
2525
)
2626

2727
type Worklog struct {
28-
Self string `json:"self"`
29-
Author *Account `json:"author"`
30-
UpdateAuthor *Account `json:"updateAuthor"`
31-
Comment string `json:"comment"`
32-
Created string `json:"created"`
33-
Updated common.Iso8601Time `json:"updated"`
34-
Started common.Iso8601Time `json:"started"`
35-
TimeSpent string `json:"timeSpent"`
36-
TimeSpentSeconds int `json:"timeSpentSeconds"`
37-
ID string `json:"id"`
38-
IssueID uint64 `json:"issueId,string"`
28+
Self string `json:"self"`
29+
Author *Account `json:"author"`
30+
UpdateAuthor *Account `json:"updateAuthor"`
31+
Comment FlexibleDescription `json:"comment"`
32+
Created string `json:"created"`
33+
Updated common.Iso8601Time `json:"updated"`
34+
Started common.Iso8601Time `json:"started"`
35+
TimeSpent string `json:"timeSpent"`
36+
TimeSpentSeconds int `json:"timeSpentSeconds"`
37+
ID string `json:"id"`
38+
IssueID uint64 `json:"issueId,string"`
3939
}
4040

4141
func (w Worklog) ToToolLayer(connectionId uint64, issueUpdated *time.Time) *models.JiraWorklog {

0 commit comments

Comments
 (0)