jirasync 0.4.2 → 0.4.3
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.
- data/.gitignore +0 -1
- data/README.md +68 -47
- data/jirasync.gemspec +1 -0
- data/lib/jirasync/jira_client.rb +24 -7
- data/lib/jirasync/syncer.rb +12 -10
- data/lib/jirasync/version.rb +1 -1
- metadata +18 -2
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# jira-sync
|
2
2
|
|
3
|
-
A suite of utilities to synchronise
|
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
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
59
|
-
formats
|
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
|
-
|
77
|
-
|
84
|
+
~~~ {.md}
|
85
|
+
[MYPROJ-1](https://jira.myorganisation.co/browse/MYPROJ-1): Build a working System
|
86
|
+
==================================================================================
|
78
87
|
|
79
|
-
|
80
|
-
|
88
|
+
Type
|
89
|
+
: Story
|
81
90
|
|
82
|
-
|
83
|
-
|
91
|
+
Status
|
92
|
+
: Closed
|
84
93
|
|
85
|
-
|
86
|
-
|
94
|
+
Reporter
|
95
|
+
: fleipold
|
87
96
|
|
88
|
-
|
89
|
-
|
97
|
+
Labels
|
98
|
+
: triaged
|
90
99
|
|
91
|
-
|
92
|
-
|
100
|
+
Updated
|
101
|
+
: 20. Jan 2014 11:30 (UTC)
|
93
102
|
|
94
|
-
|
95
|
-
|
103
|
+
Created
|
104
|
+
: 01. Aug 2013 12:29 (UTC)
|
96
105
|
|
97
106
|
|
98
|
-
|
99
|
-
|
107
|
+
Description
|
108
|
+
-----------
|
100
109
|
|
101
|
-
|
110
|
+
The myproj system shall be built to be *delpoyable* and *working*.
|
102
111
|
|
103
112
|
|
104
|
-
|
105
|
-
|
113
|
+
Comments
|
114
|
+
--------
|
106
115
|
|
107
|
-
|
116
|
+
### fleipold - 20. Jan 2014 12:19 (UTC):
|
108
117
|
|
109
|
-
|
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
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
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
|
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
|
-
|
142
|
-
|
143
|
-
|
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
|
+
|
data/jirasync.gemspec
CHANGED
data/lib/jirasync/jira_client.rb
CHANGED
@@ -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, {
|
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.
|
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, {
|
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.
|
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, {
|
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.
|
89
|
+
raise FetchError(response.code, url)
|
73
90
|
end
|
74
91
|
|
75
92
|
end
|
data/lib/jirasync/syncer.rb
CHANGED
@@ -19,32 +19,34 @@ module JiraSync
|
|
19
19
|
# couldn't be fetched.
|
20
20
|
def fetch(keys)
|
21
21
|
keys_with_errors = []
|
22
|
-
|
23
|
-
|
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
|
-
|
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
|
-
|
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.
|
65
|
-
STDERR.puts("
|
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
|
data/lib/jirasync/version.rb
CHANGED
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.
|
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-
|
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
|