Skip to content

Commit c5a4fbf

Browse files
authored
fix(jira): update epic collector to use new API endpoint and include (#8547)
* 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
1 parent ef73a6c commit c5a4fbf

File tree

1 file changed

+96
-6
lines changed

1 file changed

+96
-6
lines changed

backend/plugins/jira/tasks/epic_collector.go

Lines changed: 96 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,17 +82,42 @@ func CollectEpics(taskCtx plugin.SubTaskContext) errors.Error {
8282
jql = buildJQL(*apiCollector.GetSince(), loc)
8383
}
8484

85-
err = apiCollector.InitCollector(api.ApiCollectorArgs{
85+
// Choose API endpoint based on JIRA deployment type
86+
if data.JiraServerInfo.DeploymentType == models.DeploymentServer {
87+
logger.Info("Using api/2/search for JIRA Server")
88+
err = setupApiV2Collector(apiCollector, data, epicIterator, jql)
89+
} else {
90+
logger.Info("Using api/3/search/jql for JIRA Cloud")
91+
err = setupApiV3Collector(apiCollector, data, epicIterator, jql)
92+
}
93+
if err != nil {
94+
return err
95+
}
96+
return apiCollector.Execute()
97+
}
98+
99+
// JIRA Server API v2 collector
100+
func setupApiV2Collector(apiCollector *api.StatefulApiCollector, data *JiraTaskData, epicIterator api.Iterator, jql string) errors.Error {
101+
return apiCollector.InitCollector(api.ApiCollectorArgs{
86102
ApiClient: data.ApiClient,
87103
PageSize: 100,
88104
Incremental: false,
89105
UrlTemplate: "api/2/search",
90106
Query: func(reqData *api.RequestData) (url.Values, errors.Error) {
91107
query := url.Values{}
92108
epicKeys := []string{}
93-
for _, e := range reqData.Input.([]interface{}) {
94-
epicKeys = append(epicKeys, *e.(*string))
109+
110+
input, ok := reqData.Input.([]interface{})
111+
if !ok {
112+
return nil, errors.Default.New("invalid input type, expected []interface{}")
95113
}
114+
115+
for _, e := range input {
116+
if epicKey, ok := e.(*string); ok && epicKey != nil {
117+
epicKeys = append(epicKeys, *epicKey)
118+
}
119+
}
120+
96121
localJQL := fmt.Sprintf("issue in (%s) and %s", strings.Join(epicKeys, ","), jql)
97122
query.Set("jql", localJQL)
98123
query.Set("startAt", fmt.Sprintf("%v", reqData.Pager.Skip))
@@ -117,13 +142,78 @@ func CollectEpics(taskCtx plugin.SubTaskContext) errors.Error {
117142
}
118143
return data.Issues, nil
119144
},
120-
// Jira Server returns 400 if the epic is not found
121145
AfterResponse: ignoreHTTPStatus400,
122146
})
147+
}
148+
149+
// JIRA Cloud API v3 collector
150+
func setupApiV3Collector(apiCollector *api.StatefulApiCollector, data *JiraTaskData, epicIterator api.Iterator, jql string) errors.Error {
151+
return apiCollector.InitCollector(api.ApiCollectorArgs{
152+
ApiClient: data.ApiClient,
153+
PageSize: 100,
154+
Incremental: false,
155+
UrlTemplate: "api/3/search/jql",
156+
GetNextPageCustomData: getNextPageCustomDataForV3,
157+
Query: func(reqData *api.RequestData) (url.Values, errors.Error) {
158+
query := url.Values{}
159+
epicKeys := []string{}
160+
for _, e := range reqData.Input.([]interface{}) {
161+
epicKeys = append(epicKeys, *e.(*string))
162+
}
163+
localJQL := fmt.Sprintf("issue in (%s) and %s", strings.Join(epicKeys, ","), jql)
164+
query.Set("jql", localJQL)
165+
query.Set("maxResults", fmt.Sprintf("%v", reqData.Pager.Size))
166+
query.Set("expand", "changelog")
167+
query.Set("fields", "*all")
168+
169+
if reqData.CustomData != nil {
170+
query.Set("nextPageToken", reqData.CustomData.(string))
171+
}
172+
173+
return query, nil
174+
},
175+
Input: epicIterator,
176+
ResponseParser: func(res *http.Response) ([]json.RawMessage, errors.Error) {
177+
var data struct {
178+
Issues []json.RawMessage `json:"issues"`
179+
}
180+
blob, err := io.ReadAll(res.Body)
181+
if err != nil {
182+
return nil, errors.Convert(err)
183+
}
184+
err = json.Unmarshal(blob, &data)
185+
if err != nil {
186+
return nil, errors.Convert(err)
187+
}
188+
return data.Issues, nil
189+
},
190+
AfterResponse: ignoreHTTPStatus400,
191+
})
192+
}
193+
194+
// Get next page token for API v3
195+
func getNextPageCustomDataForV3(_ *api.RequestData, prevPageResponse *http.Response) (interface{}, errors.Error) {
196+
var response struct {
197+
NextPageToken string `json:"nextPageToken"`
198+
}
199+
200+
blob, err := io.ReadAll(prevPageResponse.Body)
123201
if err != nil {
124-
return err
202+
return nil, errors.Convert(err)
125203
}
126-
return apiCollector.Execute()
204+
205+
prevPageResponse.Body = io.NopCloser(strings.NewReader(string(blob)))
206+
207+
err = json.Unmarshal(blob, &response)
208+
if err != nil {
209+
return nil, errors.Convert(err)
210+
}
211+
212+
if response.NextPageToken == "" {
213+
return nil, api.ErrFinishCollect
214+
}
215+
216+
return response.NextPageToken, nil
127217
}
128218

129219
func GetEpicKeysIterator(db dal.Dal, data *JiraTaskData, batchSize int) (api.Iterator, errors.Error) {

0 commit comments

Comments
 (0)