jirasync 0.4.2 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,4 +1,3 @@
1
- *.gem
2
1
  *.rbc
3
2
  .bundle
4
3
  .config
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # jira-sync
2
2
 
3
- A suite of utilities to synchronise jira projects to the local file system
3
+ A suite of utilities to synchronise JIRA projects to the local file system
4
4
 
5
5
  ## Installation
6
6
 
@@ -44,26 +44,34 @@ The `sync_state.json` file contains information about the last sync, such as the
44
44
 
45
45
  The following statement will sync issues that have changed or where added during the last sync:
46
46
 
47
- jira-sync \
48
- --baseurl https://jira.myorganisation.com \
49
- --project MYPROJ \
50
- --user jira_user \
51
- --password jira_password \
52
- --target issues/MYPROJ/json \
53
- update
47
+ ~~~ {.bash}
48
+
49
+ jira-sync \
50
+ --baseurl https://jira.myorganisation.com \
51
+ --project MYPROJ \
52
+ --user jira_user \
53
+ --password jira_password \
54
+ --target issues/MYPROJ/json \
55
+ update
54
56
 
57
+ ~~~
55
58
 
56
59
  ### Formatting Issues
57
60
 
58
- While json files are very handy to use in code, they are not very readable. The `jira-format-issues` command
59
- formats json issues to markdown. It is invoked as follows:
61
+ While JSON files are very handy to use in code, they are not very readable. The `jira-format-issues` command
62
+ formats JSON issues to markdown. It is invoked as follows:
63
+
64
+ ~~~ {.bash}
60
65
 
61
66
  jira-format-issues \
62
67
  --source issues/MYPROJECT/json \
63
68
  --target issues/MYPROJECT/markdown
64
69
 
70
+ ~~~
71
+
65
72
  This will create the following structure in the `issues/MYPROJ/markdown`:
66
73
 
74
+
67
75
  MYPROJ-1.md
68
76
  MYPROJ-2.md
69
77
  MYPROJ-3.md
@@ -73,41 +81,43 @@ This will create the following structure in the `issues/MYPROJ/markdown`:
73
81
 
74
82
  The individual files look like this:
75
83
 
