jirametrics 2.5 → 2.30
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.
- checksums.yaml +4 -4
- data/bin/jirametrics-mcp +5 -0
- data/lib/jirametrics/aggregate_config.rb +16 -3
- data/lib/jirametrics/aging_work_bar_chart.rb +193 -133
- data/lib/jirametrics/aging_work_in_progress_chart.rb +138 -42
- data/lib/jirametrics/aging_work_table.rb +63 -19
- data/lib/jirametrics/anonymizer.rb +81 -6
- data/lib/jirametrics/atlassian_document_format.rb +160 -0
- data/lib/jirametrics/bar_chart_range.rb +17 -0
- data/lib/jirametrics/blocked_stalled_change.rb +6 -4
- data/lib/jirametrics/board.rb +73 -20
- data/lib/jirametrics/board_config.rb +10 -2
- data/lib/jirametrics/board_feature.rb +14 -0
- data/lib/jirametrics/board_movement_calculator.rb +155 -0
- data/lib/jirametrics/cfd_data_builder.rb +108 -0
- data/lib/jirametrics/change_item.rb +54 -18
- data/lib/jirametrics/chart_base.rb +203 -30
- data/lib/jirametrics/css_variable.rb +2 -2
- data/lib/jirametrics/cumulative_flow_diagram.rb +208 -0
- data/lib/jirametrics/cycle_time_config.rb +137 -0
- data/lib/jirametrics/cycletime_histogram.rb +17 -38
- data/lib/jirametrics/cycletime_scatterplot.rb +18 -87
- data/lib/jirametrics/daily_view.rb +306 -0
- data/lib/jirametrics/daily_wip_by_age_chart.rb +5 -8
- data/lib/jirametrics/daily_wip_by_blocked_stalled_chart.rb +15 -5
- data/lib/jirametrics/daily_wip_by_parent_chart.rb +4 -6
- data/lib/jirametrics/daily_wip_chart.rb +36 -16
- data/lib/jirametrics/data_quality_report.rb +251 -42
- data/lib/jirametrics/dependency_chart.rb +8 -6
- data/lib/jirametrics/download_config.rb +17 -2
- data/lib/jirametrics/downloader.rb +177 -108
- data/lib/jirametrics/downloader_for_cloud.rb +287 -0
- data/lib/jirametrics/downloader_for_data_center.rb +95 -0
- data/lib/jirametrics/estimate_accuracy_chart.rb +75 -14
- data/lib/jirametrics/estimation_configuration.rb +25 -0
- data/lib/jirametrics/examples/aggregated_project.rb +5 -8
- data/lib/jirametrics/examples/standard_project.rb +54 -38
- data/lib/jirametrics/expedited_chart.rb +10 -9
- data/lib/jirametrics/exporter.rb +51 -16
- data/lib/jirametrics/file_config.rb +21 -6
- data/lib/jirametrics/file_system.rb +96 -4
- data/lib/jirametrics/fix_version.rb +13 -0
- data/lib/jirametrics/flow_efficiency_scatterplot.rb +115 -0
- data/lib/jirametrics/github_gateway.rb +115 -0
- data/lib/jirametrics/groupable_issue_chart.rb +12 -4
- data/lib/jirametrics/grouping_rules.rb +26 -4
- data/lib/jirametrics/html/aging_work_bar_chart.erb +8 -17
- data/lib/jirametrics/html/aging_work_in_progress_chart.erb +24 -5
- data/lib/jirametrics/html/aging_work_table.erb +13 -4
- data/lib/jirametrics/html/collapsible_issues_panel.erb +2 -2
- data/lib/jirametrics/html/cumulative_flow_diagram.erb +503 -0
- data/lib/jirametrics/html/daily_wip_chart.erb +41 -15
- data/lib/jirametrics/html/estimate_accuracy_chart.erb +4 -12
- data/lib/jirametrics/html/expedited_chart.erb +7 -24
- data/lib/jirametrics/html/flow_efficiency_scatterplot.erb +81 -0
- data/lib/jirametrics/html/hierarchy_table.erb +1 -1
- data/lib/jirametrics/html/index.css +336 -62
- data/lib/jirametrics/html/index.erb +16 -21
- data/lib/jirametrics/html/index.js +164 -0
- data/lib/jirametrics/html/legacy_colors.css +174 -0
- data/lib/jirametrics/html/sprint_burndown.erb +18 -25
- data/lib/jirametrics/html/throughput_chart.erb +43 -21
- data/lib/jirametrics/html/time_based_histogram.erb +123 -0
- data/lib/jirametrics/html/{cycletime_scatterplot.erb → time_based_scatterplot.erb} +16 -21
- data/lib/jirametrics/html/wip_by_column_chart.erb +250 -0
- data/lib/jirametrics/html_generator.rb +32 -0
- data/lib/jirametrics/html_report_config.rb +83 -76
- data/lib/jirametrics/issue.rb +481 -97
- data/lib/jirametrics/issue_collection.rb +33 -0
- data/lib/jirametrics/issue_printer.rb +97 -0
- data/lib/jirametrics/jira_gateway.rb +96 -16
- data/lib/jirametrics/mcp_server.rb +531 -0
- data/lib/jirametrics/project_config.rb +374 -130
- data/lib/jirametrics/pull_request.rb +30 -0
- data/lib/jirametrics/pull_request_cycle_time_histogram.rb +77 -0
- data/lib/jirametrics/pull_request_cycle_time_scatterplot.rb +88 -0
- data/lib/jirametrics/pull_request_review.rb +13 -0
- data/lib/jirametrics/raw_javascript.rb +17 -0
- data/lib/jirametrics/self_or_issue_dispatcher.rb +2 -0
- data/lib/jirametrics/settings.json +7 -1
- data/lib/jirametrics/sprint.rb +13 -0
- data/lib/jirametrics/sprint_burndown.rb +47 -39
- data/lib/jirametrics/sprint_issue_change_data.rb +3 -3
- data/lib/jirametrics/status.rb +84 -19
- data/lib/jirametrics/status_collection.rb +83 -38
- data/lib/jirametrics/stitcher.rb +81 -0
- data/lib/jirametrics/throughput_by_completed_resolution_chart.rb +22 -0
- data/lib/jirametrics/throughput_chart.rb +73 -23
- data/lib/jirametrics/time_based_histogram.rb +139 -0
- data/lib/jirametrics/time_based_scatterplot.rb +107 -0
- data/lib/jirametrics/user.rb +12 -0
- data/lib/jirametrics/value_equality.rb +2 -2
- data/lib/jirametrics/wip_by_column_chart.rb +236 -0
- data/lib/jirametrics.rb +101 -66
- metadata +72 -16
- data/lib/jirametrics/cycletime_config.rb +0 -69
- data/lib/jirametrics/discard_changes_before.rb +0 -37
- data/lib/jirametrics/html/cycletime_histogram.erb +0 -47
- data/lib/jirametrics/html/data_quality_report.erb +0 -126
|
@@ -20,15 +20,30 @@ class DownloadConfig
|
|
|
20
20
|
@rolling_date_count
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
def no_earlier_than date =
|
|
24
|
-
@no_earlier_than = Date.parse(date) unless date
|
|
23
|
+
def no_earlier_than date = :not_set
|
|
24
|
+
@no_earlier_than = Date.parse(date) unless date == :not_set
|
|
25
25
|
@no_earlier_than
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
def github_repos
|
|
29
|
+
@github_repos ||= []
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def github_repo *repos
|
|
33
|
+
github_repos.concat(repos.map { |r| normalize_github_repo(r) })
|
|
34
|
+
end
|
|
35
|
+
|
|
28
36
|
def start_date today:
|
|
29
37
|
date = today.to_date - @rolling_date_count if @rolling_date_count
|
|
30
38
|
date = [date, @no_earlier_than].max if date && @no_earlier_than
|
|
31
39
|
date = @no_earlier_than if date.nil? && @no_earlier_than
|
|
32
40
|
date
|
|
33
41
|
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def normalize_github_repo repo
|
|
46
|
+
match = repo.match(%r{github\.com/([^/]+/[^/]+?)/?$})
|
|
47
|
+
match ? match[1] : repo
|
|
48
|
+
end
|
|
34
49
|
end
|
|
@@ -3,21 +3,53 @@
|
|
|
3
3
|
require 'cgi'
|
|
4
4
|
require 'json'
|
|
5
5
|
|
|
6
|
+
class DownloadIssueData
|
|
7
|
+
attr_accessor :key, :found_in_primary_query, :last_modified,
|
|
8
|
+
:up_to_date, :cache_path, :issue
|
|
9
|
+
|
|
10
|
+
def initialize(
|
|
11
|
+
key:,
|
|
12
|
+
found_in_primary_query: true,
|
|
13
|
+
last_modified: nil,
|
|
14
|
+
up_to_date: true,
|
|
15
|
+
cache_path: nil,
|
|
16
|
+
issue: nil
|
|
17
|
+
)
|
|
18
|
+
@key = key
|
|
19
|
+
@found_in_primary_query = found_in_primary_query
|
|
20
|
+
@last_modified = last_modified
|
|
21
|
+
@up_to_date = up_to_date
|
|
22
|
+
@cache_path = cache_path
|
|
23
|
+
@issue = issue
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
6
27
|
class Downloader
|
|
7
|
-
CURRENT_METADATA_VERSION =
|
|
28
|
+
CURRENT_METADATA_VERSION = 5
|
|
8
29
|
|
|
9
|
-
attr_accessor :metadata
|
|
30
|
+
attr_accessor :metadata
|
|
10
31
|
attr_reader :file_system
|
|
11
32
|
|
|
12
33
|
# For testing only
|
|
13
34
|
attr_reader :start_date_in_query, :board_id_to_filter_id
|
|
14
35
|
|
|
15
|
-
def
|
|
36
|
+
def self.create download_config:, file_system:, jira_gateway:, github_pr_cache: {}
|
|
37
|
+
is_cloud = jira_gateway.settings['jira_cloud'] || jira_gateway.cloud?
|
|
38
|
+
(is_cloud ? DownloaderForCloud : DownloaderForDataCenter).new(
|
|
39
|
+
download_config: download_config,
|
|
40
|
+
file_system: file_system,
|
|
41
|
+
jira_gateway: jira_gateway,
|
|
42
|
+
github_pr_cache: github_pr_cache
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def initialize download_config:, file_system:, jira_gateway:, github_pr_cache: {}
|
|
16
47
|
@metadata = {}
|
|
17
48
|
@download_config = download_config
|
|
18
49
|
@target_path = @download_config.project_config.target_path
|
|
19
50
|
@file_system = file_system
|
|
20
51
|
@jira_gateway = jira_gateway
|
|
52
|
+
@github_pr_cache = github_pr_cache
|
|
21
53
|
@board_id_to_filter_id = {}
|
|
22
54
|
|
|
23
55
|
@issue_keys_downloaded_in_current_run = []
|
|
@@ -28,7 +60,6 @@ class Downloader
|
|
|
28
60
|
log '', both: true
|
|
29
61
|
log @download_config.project_config.name, both: true
|
|
30
62
|
|
|
31
|
-
init_gateway
|
|
32
63
|
load_metadata
|
|
33
64
|
|
|
34
65
|
if @metadata['no-download']
|
|
@@ -39,94 +70,49 @@ class Downloader
|
|
|
39
70
|
# board_ids = @download_config.board_ids
|
|
40
71
|
|
|
41
72
|
remove_old_files
|
|
73
|
+
update_status_history_file
|
|
42
74
|
download_statuses
|
|
43
75
|
find_board_ids.each do |id|
|
|
44
|
-
download_board_configuration board_id: id
|
|
45
|
-
|
|
76
|
+
board = download_board_configuration board_id: id
|
|
77
|
+
board.project_config = @download_config.project_config
|
|
78
|
+
download_issues board: board
|
|
46
79
|
end
|
|
80
|
+
download_users
|
|
47
81
|
|
|
48
82
|
save_metadata
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def init_gateway
|
|
52
|
-
@jira_gateway.load_jira_config(@download_config.project_config.jira_config)
|
|
53
|
-
@jira_gateway.ignore_ssl_errors = @download_config.project_config.settings['ignore_ssl_errors']
|
|
83
|
+
download_github_prs if @download_config.github_repos.any?
|
|
54
84
|
end
|
|
55
85
|
|
|
56
86
|
def log text, both: false
|
|
57
|
-
@file_system.log text
|
|
58
|
-
puts text if both && !@quiet_mode
|
|
87
|
+
@file_system.log text, also_write_to_stderr: both
|
|
59
88
|
end
|
|
60
89
|
|
|
61
|
-
def
|
|
62
|
-
|
|
63
|
-
raise 'Board ids must be specified' if ids.empty?
|
|
64
|
-
|
|
65
|
-
ids
|
|
90
|
+
def log_start text
|
|
91
|
+
@file_system.log_start text
|
|
66
92
|
end
|
|
67
93
|
|
|
68
|
-
def
|
|
69
|
-
|
|
70
|
-
path = "#{@target_path}#{@download_config.project_config.file_prefix}_issues/"
|
|
71
|
-
unless Dir.exist?(path)
|
|
72
|
-
log " Creating path #{path}"
|
|
73
|
-
Dir.mkdir(path)
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
filter_id = @board_id_to_filter_id[board_id]
|
|
77
|
-
jql = make_jql(filter_id: filter_id)
|
|
78
|
-
jira_search_by_jql(jql: jql, initial_query: true, board_id: board_id, path: path)
|
|
79
|
-
|
|
80
|
-
log " Downloading linked issues for board #{board_id}", both: true
|
|
81
|
-
loop do
|
|
82
|
-
@issue_keys_pending_download.reject! { |key| @issue_keys_downloaded_in_current_run.include? key }
|
|
83
|
-
break if @issue_keys_pending_download.empty?
|
|
84
|
-
|
|
85
|
-
keys_to_request = @issue_keys_pending_download[0..99]
|
|
86
|
-
@issue_keys_pending_download.reject! { |key| keys_to_request.include? key }
|
|
87
|
-
jql = "key in (#{keys_to_request.join(', ')})"
|
|
88
|
-
jira_search_by_jql(jql: jql, initial_query: false, board_id: board_id, path: path)
|
|
89
|
-
end
|
|
94
|
+
def start_progress
|
|
95
|
+
@file_system.start_progress
|
|
90
96
|
end
|
|
91
97
|
|
|
92
|
-
def
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
log " JQL: #{jql}"
|
|
97
|
-
escaped_jql = CGI.escape jql
|
|
98
|
-
|
|
99
|
-
max_results = 100
|
|
100
|
-
start_at = 0
|
|
101
|
-
total = 1
|
|
102
|
-
while start_at < total
|
|
103
|
-
json = @jira_gateway.call_url relative_url: '/rest/api/2/search' \
|
|
104
|
-
"?jql=#{escaped_jql}&maxResults=#{max_results}&startAt=#{start_at}&expand=changelog&fields=*all"
|
|
105
|
-
|
|
106
|
-
exit_if_call_failed json
|
|
107
|
-
|
|
108
|
-
json['issues'].each do |issue_json|
|
|
109
|
-
issue_json['exporter'] = {
|
|
110
|
-
'in_initial_query' => initial_query
|
|
111
|
-
}
|
|
112
|
-
identify_other_issues_to_be_downloaded issue_json
|
|
113
|
-
file = "#{issue_json['key']}-#{board_id}.json"
|
|
114
|
-
|
|
115
|
-
@file_system.save_json(json: issue_json, filename: File.join(path, file))
|
|
116
|
-
end
|
|
98
|
+
def progress_dot message = nil
|
|
99
|
+
@file_system.log message if message
|
|
100
|
+
@file_system.progress_dot
|
|
101
|
+
end
|
|
117
102
|
|
|
118
|
-
|
|
119
|
-
|
|
103
|
+
def end_progress
|
|
104
|
+
@file_system.end_progress
|
|
105
|
+
end
|
|
120
106
|
|
|
121
|
-
|
|
122
|
-
|
|
107
|
+
def find_board_ids
|
|
108
|
+
ids = @download_config.project_config.board_configs.collect(&:id)
|
|
109
|
+
raise 'Board ids must be specified' if ids.empty?
|
|
123
110
|
|
|
124
|
-
|
|
125
|
-
end
|
|
111
|
+
ids
|
|
126
112
|
end
|
|
127
113
|
|
|
128
|
-
def identify_other_issues_to_be_downloaded raw_issue
|
|
129
|
-
issue = Issue.new raw: raw_issue, board:
|
|
114
|
+
def identify_other_issues_to_be_downloaded raw_issue:, board:
|
|
115
|
+
issue = Issue.new raw: raw_issue, board: board
|
|
130
116
|
@issue_keys_downloaded_in_current_run << issue.key
|
|
131
117
|
|
|
132
118
|
# Parent
|
|
@@ -137,22 +123,6 @@ class Downloader
|
|
|
137
123
|
issue.raw['fields']['subtasks']&.each do |raw_subtask|
|
|
138
124
|
@issue_keys_pending_download << raw_subtask['key']
|
|
139
125
|
end
|
|
140
|
-
|
|
141
|
-
# Links
|
|
142
|
-
# We shouldn't blindly follow links as some, like cloners, aren't valuable and are just wasting time/effort
|
|
143
|
-
# to download
|
|
144
|
-
# issue.raw['fields']['issuelinks'].each do |raw_link|
|
|
145
|
-
# @issue_keys_pending_download << IssueLink(raw: raw_link).other_issue.key
|
|
146
|
-
# end
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
def exit_if_call_failed json
|
|
150
|
-
# Sometimes Jira returns the singular form of errorMessage and sometimes the plural. Consistency FTW.
|
|
151
|
-
return unless json['error'] || json['errorMessages'] || json['errorMessage']
|
|
152
|
-
|
|
153
|
-
log "Download failed. See #{@file_system.logfile_name} for details.", both: true
|
|
154
|
-
log " #{JSON.pretty_generate(json)}"
|
|
155
|
-
exit 1
|
|
156
126
|
end
|
|
157
127
|
|
|
158
128
|
def download_statuses
|
|
@@ -161,29 +131,85 @@ class Downloader
|
|
|
161
131
|
|
|
162
132
|
@file_system.save_json(
|
|
163
133
|
json: json,
|
|
164
|
-
filename:
|
|
134
|
+
filename: File.join(@target_path, "#{file_prefix}_statuses.json")
|
|
165
135
|
)
|
|
166
136
|
end
|
|
167
137
|
|
|
138
|
+
def download_users
|
|
139
|
+
return unless @jira_gateway.cloud?
|
|
140
|
+
|
|
141
|
+
log ' Downloading all users', both: true
|
|
142
|
+
json = @jira_gateway.call_url relative_url: '/rest/api/2/users'
|
|
143
|
+
|
|
144
|
+
@file_system.save_json(
|
|
145
|
+
json: json,
|
|
146
|
+
filename: File.join(@target_path, "#{file_prefix}_users.json")
|
|
147
|
+
)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def update_status_history_file
|
|
151
|
+
status_filename = File.join(@target_path, "#{file_prefix}_statuses.json")
|
|
152
|
+
return unless file_system.file_exist? status_filename
|
|
153
|
+
|
|
154
|
+
status_json = file_system.load_json(status_filename)
|
|
155
|
+
|
|
156
|
+
history_filename = File.join(@target_path, "#{file_prefix}_status_history.json")
|
|
157
|
+
history_json = file_system.load_json(history_filename) if file_system.file_exist? history_filename
|
|
158
|
+
|
|
159
|
+
if history_json
|
|
160
|
+
file_system.log ' Updating status history file', also_write_to_stderr: true
|
|
161
|
+
else
|
|
162
|
+
file_system.log ' Creating status history file', also_write_to_stderr: true
|
|
163
|
+
history_json = []
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
status_json.each do |status_item|
|
|
167
|
+
id = status_item['id']
|
|
168
|
+
history_item = history_json.find { |s| s['id'] == id }
|
|
169
|
+
history_json.delete(history_item) if history_item
|
|
170
|
+
history_json << status_item
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
file_system.save_json(filename: history_filename, json: history_json)
|
|
174
|
+
end
|
|
175
|
+
|
|
168
176
|
def download_board_configuration board_id:
|
|
169
177
|
log " Downloading board configuration for board #{board_id}", both: true
|
|
170
178
|
json = @jira_gateway.call_url relative_url: "/rest/agile/1.0/board/#{board_id}/configuration"
|
|
171
179
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
180
|
+
@file_system.save_json(
|
|
181
|
+
json: json,
|
|
182
|
+
filename: File.join(@target_path, "#{file_prefix}_board_#{board_id}_configuration.json")
|
|
183
|
+
)
|
|
176
184
|
|
|
177
185
|
# We have a reported bug that blew up on this line. Moved it after the save so we can
|
|
178
186
|
# actually look at the returned json.
|
|
179
187
|
@board_id_to_filter_id[board_id] = json['filter']['id'].to_i
|
|
180
188
|
|
|
189
|
+
if json['type'] == 'simple'
|
|
190
|
+
features_json = download_features board_id: board_id
|
|
191
|
+
if BoardFeature.from_raw(features_json).any? { |f| f.name == 'jsw.agility.sprints' && f.enabled? }
|
|
192
|
+
download_sprints board_id: board_id
|
|
193
|
+
end
|
|
194
|
+
end
|
|
181
195
|
download_sprints board_id: board_id if json['type'] == 'scrum'
|
|
196
|
+
# TODO: Should be passing actual statuses, not empty list
|
|
197
|
+
Board.new raw: json, possible_statuses: StatusCollection.new
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def download_features board_id:
|
|
201
|
+
log " Downloading features for board #{board_id}", both: true
|
|
202
|
+
json = @jira_gateway.call_url relative_url: "/rest/agile/1.0/board/#{board_id}/features"
|
|
203
|
+
|
|
204
|
+
@file_system.save_json(
|
|
205
|
+
json: json,
|
|
206
|
+
filename: File.join(@target_path, "#{file_prefix}_board_#{board_id}_features.json")
|
|
207
|
+
)
|
|
208
|
+
json
|
|
182
209
|
end
|
|
183
210
|
|
|
184
211
|
def download_sprints board_id:
|
|
185
212
|
log " Downloading sprints for board #{board_id}", both: true
|
|
186
|
-
file_prefix = @download_config.project_config.file_prefix
|
|
187
213
|
max_results = 100
|
|
188
214
|
start_at = 0
|
|
189
215
|
is_last = false
|
|
@@ -191,11 +217,10 @@ class Downloader
|
|
|
191
217
|
while is_last == false
|
|
192
218
|
json = @jira_gateway.call_url relative_url: "/rest/agile/1.0/board/#{board_id}/sprint?" \
|
|
193
219
|
"maxResults=#{max_results}&startAt=#{start_at}"
|
|
194
|
-
exit_if_call_failed json
|
|
195
220
|
|
|
196
221
|
@file_system.save_json(
|
|
197
222
|
json: json,
|
|
198
|
-
filename:
|
|
223
|
+
filename: File.join(@target_path, "#{file_prefix}_board_#{board_id}_sprints_#{start_at}.json")
|
|
199
224
|
)
|
|
200
225
|
is_last = json['isLast']
|
|
201
226
|
max_results = json['maxResults']
|
|
@@ -208,7 +233,7 @@ class Downloader
|
|
|
208
233
|
end
|
|
209
234
|
|
|
210
235
|
def metadata_pathname
|
|
211
|
-
|
|
236
|
+
File.join(@target_path, "#{file_prefix}_meta.json")
|
|
212
237
|
end
|
|
213
238
|
|
|
214
239
|
def load_metadata
|
|
@@ -224,19 +249,29 @@ class Downloader
|
|
|
224
249
|
value = Date.parse(value) if value.is_a?(String) && value =~ /^\d{4}-\d{2}-\d{2}$/
|
|
225
250
|
@metadata[key] = value
|
|
226
251
|
end
|
|
252
|
+
|
|
227
253
|
end
|
|
228
254
|
|
|
229
255
|
# Even if this is the old format, we want to obey this one tag
|
|
230
256
|
@metadata['no-download'] = hash['no-download'] if hash['no-download']
|
|
231
257
|
end
|
|
232
258
|
|
|
259
|
+
def timezone_offset
|
|
260
|
+
@download_config.project_config.exporter.timezone_offset
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def today_in_project_timezone
|
|
264
|
+
Time.now.getlocal(timezone_offset).to_date
|
|
265
|
+
end
|
|
266
|
+
|
|
233
267
|
def save_metadata
|
|
234
268
|
@metadata['version'] = CURRENT_METADATA_VERSION
|
|
269
|
+
@metadata['rolling_date_count'] = @download_config.rolling_date_count
|
|
235
270
|
@metadata['date_start_from_last_query'] = @start_date_in_query if @start_date_in_query
|
|
236
271
|
|
|
237
272
|
if @download_date_range.nil?
|
|
238
273
|
log "Making up a date range in meta since one wasn't specified. You'll want to change that.", both: true
|
|
239
|
-
today =
|
|
274
|
+
today = today_in_project_timezone
|
|
240
275
|
@download_date_range = (today - 7)..today
|
|
241
276
|
end
|
|
242
277
|
|
|
@@ -251,17 +286,17 @@ class Downloader
|
|
|
251
286
|
end
|
|
252
287
|
|
|
253
288
|
def remove_old_files
|
|
254
|
-
file_prefix = @download_config.project_config.file_prefix
|
|
255
289
|
Dir.foreach @target_path do |file|
|
|
256
290
|
next unless file.match?(/^#{file_prefix}_\d+\.json$/)
|
|
291
|
+
next if file == "#{file_prefix}_status_history.json"
|
|
257
292
|
|
|
258
|
-
File.unlink
|
|
293
|
+
File.unlink File.join(@target_path, file)
|
|
259
294
|
end
|
|
260
295
|
|
|
261
296
|
return if @cached_data_format_is_current
|
|
262
297
|
|
|
263
298
|
# Also throw away all the previously downloaded issues.
|
|
264
|
-
path = File.join
|
|
299
|
+
path = File.join(@target_path, "#{file_prefix}_issues")
|
|
265
300
|
return unless File.exist? path
|
|
266
301
|
|
|
267
302
|
Dir.foreach path do |file|
|
|
@@ -271,7 +306,8 @@ class Downloader
|
|
|
271
306
|
end
|
|
272
307
|
end
|
|
273
308
|
|
|
274
|
-
def make_jql filter_id:, today:
|
|
309
|
+
def make_jql filter_id:, today: nil
|
|
310
|
+
today ||= today_in_project_timezone
|
|
275
311
|
segments = []
|
|
276
312
|
segments << "filter=#{filter_id}"
|
|
277
313
|
|
|
@@ -279,11 +315,7 @@ class Downloader
|
|
|
279
315
|
|
|
280
316
|
if start_date
|
|
281
317
|
@download_date_range = start_date..today.to_date
|
|
282
|
-
|
|
283
|
-
# For an incremental download, we want to query from the end of the previous one, not from the
|
|
284
|
-
# beginning of the full range.
|
|
285
|
-
@start_date_in_query = metadata['date_end'] || @download_date_range.begin
|
|
286
|
-
log " Incremental download only. Pulling from #{@start_date_in_query}", both: true if metadata['date_end']
|
|
318
|
+
@start_date_in_query = @download_date_range.begin
|
|
287
319
|
|
|
288
320
|
# Catch-all to pick up anything that's been around since before the range started but hasn't
|
|
289
321
|
# had an update during the range.
|
|
@@ -299,4 +331,41 @@ class Downloader
|
|
|
299
331
|
|
|
300
332
|
segments.join ' AND '
|
|
301
333
|
end
|
|
334
|
+
|
|
335
|
+
def download_github_prs
|
|
336
|
+
project_keys = extract_project_keys_from_downloaded_issues
|
|
337
|
+
if project_keys.empty?
|
|
338
|
+
log ' No project keys found in downloaded issues, skipping GitHub PR download', both: true
|
|
339
|
+
return
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
prs = @download_config.github_repos.flat_map do |repo|
|
|
343
|
+
GithubGateway.new(
|
|
344
|
+
repo: repo,
|
|
345
|
+
project_keys: project_keys,
|
|
346
|
+
file_system: @file_system,
|
|
347
|
+
raw_pr_cache: @github_pr_cache
|
|
348
|
+
).fetch_pull_requests(since: @download_date_range&.begin)
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
@file_system.save_json(
|
|
352
|
+
json: prs.map(&:raw),
|
|
353
|
+
filename: File.join(@target_path, "#{file_prefix}_github_prs.json")
|
|
354
|
+
)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def extract_project_keys_from_downloaded_issues
|
|
358
|
+
path = File.join(@target_path, "#{file_prefix}_issues")
|
|
359
|
+
return [] unless @file_system.dir_exist?(path)
|
|
360
|
+
|
|
361
|
+
keys = []
|
|
362
|
+
@file_system.foreach(path) do |filename|
|
|
363
|
+
keys << filename.split('-').first if filename.match?(/^[A-Z][A-Z_0-9]+-\d+-\d+\.json$/)
|
|
364
|
+
end
|
|
365
|
+
keys.uniq
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def file_prefix
|
|
369
|
+
@download_config.project_config.get_file_prefix
|
|
370
|
+
end
|
|
302
371
|
end
|