jirametrics 2.12.1 → 2.13pre2
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/lib/jirametrics/atlassian_document_format.rb +114 -0
- data/lib/jirametrics/change_item.rb +1 -0
- data/lib/jirametrics/daily_view.rb +14 -17
- data/lib/jirametrics/downloader.rb +52 -23
- data/lib/jirametrics/html/index.erb +2 -2
- data/lib/jirametrics/jira_gateway.rb +4 -0
- data/lib/jirametrics.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 86cc390c570ee4e3dcbcc8544ff9f78c02c648f65051c4765809728bc8f60dee
|
4
|
+
data.tar.gz: fc700a3285e4e0ed5aac26e7490aac787bdd777070dad27b3ece23b8bbfe7d4b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: db561b3fb89c5e102cafb5a1becd55d198cd8ef81f433b18a7e4a967c618fdace5f7b960c3f29efe20f240dff673898e435c0656a92a8fda5974f7346bfc27ce
|
7
|
+
data.tar.gz: dbcd7a50423efc68a69f889bfcfeabe56e78820946c3d5d68b4b28bf434a71fd308c62dd70c14d06edee2e85ba7b5dfe9d48bf388811300cbf869fe7d6215105
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class AtlassianDocumentFormat
|
4
|
+
attr_reader :users
|
5
|
+
|
6
|
+
def initialize users:
|
7
|
+
@users = users
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_html input
|
11
|
+
if input.is_a? String
|
12
|
+
input
|
13
|
+
.gsub(/{color:(#\w{6})}([^{]+){color}/, '<span style="color: \1">\2</span>') # Colours
|
14
|
+
.gsub(/\[~accountid:([^\]]+)\]/) { expand_account_id $1 } # Tagged people
|
15
|
+
.gsub(/\[([^\|]+)\|(https?[^\]]+)\]/, '<a href="\2">\1</a>') # URLs
|
16
|
+
.gsub("\n", '<br />')
|
17
|
+
else
|
18
|
+
input['content'].collect { |element| adf_node_to_html element }.join("\n")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# ADF is Atlassian Document Format
|
23
|
+
# https://developer.atlassian.com/cloud/jira/platform/apis/document/structure/
|
24
|
+
def adf_node_to_html node
|
25
|
+
closing_tag = nil
|
26
|
+
result = +''
|
27
|
+
case node['type']
|
28
|
+
when 'paragraph'
|
29
|
+
result << '<p>'
|
30
|
+
closing_tag = '</p>'
|
31
|
+
when 'text'
|
32
|
+
marks = adf_marks_to_html node['marks']
|
33
|
+
result << marks.collect(&:first).join
|
34
|
+
result << node['text']
|
35
|
+
result << marks.collect(&:last).join
|
36
|
+
when 'bulletList'
|
37
|
+
result << '<ul>'
|
38
|
+
closing_tag = '</ul>'
|
39
|
+
when 'orderedList'
|
40
|
+
result << '<ol>'
|
41
|
+
closing_tag = '</ol>'
|
42
|
+
when 'listItem'
|
43
|
+
result << '<li>'
|
44
|
+
closing_tag = '</li>'
|
45
|
+
when 'table'
|
46
|
+
result << '<table>'
|
47
|
+
closing_tag = '</table>'
|
48
|
+
when 'tableRow'
|
49
|
+
result << '<tr>'
|
50
|
+
closing_tag = '</tr>'
|
51
|
+
when 'tableCell'
|
52
|
+
result << '<td>'
|
53
|
+
closing_tag = '</td>'
|
54
|
+
when 'tableHeader'
|
55
|
+
result << '<th>'
|
56
|
+
closing_tag = '</th>'
|
57
|
+
when 'mention'
|
58
|
+
user = node['attrs']['text']
|
59
|
+
result << "<b>#{user}</b>"
|
60
|
+
when 'taskList'
|
61
|
+
result << "<ul class='taskList'>"
|
62
|
+
closing_tag = '</ul>'
|
63
|
+
when 'taskItem'
|
64
|
+
state = node['attrs']['state'] == 'TODO' ? '☐' : '☑'
|
65
|
+
result << "<li>#{state} "
|
66
|
+
closing_tag = '</li>'
|
67
|
+
when 'emoji'
|
68
|
+
result << node['attrs']['text']
|
69
|
+
else
|
70
|
+
result << "<p>Unparseable section: #{node['type']}</p>"
|
71
|
+
end
|
72
|
+
|
73
|
+
node['content']&.each do |child|
|
74
|
+
result << adf_node_to_html(child)
|
75
|
+
end
|
76
|
+
|
77
|
+
result << closing_tag if closing_tag
|
78
|
+
result
|
79
|
+
end
|
80
|
+
|
81
|
+
def adf_marks_to_html list
|
82
|
+
return [] if list.nil?
|
83
|
+
|
84
|
+
mappings = [
|
85
|
+
['strong', '<b>', '</b>'],
|
86
|
+
['code', '<code>', '</code>'],
|
87
|
+
['em', '<em>', '</em>'],
|
88
|
+
['strike', '<s>', '</s>'],
|
89
|
+
['underline', '<u>', '</u>']
|
90
|
+
]
|
91
|
+
|
92
|
+
list.filter_map do |mark|
|
93
|
+
type = mark['type']
|
94
|
+
if type == 'textColor'
|
95
|
+
color = mark['attrs']['color']
|
96
|
+
["<span style='color: #{color}'>", '</span>']
|
97
|
+
elsif type == 'link'
|
98
|
+
href = mark['attrs']['href']
|
99
|
+
title = mark['attrs']['title']
|
100
|
+
["<a href='#{href}' title='#{title}'>", '</a>']
|
101
|
+
else
|
102
|
+
line = mappings.find { |key, _open, _close| key == type }
|
103
|
+
[line[1], line[2]] if line
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def expand_account_id account_id
|
109
|
+
user = @users.find { |u| u.account_id == account_id }
|
110
|
+
text = account_id
|
111
|
+
text = "@#{user.display_name}" if user
|
112
|
+
"<span class='account_id'>#{text}</span>"
|
113
|
+
end
|
114
|
+
end
|
@@ -30,6 +30,7 @@ class ChangeItem
|
|
30
30
|
def artificial? = @artificial
|
31
31
|
def assignee? = (field == 'assignee')
|
32
32
|
def comment? = (field == 'comment')
|
33
|
+
def description? = (field == 'description')
|
33
34
|
def due_date? = (field == 'duedate')
|
34
35
|
def flagged? = (field == 'Flagged')
|
35
36
|
def issue_type? = field == 'issuetype'
|
@@ -33,6 +33,10 @@ class DailyView < ChartBase
|
|
33
33
|
result
|
34
34
|
end
|
35
35
|
|
36
|
+
def atlassian_document_format
|
37
|
+
@atlassian_document_format ||= AtlassianDocumentFormat.new(users: users)
|
38
|
+
end
|
39
|
+
|
36
40
|
def select_aging_issues
|
37
41
|
aging_issues = issues.select do |issue|
|
38
42
|
started_at, stopped_at = issue.board.cycletime.started_stopped_times(issue)
|
@@ -164,21 +168,6 @@ class DailyView < ChartBase
|
|
164
168
|
lines
|
165
169
|
end
|
166
170
|
|
167
|
-
def jira_rich_text_to_html text
|
168
|
-
text
|
169
|
-
.gsub(/{color:(#\w{6})}([^{]+){color}/, '<span style="color: \1">\2</span>') # Colours
|
170
|
-
.gsub(/\[~accountid:([^\]]+)\]/) { expand_account_id $1 } # Tagged people
|
171
|
-
.gsub(/\[([^\|]+)\|(https?[^\]]+)\]/, '<a href="\2">\1</a>') # URLs
|
172
|
-
.gsub("\n", '<br />')
|
173
|
-
end
|
174
|
-
|
175
|
-
def expand_account_id account_id
|
176
|
-
user = @users.find { |u| u.account_id == account_id }
|
177
|
-
text = account_id
|
178
|
-
text = "@#{user.display_name}" if user
|
179
|
-
"<span class='account_id'>#{text}</span>"
|
180
|
-
end
|
181
|
-
|
182
171
|
def make_history_lines issue
|
183
172
|
history = issue.changes.reverse
|
184
173
|
lines = []
|
@@ -207,8 +196,8 @@ class DailyView < ChartBase
|
|
207
196
|
end
|
208
197
|
|
209
198
|
def history_text change:, board:
|
210
|
-
if change.comment?
|
211
|
-
|
199
|
+
if change.comment? || change.description?
|
200
|
+
atlassian_document_format.to_html(change.value)
|
212
201
|
elsif change.status?
|
213
202
|
convertor = ->(id) { format_status(board.possible_statuses.find_by_id(id), board: board) }
|
214
203
|
to = convertor.call(change.value_id)
|
@@ -245,11 +234,19 @@ class DailyView < ChartBase
|
|
245
234
|
.join(' ')]]
|
246
235
|
end
|
247
236
|
|
237
|
+
def make_description_lines issue
|
238
|
+
description = issue.raw['fields']['description']
|
239
|
+
result = []
|
240
|
+
result << [atlassian_document_format.to_html(description)] if description
|
241
|
+
result
|
242
|
+
end
|
243
|
+
|
248
244
|
def assemble_issue_lines issue, child:
|
249
245
|
lines = []
|
250
246
|
lines << [make_title_line(issue)]
|
251
247
|
lines += make_parent_lines(issue) unless child
|
252
248
|
lines += make_stats_lines(issue)
|
249
|
+
lines += make_description_lines(issue)
|
253
250
|
lines += make_sprints_lines(issue)
|
254
251
|
lines += make_blocked_stalled_lines(issue)
|
255
252
|
lines += make_child_lines(issue)
|
@@ -97,30 +97,59 @@ class Downloader
|
|
97
97
|
log " JQL: #{jql}"
|
98
98
|
escaped_jql = CGI.escape jql
|
99
99
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
100
|
+
if @jira_gateway.cloud?
|
101
|
+
max_results = 5_000 # The maximum allowed by Jira
|
102
|
+
next_page_token = nil
|
103
|
+
issue_count = 0
|
104
|
+
|
105
|
+
loop do
|
106
|
+
json = @jira_gateway.call_url relative_url: '/rest/api/3/search/jql' \
|
107
|
+
"?jql=#{escaped_jql}&maxResults=#{max_results}&" \
|
108
|
+
"nextPageToken=#{next_page_token}&expand=changelog&fields=*all"
|
109
|
+
next_page_token = json['nextPageToken']
|
110
|
+
|
111
|
+
json['issues'].each do |issue_json|
|
112
|
+
issue_json['exporter'] = {
|
113
|
+
'in_initial_query' => initial_query
|
114
|
+
}
|
115
|
+
identify_other_issues_to_be_downloaded raw_issue: issue_json, board: board
|
116
|
+
file = "#{issue_json['key']}-#{board.id}.json"
|
117
|
+
|
118
|
+
@file_system.save_json(json: issue_json, filename: File.join(path, file))
|
119
|
+
issue_count += 1
|
120
|
+
end
|
121
|
+
|
122
|
+
message = " Downloaded #{issue_count} issues"
|
123
|
+
log message, both: true
|
124
|
+
|
125
|
+
break unless next_page_token
|
126
|
+
end
|
127
|
+
else
|
128
|
+
max_results = 100
|
129
|
+
start_at = 0
|
130
|
+
total = 1
|
131
|
+
while start_at < total
|
132
|
+
json = @jira_gateway.call_url relative_url: '/rest/api/2/search' \
|
133
|
+
"?jql=#{escaped_jql}&maxResults=#{max_results}&startAt=#{start_at}&expand=changelog&fields=*all"
|
134
|
+
|
135
|
+
json['issues'].each do |issue_json|
|
136
|
+
issue_json['exporter'] = {
|
137
|
+
'in_initial_query' => initial_query
|
138
|
+
}
|
139
|
+
identify_other_issues_to_be_downloaded raw_issue: issue_json, board: board
|
140
|
+
file = "#{issue_json['key']}-#{board.id}.json"
|
141
|
+
|
142
|
+
@file_system.save_json(json: issue_json, filename: File.join(path, file))
|
143
|
+
end
|
144
|
+
|
145
|
+
total = json['total'].to_i
|
146
|
+
max_results = json['maxResults']
|
147
|
+
|
148
|
+
message = " Downloaded #{start_at + 1}-#{[start_at + max_results, total].min} of #{total} issues to #{path} "
|
149
|
+
log message, both: true
|
150
|
+
|
151
|
+
start_at += json['issues'].size
|
115
152
|
end
|
116
|
-
|
117
|
-
total = json['total'].to_i
|
118
|
-
max_results = json['maxResults']
|
119
|
-
|
120
|
-
message = " Downloaded #{start_at + 1}-#{[start_at + max_results, total].min} of #{total} issues to #{path} "
|
121
|
-
log message, both: true
|
122
|
-
|
123
|
-
start_at += json['issues'].size
|
124
153
|
end
|
125
154
|
end
|
126
155
|
|
@@ -50,8 +50,8 @@
|
|
50
50
|
</head>
|
51
51
|
<body>
|
52
52
|
<noscript>
|
53
|
-
<div style="padding: 1em; background:
|
54
|
-
Javascript is currently disabled and that means that almost all of the charts in this report won't render. If you'
|
53
|
+
<div style="padding: 1em; background: red; color: white; font-size: 2em;">
|
54
|
+
Javascript is currently disabled and that means that almost all of the charts in this report won't render. If you've loaded this from a folder on SharePoint then save it locally and load it again.
|
55
55
|
</div>
|
56
56
|
</noscript>
|
57
57
|
<%= "\n" + @sections.collect { |text, type| text if type == :header }.compact.join("\n\n") %>
|
data/lib/jirametrics.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jirametrics
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.13pre2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Bowler
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-07-
|
10
|
+
date: 2025-07-18 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: random-word
|
@@ -65,6 +65,7 @@ files:
|
|
65
65
|
- lib/jirametrics/aging_work_in_progress_chart.rb
|
66
66
|
- lib/jirametrics/aging_work_table.rb
|
67
67
|
- lib/jirametrics/anonymizer.rb
|
68
|
+
- lib/jirametrics/atlassian_document_format.rb
|
68
69
|
- lib/jirametrics/blocked_stalled_change.rb
|
69
70
|
- lib/jirametrics/board.rb
|
70
71
|
- lib/jirametrics/board_column.rb
|