76
- [MYPROJ-1](https://jira.myorganisation.co/browse/MYPROJ-1): Build a working System
77
- ==================================================================================
84
+ ~~~ {.md}
85
+ [MYPROJ-1](https://jira.myorganisation.co/browse/MYPROJ-1): Build a working System
86
+ ==================================================================================
78
87
 
79
- Type
80
- : Story
88
+ Type
89
+ : Story
81
90
 
82
- Status
83
- : Closed
91
+ Status
92
+ : Closed
84
93
 
85
- Reporter
86
- : fleipold
94
+ Reporter
95
+ : fleipold
87
96
 
88
- Labels
89
- : triaged
97
+ Labels
98
+ : triaged
90
99
 
91
- Updated
92
- : 20. Jan 2014 11:30 (UTC)
100
+ Updated
101
+ : 20. Jan 2014 11:30 (UTC)
93
102
 
94
- Created
95
- : 01. Aug 2013 12:29 (UTC)
103
+ Created
104
+ : 01. Aug 2013 12:29 (UTC)
96
105
 
97
106
 
98
- Description
99
- -----------
107
+ Description
108
+ -----------
100
109
 
101
- The myproj system shall be built to be *delpoyable* and *working*.
110
+ The myproj system shall be built to be *delpoyable* and *working*.
102
111
 
103
112
 
104
- Comments
105
- --------
113
+ Comments
114
+ --------
106
115
 
107
- ### fleipold - 20. Jan 2014 12:19 (UTC):
116
+ ### fleipold - 20. Jan 2014 12:19 (UTC):
108
117
 
109
- Is this still relevant?
118
+ Is this still relevant?
110
119
 
120
+ ~~~
111
121
 
112
122
  These files can be easily searched by ensuring they get indexed by a desktop search engine, e.g.
113
123
  [spotlight](https://gist.github.com/gereon/3150445) on the Mac.
@@ -116,28 +126,39 @@ These files can be easily searched by ensuring they get indexed by a desktop sea
116
126
  data* fields which are rendered as definitions at the top of the ticket and *sections* that are rendered as paragraph
117
127
  with a heading. Here is an example file, `custom-data.json`:
118
128
 
119
- {
120
- "simple_fields" : {
121
- "Audience" : ["customfield_10123", "value"]
122
- },
123
- "sections" : {
124
- "Release Notes" : ["customfield_10806"]
125
- }
126
- }
129
+ ~~~ {.json}
130
+ {
131
+ "simple_fields" : {
132
+ "Audience" : ["customfield_10123", "value"]
133
+ },
134
+ "sections" : {
135
+ "Release Notes" : ["customfield_10806"]
136
+ }
137
+ }
138
+
139
+ ~~~
140
+
127
141
 
128
142
  This file can be passed in like this:
129
143
 
130
- jira-format-issues \
131
- --source issues/MYPROJECT/json \
132
- --target issues/MYPROJECT/markdown \
133
- --custom-data-path custom-data.json
144
+ ~~~ {.bash}
145
+
146
+ jira-format-issues \
147
+ --source issues/MYPROJECT/json \
148
+ --target issues/MYPROJECT/markdown \
149
+ --custom-data-path custom-data.json
150
+
151
+ ~~~
134
152
 
135
153
  ## Motivation
136
154
 
137
- Having a local, unix-friendly copy to avoid jira performance issues and make information available offline.
155
+ Having a local, unix-friendly copy to avoid JIRA performance issues and make information available offline.
138
156
 
139
157
  ## Potential Future Work
140
158
 
141
- * Remove tickets that have been moved to a different project
142
- * Use OAuth authentication
143
- * Improved error handling
159
+ - [X] Make progress bar work
160
+ - [X] Make output less noisy
161
+ - [ ] Deal with authentication problems explicitly
162
+ - [ ] Remove tickets that have been moved to a different project
163
+ - [ ] Use OAuth authentication
164
+
@@ -29,4 +29,5 @@ Gem::Specification.new do |s|
29
29
  s.add_dependency('trollop')
30
30
  s.add_dependency('httparty')
31
31
  s.add_dependency('parallel')
32
+ s.add_dependency('ruby-progressbar')
32
33
  end
@@ -4,6 +4,7 @@ module JiraSync
4
4
  require 'uri'
5
5
  require 'json'
6
6
 
7
+
7
8
  class FetchError < StandardError
8
9
  attr_reader :status, :url
9
10
 
@@ -24,12 +25,16 @@ module JiraSync
24
25
  @username = username
25
26
  @password = password
26
27
  @baseurl = baseurl
28
+ @timeout = 15
29
+ @first_requets_timeout = 60
27
30
  end
28
31
 
32
+
33
+
29
34
  def get(jira_id)
30
35
  url = "#{@baseurl}/rest/api/latest/issue/#{jira_id}"
31
36
  auth = {:username => @username, :password => @password}
32
- response = HTTParty.get url, {:basic_auth => auth}
37
+ response = HTTParty.get url, {:basic_auth => auth, :timeout => @timeout}
33
38
  if response.code == 200
34
39
  response.parsed_response
35
40
  else
@@ -41,11 +46,15 @@ module JiraSync
41
46
  url = "#{@baseurl}/rest/api/2/search?"
42
47
  auth = {:username => @username, :password => @password}
43
48
 
44
- response = HTTParty.get url, {:basic_auth => auth, :query => {:jql => 'project="' + project_id + '" order by created', fields: 'summary,updated', maxResults: '1'}}
49
+ response = HTTParty.get url, {
50
+ :basic_auth => auth,
51
+ :query => {:jql => 'project="' + project_id + '" order by created', fields: 'summary,updated', maxResults: '1'},
52
+ :timeout => @first_requets_timeout
53
+ }
45
54
  if response.code == 200
46
55
  response.parsed_response
47
56
  else
48
- raise FetchError.new(response.status, url)
57
+ raise FetchError.new(response.code, url)
49
58
  end
50
59
  end
51
60
 
@@ -54,22 +63,30 @@ module JiraSync
54
63
  auth = {:username => @username, :password => @password}
55
64
  jql = 'project = "' + project_id + '" AND updated > ' + (date.to_time.to_i * 1000).to_s
56
65
  # "' + date.to_s + '"'
57
- response = HTTParty.get url, {:basic_auth => auth, :query => {:jql => jql, fields: 'summary,updated', maxResults: '1000'}}
66
+ response = HTTParty.get url, {
67
+ :basic_auth => auth,
68
+ :query => {:jql => jql, fields: 'summary,updated', maxResults: '1000'},
69
+ :timeout => @timeout
70
+ }
58
71
  if response.code == 200
59
72
  response.parsed_response
60
73
  else
61
- raise FetchError.new(response.status, url)
74
+ raise FetchError.new(response.code, url)
62
75
  end
63
76
  end
64
77
 
65
78
  def project_info(project_id)
66
79
  url = "#{@baseurl}/rest/api/2/project/#{project_id}"
67
80
  auth = {:username => @username, :password => @password}
68
- response = HTTParty.get url, {:basic_auth => auth, :query => {:jql => 'project="' + project_id + '"', fields: 'summary,updated', maxResults: '50'}}
81
+ response = HTTParty.get url, {
82
+ :basic_auth => auth,
83
+ :query => {:jql => 'project="' + project_id + '"', fields: 'summary,updated', maxResults: '50'},
84
+ :timeout => @timeout
85
+ }
69
86
  if response.code == 200
70
87
  response.parse_response
71
88
  else
72
- raise FetchError(response.status, url)
89
+ raise FetchError(response.code, url)
73
90
  end
74
91
 
75
92
  end
@@ -19,32 +19,34 @@ module JiraSync
19
19
  # couldn't be fetched.
20
20
  def fetch(keys)
21
21
  keys_with_errors = []
22
- Parallel.each_with_index(keys, :in_threads => 64) do |key, index|
23
- STDERR.puts(key) if ((index % 100) == 0)
22
+ tickets_moved = []
23
+ Parallel.each_with_index(keys, :in_threads => 64, :progress => {title:"Fetching", output: STDERR}) do |key, index|
24
24
  begin
25
25
  issue = @client.get(key)
26
26
  issue_project_key = issue['fields']['project']['key']
27
27
  if (issue_project_key == @project_key)
28
28
  @repo.save(issue)
29
29
  else
30
- STDERR.puts("Skipping ticket #{key} which has moved to #{issue_project_key}.")
30
+ tickets_moved.push(issue_project_key)
31
31
  end
32
32
 
33
33
  rescue FetchError => e
34
34
  if (e.status != 404)
35
- STDERR.puts(e.to_s)
36
35
  keys_with_errors.push(key)
37
36
  else
38
- STDERR.puts("Ignoring 404 for ticket #{key}")
37
+ # Ticket has disappeared
39
38
  end
40
39
  rescue => e
41
40
  STDERR.puts(e.to_s)
42
41
  keys_with_errors.push(key)
43
42
  end
44
43
  end
45
- keys_with_errors.sort
44
+ keys_with_errors.sort!
45
+ STDERR.puts("Errors fetching these tickets: #{keys_with_errors.join(",")}")
46
+ keys_with_errors
46
47
  end
47
48
 
49
+ # Fetches all tickets for the project
48
50
  def fetch_all
49
51
  start_time = DateTime.now
50
52
 
@@ -54,16 +56,16 @@ module JiraSync
54
56
  @repo.save_state({"time" => start_time, "errors" => keys_with_errors})
55
57
  end
56
58
 
59
+ # Fetches only tickets that have been changed/ added since the previous fetch/ update
57
60
  def update()
58
61
  state = @repo.load_state()
59
62
  start_time = DateTime.now
60
63
  since = DateTime.parse(state['time']).new_offset(0)
61
64
  STDERR.puts("Fetching issues that have changes since #{since.to_s}")
62
65
  issues = @client.changed_since(@project_key, since)['issues'].map { |issue| issue['key'] }
63
- STDERR.puts("Updated Issues")
64
- STDERR.puts(issues.empty? ? "None" : issues.join(", "))
65
- STDERR.puts("Issues with earlier errors")
66
- STDERR.puts(state['errors'].empty? ? "None" : state['errors'].join(", "))
66
+ STDERR.puts("Updated Issues: #{issues.empty? ? "None" : issues.join(",")}")
67
+ STDERR.print("Retrying issues with earlier errors: ")
68
+ STDERR.puts(state['errors'].empty? ? "None" : state['errors'].join(","))
67
69
  keys_with_errors = fetch(issues + state['errors'])
68
70
  @repo.save_state({"time" => start_time, "errors" => keys_with_errors})
69
71
  end
@@ -1,3 +1,3 @@
1
1
  module JiraSync
2
- VERSION = "0.4.2"
2
+ VERSION = "0.4.3"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jirasync
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.4.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-03-20 00:00:00.000000000 Z
12
+ date: 2015-04-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: trollop
@@ -59,6 +59,22 @@ dependencies:
59
59
  - - ! '>='
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: ruby-progressbar
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
62
78
  description: ! "jirasync synchronises tickets from a jira project to the local\n file
63
79
  system. It supports a complete fetch operation as well as\n an
64
80
  incremental update.\n\n Each ticket is stored in a simple, pretty