11using Atlassian . Jira ;
2- using Atlassian . Jira . Remote ;
32using Migration . Common ;
43using Migration . Common . Log ;
54using Newtonsoft . Json . Linq ;
98using System . IO ;
109using System . Linq ;
1110using System . Threading . Tasks ;
12- using System . Web ;
1311
1412namespace JiraExport
1513{
@@ -24,7 +22,7 @@ public enum DownloadOptions
2422 IncludeSubItems = 4
2523 }
2624
27- private readonly string JiraApiV2 = "rest/api/2 " ;
25+ private readonly string JiraApi = "rest/api" ;
2826
2927 private ILookup < string , string > JiraNameFieldCache = null ;
3028
@@ -49,6 +47,11 @@ public void Initialize(JiraSettings settings, ExportIssuesSummary exportIssuesSu
4947 {
5048 Settings = settings ;
5149
50+ if ( Settings . JiraApiVersion != 2 && Settings . JiraApiVersion != 3 )
51+ {
52+ Logger . Log ( LogLevel . Error , $ "Invalid Jira API version: { Settings . JiraApiVersion } . Must be either 2 or 3.") ;
53+ }
54+
5255 Logger . Log ( LogLevel . Info , "Retrieving Jira fields..." ) ;
5356 try
5457 {
@@ -108,7 +111,9 @@ public IssueLinkType GetLinkType(string linkTypeString, string targetItemKey, ou
108111
109112 public IEnumerable < Comment > GetCommentsByItemKey ( string itemKey )
110113 {
111- return _jiraServiceWrapper . Issues . GetCommentsAsync ( itemKey ) . Result ;
114+ var options = new CommentQueryOptions ( ) ;
115+ options . Expand . Add ( "renderedBody" ) ;
116+ return _jiraServiceWrapper . Issues . GetCommentsAsync ( itemKey , options ) . Result ;
112117 }
113118
114119 public CustomField GetCustomField ( string fieldName )
@@ -140,7 +145,7 @@ private async Task<JiraAttachment> GetAttachmentInfo(string id)
140145
141146 try
142147 {
143- var response = await _jiraServiceWrapper . RestClient . ExecuteRequestAsync ( Method . GET , $ "{ JiraApiV2 } /attachment/{ id } ") ;
148+ var response = await _jiraServiceWrapper . RestClient . ExecuteRequestAsync ( Method . GET , $ "{ JiraApi } / { Settings . JiraApiVersion } /attachment/{ id } ") ;
144149 var attObj = ( JObject ) response ;
145150
146151 return new JiraAttachment
@@ -217,6 +222,24 @@ private void EnsurePath(string path)
217222 }
218223
219224 public IEnumerable < JiraItem > EnumerateIssues ( string jql , HashSet < string > skipList , DownloadOptions downloadOptions )
225+ {
226+ if ( Settings . JiraApiVersion == 2 )
227+ {
228+ return EnumerateIssuesV2 ( jql , skipList , downloadOptions ) ;
229+ }
230+ else if ( Settings . JiraApiVersion == 3 )
231+ {
232+ return EnumerateIssuesV3 ( jql , skipList , downloadOptions ) ;
233+ }
234+ else
235+ {
236+ // Invalid API Version already checked in Initialize()
237+ Logger . Log ( LogLevel . Error , $ "Invalid Jira API version: { Settings . JiraApiVersion } . Must be either 2 or 3.") ;
238+ return null ;
239+ }
240+ }
241+
242+ public IEnumerable < JiraItem > EnumerateIssuesV2 ( string jql , HashSet < string > skipList , DownloadOptions downloadOptions )
220243 {
221244 var currentStart = 0 ;
222245 IEnumerable < string > remoteIssueBatch = null ;
@@ -229,7 +252,7 @@ public IEnumerable<JiraItem> EnumerateIssues(string jql, HashSet<string> skipLis
229252 JToken response = null ;
230253 try
231254 {
232- response = _jiraServiceWrapper . RestClient . ExecuteRequestAsync ( Method . GET , $ "{ JiraApiV2 } /search?jql={ jql } &startAt={ currentStart } &maxResults={ Settings . BatchSize } &fields=key") . Result ;
255+ response = _jiraServiceWrapper . RestClient . ExecuteRequestAsync ( Method . GET , $ "{ JiraApi } / { Settings . JiraApiVersion } /search?jql={ jql } &startAt={ currentStart } &maxResults={ Settings . BatchSize } &fields=key") . Result ;
233256 }
234257 catch ( Exception e )
235258 {
@@ -304,6 +327,93 @@ public IEnumerable<JiraItem> EnumerateIssues(string jql, HashSet<string> skipLis
304327 while ( remoteIssueBatch != null && remoteIssueBatch . Any ( ) ) ;
305328 }
306329
330+ public IEnumerable < JiraItem > EnumerateIssuesV3 ( string jql , HashSet < string > skipList , DownloadOptions downloadOptions )
331+ {
332+ var nextPageToken = string . Empty ;
333+ IEnumerable < string > remoteIssueBatch = null ;
334+ var index = 0 ;
335+
336+ Logger . Log ( LogLevel . Debug , "Enumerate remote issues" ) ;
337+
338+ var totalItems = GetItemCount ( jql ) ;
339+
340+ do
341+ {
342+ JToken response = null ;
343+ try
344+ {
345+ response = _jiraServiceWrapper . RestClient . ExecuteRequestAsync ( Method . GET , $ "{ JiraApi } /{ Settings . JiraApiVersion } /search/jql?jql={ jql } &nextPageToken={ nextPageToken } &maxResults={ Settings . BatchSize } &fields=key") . Result ;
346+ nextPageToken = ( string ) response . SelectToken ( "$.nextPageToken" ) ;
347+ }
348+ catch ( Exception e )
349+ {
350+ Logger . Log ( e , "Failed to retrieve issues" ) ;
351+ break ;
352+ }
353+ if ( response != null )
354+ {
355+ remoteIssueBatch = response . SelectTokens ( "$.issues[*]" ) . OfType < JObject > ( )
356+ . Select ( i => i . SelectToken ( "$.key" ) . Value < string > ( ) ) ;
357+
358+ if ( remoteIssueBatch == null || ! remoteIssueBatch . Any ( ) )
359+ {
360+ if ( index == 0 )
361+ {
362+ Logger . Log ( LogLevel . Warning , $ "No issuse were found using jql: { jql } ") ;
363+ }
364+ break ;
365+ }
366+
367+ foreach ( var issueKey in remoteIssueBatch )
368+ {
369+ if ( skipList . Contains ( issueKey ) )
370+ {
371+ Logger . Log ( LogLevel . Info , $ "Skipped Jira '{ issueKey } ' - already downloaded.") ;
372+ index ++ ;
373+ continue ;
374+ }
375+
376+ Logger . Log ( LogLevel . Info , $ "Processing { index + 1 } /{ totalItems } - '{ issueKey } '.") ;
377+ var issue = ProcessItem ( issueKey , skipList ) ;
378+
379+ if ( issue == null )
380+ continue ;
381+
382+ yield return issue ;
383+ index ++ ;
384+
385+ if ( downloadOptions . HasFlag ( DownloadOptions . IncludeParentEpics ) && ( issue . EpicParent != null ) && ! skipList . Contains ( issue . EpicParent ) )
386+ {
387+ Logger . Log ( LogLevel . Info , $ "Processing epic parent '{ issue . EpicParent } '.") ;
388+ var parentEpic = ProcessItem ( issue . EpicParent , skipList ) ;
389+ yield return parentEpic ;
390+ }
391+
392+ if ( downloadOptions . HasFlag ( DownloadOptions . IncludeParents ) && ( issue . Parent != null ) && ! skipList . Contains ( issue . Parent ) )
393+ {
394+ Logger . Log ( LogLevel . Info , $ "Processing parent issue '{ issue . Parent } '.") ;
395+ var parent = ProcessItem ( issue . Parent , skipList ) ;
396+ yield return parent ;
397+ }
398+
399+ if ( downloadOptions . HasFlag ( DownloadOptions . IncludeSubItems ) && ( issue . SubItems != null ) && issue . SubItems . Any ( ) )
400+ {
401+ foreach ( var subitemKey in issue . SubItems )
402+ {
403+ if ( ! skipList . Contains ( subitemKey ) )
404+ {
405+ Logger . Log ( LogLevel . Info , $ "Processing sub-item '{ subitemKey } '.") ;
406+ var subItem = ProcessItem ( subitemKey , skipList ) ;
407+ yield return subItem ;
408+ }
409+ }
410+ }
411+ }
412+ }
413+ }
414+ while ( nextPageToken != null ) ;
415+ }
416+
307417 public struct JiraVersion
308418 {
309419 public string Version { get ; set ; }
@@ -321,9 +431,27 @@ public int GetItemCount(string jql)
321431 Logger . Log ( LogLevel . Debug , $ "Get item count using query: '{ jql } '") ;
322432 try
323433 {
324- var response = _jiraServiceWrapper . RestClient . ExecuteRequestAsync ( Method . GET , $ "{ JiraApiV2 } /search?jql={ jql } &maxResults=0") . Result ;
434+ if ( Settings . JiraApiVersion == 2 )
435+ {
436+ var response = _jiraServiceWrapper . RestClient . ExecuteRequestAsync ( Method . GET , $ "{ JiraApi } /{ Settings . JiraApiVersion } /search?jql={ jql } &maxResults=0") . Result ;
437+ return ( int ) response . SelectToken ( "$.total" ) ;
438+ }
439+ else if ( Settings . JiraApiVersion == 3 )
440+ {
441+ var requestBody = new
442+ {
443+ jql = jql
444+ } ;
445+ var response = _jiraServiceWrapper . RestClient . ExecuteRequestAsync ( Method . POST , $ "{ JiraApi } /{ Settings . JiraApiVersion } /search/approximate-count", requestBody ) . Result ;
325446
326- return ( int ) response . SelectToken ( "$.total" ) ;
447+ return ( int ) response . SelectToken ( "$.count" ) ;
448+ }
449+ else
450+ {
451+ // Invalid API Version already checked in Initialize()
452+ Logger . Log ( LogLevel . Error , $ "Invalid Jira API version: { Settings . JiraApiVersion } . Must be either 2 or 3.") ;
453+ return - 1 ;
454+ }
327455 }
328456 catch ( Exception e )
329457 {
@@ -335,13 +463,13 @@ public int GetItemCount(string jql)
335463
336464 public JiraVersion GetJiraVersion ( )
337465 {
338- var response = ( JObject ) _jiraServiceWrapper . RestClient . ExecuteRequestAsync ( Method . GET , $ "{ JiraApiV2 } /serverInfo") . Result ;
466+ var response = ( JObject ) _jiraServiceWrapper . RestClient . ExecuteRequestAsync ( Method . GET , $ "{ JiraApi } / { Settings . JiraApiVersion } /serverInfo") . Result ;
339467 return new JiraVersion ( ( string ) response . SelectToken ( "$.version" ) , ( string ) response . SelectToken ( "$.deploymentType" ) ) ;
340468 }
341469
342470 public IEnumerable < JObject > DownloadChangelog ( string issueKey )
343471 {
344- var response = ( JObject ) _jiraServiceWrapper . RestClient . ExecuteRequestAsync ( Method . GET , $ "{ JiraApiV2 } /issue/{ issueKey } ?expand=changelog,renderedFields&fields=created") . Result ;
472+ var response = ( JObject ) _jiraServiceWrapper . RestClient . ExecuteRequestAsync ( Method . GET , $ "{ JiraApi } / { Settings . JiraApiVersion } /issue/{ issueKey } ?expand=changelog,renderedFields&fields=created") . Result ;
345473 return response . SelectTokens ( "$.changelog.histories[*]" ) . Cast < JObject > ( ) ;
346474 }
347475
@@ -350,7 +478,7 @@ public JObject DownloadIssue(string key)
350478 try
351479 {
352480 var response =
353- _jiraServiceWrapper . RestClient . ExecuteRequestAsync ( Method . GET , $ "{ JiraApiV2 } /issue/{ key } ?expand=renderedFields") . Result ;
481+ _jiraServiceWrapper . RestClient . ExecuteRequestAsync ( Method . GET , $ "{ JiraApi } / { Settings . JiraApiVersion } /issue/{ key } ?expand=renderedFields") . Result ;
354482
355483 var remoteItem = ( JObject ) response ;
356484 return remoteItem ;
@@ -360,7 +488,6 @@ public JObject DownloadIssue(string key)
360488 Logger . Log ( e , $ "Failed to download issue with key: { key } ") ;
361489 return default ( JObject ) ;
362490 }
363-
364491 }
365492
366493 public async Task < List < RevisionAction < JiraAttachment > > > DownloadAttachments ( JiraRevision rev )
@@ -440,7 +567,7 @@ public string GetCustomId(string propertyName)
440567
441568 if ( JiraNameFieldCache == null )
442569 {
443- response = ( JArray ) _jiraServiceWrapper . RestClient . ExecuteRequestAsync ( Method . GET , $ "{ JiraApiV2 } /field") . Result ;
570+ response = ( JArray ) _jiraServiceWrapper . RestClient . ExecuteRequestAsync ( Method . GET , $ "{ JiraApi } / { Settings . JiraApiVersion } /field") . Result ;
444571 JiraNameFieldCache = CreateFieldCacheLookup ( response , "name" , "id" ) ;
445572 }
446573
@@ -450,7 +577,7 @@ public string GetCustomId(string propertyName)
450577 {
451578 if ( JiraKeyFieldCache == null )
452579 {
453- response = response ?? ( JArray ) _jiraServiceWrapper . RestClient . ExecuteRequestAsync ( Method . GET , $ "{ JiraApiV2 } /field") . Result ;
580+ response = response ?? ( JArray ) _jiraServiceWrapper . RestClient . ExecuteRequestAsync ( Method . GET , $ "{ JiraApi } / { Settings . JiraApiVersion } /field") . Result ;
454581 JiraKeyFieldCache = CreateFieldCacheLookup ( response , "key" , "id" ) ;
455582 }
456583 customId = GetItemFromFieldCache ( propertyName , JiraKeyFieldCache ) ;
0 commit comments