jirametrics 2.4 → 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 +74 -22
- data/lib/jirametrics/board_config.rb +11 -3
- 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 +42 -12
- data/lib/jirametrics/download_config.rb +27 -0
- data/lib/jirametrics/downloader.rb +185 -110
- 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 +9 -23
- data/lib/jirametrics/examples/standard_project.rb +57 -58
- data/lib/jirametrics/expedited_chart.rb +11 -10
- data/lib/jirametrics/exporter.rb +51 -14
- 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 +499 -91
- 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/rules.rb +2 -2
- data/lib/jirametrics/self_or_issue_dispatcher.rb +2 -0
- data/lib/jirametrics/settings.json +10 -2
- 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
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open3'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
class GithubGateway
|
|
7
|
+
attr_reader :repo
|
|
8
|
+
|
|
9
|
+
def initialize repo:, project_keys:, file_system:, raw_pr_cache: {}
|
|
10
|
+
@repo = repo
|
|
11
|
+
@project_keys = project_keys
|
|
12
|
+
@file_system = file_system
|
|
13
|
+
@raw_pr_cache = raw_pr_cache
|
|
14
|
+
@issue_key_pattern = build_issue_key_pattern
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def fetch_pull_requests since: nil
|
|
18
|
+
raw_prs = @raw_pr_cache[[@repo, since]] ||= fetch_raw_pull_requests(since: since)
|
|
19
|
+
raw_prs.filter_map { |pr| build_pr_data(pr) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def fetch_raw_pull_requests since: nil
|
|
23
|
+
# Note: 'commits' is intentionally excluded — including it triggers GitHub's GraphQL node
|
|
24
|
+
# limit (authors sub-connection × PRs × commits exceeds 500,000 nodes). Branch name,
|
|
25
|
+
# title, and body are sufficient for issue key extraction in the vast majority of cases.
|
|
26
|
+
json_fields = %w[number title body headRefName createdAt closedAt mergedAt
|
|
27
|
+
url state reviews additions deletions changedFiles].join(',')
|
|
28
|
+
args = ['pr', 'list', '--state', 'all', '--limit', '5000', '--json', json_fields]
|
|
29
|
+
args += ['--repo', @repo]
|
|
30
|
+
args += ['--search', "updated:>=#{since}"] if since
|
|
31
|
+
|
|
32
|
+
@file_system.log " Downloading pull requests from #{@repo}", also_write_to_stderr: true
|
|
33
|
+
run_command(args)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def build_pr_data raw_pr
|
|
37
|
+
issue_keys = extract_issue_keys(raw_pr)
|
|
38
|
+
return nil if issue_keys.empty?
|
|
39
|
+
|
|
40
|
+
PullRequest.new(raw: {
|
|
41
|
+
'number' => raw_pr['number'],
|
|
42
|
+
'repo' => @repo,
|
|
43
|
+
'url' => raw_pr['url'],
|
|
44
|
+
'title' => raw_pr['title'],
|
|
45
|
+
'branch' => raw_pr['headRefName'],
|
|
46
|
+
'opened_at' => raw_pr['createdAt'],
|
|
47
|
+
'closed_at' => raw_pr['closedAt'],
|
|
48
|
+
'merged_at' => raw_pr['mergedAt'],
|
|
49
|
+
'state' => raw_pr['state'],
|
|
50
|
+
'issue_keys' => issue_keys,
|
|
51
|
+
'reviews' => extract_reviews(raw_pr['reviews'] || []),
|
|
52
|
+
'additions' => raw_pr['additions'],
|
|
53
|
+
'deletions' => raw_pr['deletions'],
|
|
54
|
+
'changed_files' => raw_pr['changedFiles']
|
|
55
|
+
})
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def extract_issue_keys raw_pr
|
|
59
|
+
return [] if @issue_key_pattern.nil?
|
|
60
|
+
|
|
61
|
+
sources = [
|
|
62
|
+
raw_pr['headRefName'],
|
|
63
|
+
raw_pr['title'],
|
|
64
|
+
raw_pr['body']
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
keys = sources.compact.flat_map { |s| s.scan(@issue_key_pattern) }.uniq
|
|
68
|
+
return keys unless keys.empty?
|
|
69
|
+
|
|
70
|
+
commit_messages_for(raw_pr['number']).flat_map { |msg| msg.scan(@issue_key_pattern) }.uniq
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def extract_reviews raw_reviews
|
|
74
|
+
raw_reviews
|
|
75
|
+
.select { |r| %w[APPROVED CHANGES_REQUESTED].include?(r['state']) }
|
|
76
|
+
.map do |r|
|
|
77
|
+
{
|
|
78
|
+
'author' => r.dig('author', 'login'),
|
|
79
|
+
'submitted_at' => r['submittedAt'],
|
|
80
|
+
'state' => r['state']
|
|
81
|
+
}
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def commit_messages_for pr_number
|
|
88
|
+
args = ['pr', 'view', pr_number.to_s, '--json', 'commits', '--repo', @repo]
|
|
89
|
+
result = run_command(args)
|
|
90
|
+
(result['commits'] || []).flat_map do |commit|
|
|
91
|
+
[commit['messageHeadline'], commit['messageBody']].compact
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def build_issue_key_pattern
|
|
96
|
+
return nil if @project_keys.empty?
|
|
97
|
+
|
|
98
|
+
keys_pattern = @project_keys.map { |k| Regexp.escape(k) }.join('|')
|
|
99
|
+
Regexp.new("\\b(?:#{keys_pattern})-\\d+(?![A-Za-z0-9])")
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def run_command args
|
|
103
|
+
stdout, stderr, status = Open3.capture3('gh', *args)
|
|
104
|
+
|
|
105
|
+
# This extra check seems to only matter on Windows. On the mac, auth failures don't pass status.success?
|
|
106
|
+
if stderr.include?('SAML enforcement')
|
|
107
|
+
raise "GitHub CLI is not authorized to access #{@repo}. " \
|
|
108
|
+
'Run: gh auth refresh -h github.com -s read:org'
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
raise "GitHub CLI command failed for #{@repo}: #{stderr}" unless status.success?
|
|
112
|
+
|
|
113
|
+
JSON.parse(stdout)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -6,9 +6,7 @@ require 'jirametrics/grouping_rules'
|
|
|
6
6
|
module GroupableIssueChart
|
|
7
7
|
def init_configuration_block user_provided_block, &default_block
|
|
8
8
|
instance_eval(&user_provided_block)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
instance_eval(&default_block)
|
|
9
|
+
instance_eval(&default_block) unless @group_by_block
|
|
12
10
|
end
|
|
13
11
|
|
|
14
12
|
def grouping_rules &block
|
|
@@ -17,14 +15,24 @@ module GroupableIssueChart
|
|
|
17
15
|
|
|
18
16
|
def group_issues completed_issues
|
|
19
17
|
result = {}
|
|
18
|
+
ignored_issues = []
|
|
19
|
+
@issue_hints = {}
|
|
20
|
+
@issue_periods = {}
|
|
20
21
|
completed_issues.each do |issue|
|
|
21
22
|
rules = GroupingRules.new
|
|
22
23
|
@group_by_block.call(issue, rules)
|
|
23
|
-
|
|
24
|
+
if rules.ignored?
|
|
25
|
+
ignored_issues << issue
|
|
26
|
+
next
|
|
27
|
+
end
|
|
24
28
|
|
|
29
|
+
@issue_hints[issue] = rules.issue_hint
|
|
30
|
+
@issue_periods[issue] = rules.last_day_of_period
|
|
25
31
|
(result[rules] ||= []) << issue
|
|
26
32
|
end
|
|
27
33
|
|
|
34
|
+
completed_issues.reject! { |issue| ignored_issues.include? issue }
|
|
35
|
+
|
|
28
36
|
result.each_key do |rules|
|
|
29
37
|
rules.color = random_color if rules.color.nil?
|
|
30
38
|
end
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class GroupingRules < Rules
|
|
4
|
-
attr_accessor :label
|
|
5
|
-
attr_reader :color
|
|
4
|
+
attr_accessor :label, :issue_hint, :label_hint
|
|
5
|
+
attr_reader :color, :last_day_of_period
|
|
6
|
+
|
|
7
|
+
def last_day_of_period= value
|
|
8
|
+
@last_day_of_period = value.is_a?(String) ? Date.parse(value) : value
|
|
9
|
+
end
|
|
6
10
|
|
|
7
11
|
def eql? other
|
|
8
12
|
other.label == @label && other.color == @color
|
|
@@ -13,7 +17,25 @@ class GroupingRules < Rules
|
|
|
13
17
|
end
|
|
14
18
|
|
|
15
19
|
def color= color
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
if color.is_a?(Array)
|
|
21
|
+
raise ArgumentError, 'Color pair must have exactly two elements: [light_color, dark_color]' unless color.size == 2
|
|
22
|
+
raise ArgumentError, 'Color pair elements must be strings' unless color.all?(String)
|
|
23
|
+
|
|
24
|
+
if color.any? { |c| c.start_with?('--') }
|
|
25
|
+
raise ArgumentError,
|
|
26
|
+
'CSS variable references are not supported as color pair elements; use a literal color value instead'
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
light, dark = color
|
|
30
|
+
@color = RawJavascript.new(
|
|
31
|
+
"(document.documentElement.dataset.theme === 'dark' || " \
|
|
32
|
+
'(!document.documentElement.dataset.theme && ' \
|
|
33
|
+
"window.matchMedia('(prefers-color-scheme: dark)').matches)) " \
|
|
34
|
+
"? #{dark.to_json} : #{light.to_json}"
|
|
35
|
+
)
|
|
36
|
+
else
|
|
37
|
+
color = CssVariable[color] unless color.is_a?(CssVariable)
|
|
38
|
+
@color = color
|
|
39
|
+
end
|
|
18
40
|
end
|
|
19
41
|
end
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
<%= seam_start %>
|
|
1
2
|
<div class="chart">
|
|
2
3
|
<canvas id="<%= chart_id %>" width="<%= canvas_width %>" height="<%= canvas_height %>"></canvas>
|
|
3
4
|
</div>
|
|
@@ -15,11 +16,9 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'),
|
|
|
15
16
|
x: {
|
|
16
17
|
type: 'time',
|
|
17
18
|
min: '<%= @date_range.begin.to_s %>',
|
|
18
|
-
max: '<%= (@date_range.end ).to_s %>',
|
|
19
|
+
max: '<%= (@date_range.end + 1).to_s %>',
|
|
19
20
|
stacked: false,
|
|
20
|
-
|
|
21
|
-
display: false
|
|
22
|
-
},
|
|
21
|
+
<%= render_axis_title :x %>
|
|
23
22
|
grid: {
|
|
24
23
|
color: <%= CssVariable['--grid-line-color'].to_json %>
|
|
25
24
|
},
|
|
@@ -30,6 +29,7 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'),
|
|
|
30
29
|
ticks: {
|
|
31
30
|
display: true
|
|
32
31
|
},
|
|
32
|
+
<%= render_axis_title :y %>
|
|
33
33
|
grid: {
|
|
34
34
|
color: <%= CssVariable['--grid-line-color'].to_json %>
|
|
35
35
|
},
|
|
@@ -38,22 +38,13 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'),
|
|
|
38
38
|
plugins: {
|
|
39
39
|
annotation: {
|
|
40
40
|
annotations: {
|
|
41
|
-
|
|
42
|
-
holiday<%= index %>: {
|
|
43
|
-
drawTime: 'beforeDraw',
|
|
44
|
-
type: 'box',
|
|
45
|
-
xMin: '<%= range.begin %>T00:00:00',
|
|
46
|
-
xMax: '<%= range.end %>T23:59:59',
|
|
47
|
-
backgroundColor: <%= CssVariable.new('--non-working-days-color').to_json %>,
|
|
48
|
-
borderColor: <%= CssVariable.new('--non-working-days-color').to_json %>
|
|
49
|
-
},
|
|
50
|
-
<% end %>
|
|
41
|
+
<%= working_days_annotation %>
|
|
51
42
|
|
|
52
43
|
<% if percentage_line_x %>
|
|
53
44
|
line: {
|
|
54
45
|
type: 'line',
|
|
55
|
-
|
|
56
|
-
|
|
46
|
+
scaleID: 'x',
|
|
47
|
+
value: '<%= percentage_line_x %>',
|
|
57
48
|
borderColor: <%= CssVariable.new('--aging-work-bar-chart-percentage-line-color').to_json %>,
|
|
58
49
|
borderWidth: 1,
|
|
59
50
|
drawTime: 'afterDraw'
|
|
@@ -75,4 +66,4 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'),
|
|
|
75
66
|
}
|
|
76
67
|
});
|
|
77
68
|
</script>
|
|
78
|
-
|
|
69
|
+
<%= seam_end %>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
<%= seam_start %>
|
|
1
2
|
<div class="chart">
|
|
2
3
|
<canvas id="<%= chart_id %>" width="<%= canvas_width %>" height="<%= canvas_height %>"></canvas>
|
|
3
4
|
</div>
|
|
@@ -6,7 +7,7 @@ new Chart(document.getElementById(<%= chart_id.inspect %>).getContext('2d'),
|
|
|
6
7
|
{
|
|
7
8
|
type: 'bar',
|
|
8
9
|
data: {
|
|
9
|
-
labels: [<%=
|
|
10
|
+
labels: [<%= @board_columns.collect { |c| c.name.inspect }.join(',') %>],
|
|
10
11
|
datasets: <%= JSON.generate(data_sets) %>
|
|
11
12
|
},
|
|
12
13
|
options: {
|
|
@@ -22,8 +23,10 @@ new Chart(document.getElementById(<%= chart_id.inspect %>).getContext('2d'),
|
|
|
22
23
|
labelString: 'Date Completed'
|
|
23
24
|
},
|
|
24
25
|
grid: {
|
|
25
|
-
color: <%= CssVariable['--grid-line-color'].to_json
|
|
26
|
+
color: <%= CssVariable['--grid-line-color'].to_json %>,
|
|
27
|
+
z: 1 // draw the grid lines on top of the bars
|
|
26
28
|
},
|
|
29
|
+
stacked: true
|
|
27
30
|
},
|
|
28
31
|
y: {
|
|
29
32
|
scaleLabel: {
|
|
@@ -35,8 +38,11 @@ new Chart(document.getElementById(<%= chart_id.inspect %>).getContext('2d'),
|
|
|
35
38
|
text: 'Age in days'
|
|
36
39
|
},
|
|
37
40
|
grid: {
|
|
38
|
-
color: <%= CssVariable['--grid-line-color'].to_json
|
|
41
|
+
color: <%= CssVariable['--grid-line-color'].to_json %>,
|
|
42
|
+
z: 1 // draw the grid lines on top of the bars
|
|
39
43
|
},
|
|
44
|
+
stacked: false,
|
|
45
|
+
max: <%= (@max_age * 1.1).to_i %>
|
|
40
46
|
}
|
|
41
47
|
},
|
|
42
48
|
plugins: {
|
|
@@ -44,15 +50,28 @@ new Chart(document.getElementById(<%= chart_id.inspect %>).getContext('2d'),
|
|
|
44
50
|
callbacks: {
|
|
45
51
|
label: function(context) {
|
|
46
52
|
if( typeof(context.dataset.data[context.dataIndex]) == "number" ) {
|
|
47
|
-
|
|
53
|
+
let full_data = <%= @bar_data.inspect %>;
|
|
54
|
+
let columnIndex = context.dataIndex;
|
|
55
|
+
let rowIndex = context.datasetIndex - <%= @row_index_offset %>;
|
|
56
|
+
return context.dataset.label + " of completed work items left this column in " +full_data[rowIndex][columnIndex] + " days or less";
|
|
48
57
|
}
|
|
49
58
|
else {
|
|
50
|
-
return context.dataset.data[context.dataIndex].title
|
|
59
|
+
return context.dataset.data[context.dataIndex].title;
|
|
51
60
|
}
|
|
52
61
|
}
|
|
53
62
|
}
|
|
63
|
+
},
|
|
64
|
+
legend: {
|
|
65
|
+
labels: {
|
|
66
|
+
filter: function(item, chart) {
|
|
67
|
+
// Logic to remove a particular legend item goes here
|
|
68
|
+
return !item.text.includes('%');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
54
71
|
}
|
|
72
|
+
|
|
55
73
|
}
|
|
56
74
|
}
|
|
57
75
|
});
|
|
58
76
|
</script>
|
|
77
|
+
<%= seam_end %>
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
<%= seam_start %>
|
|
1
2
|
<table class='standard'>
|
|
2
3
|
<thead>
|
|
3
4
|
<tr>
|
|
4
|
-
<th
|
|
5
|
-
<th>E</th>
|
|
6
|
-
<th>B</th>
|
|
5
|
+
<th title="Age in days">Age</th>
|
|
6
|
+
<th title="Expedited">E</th>
|
|
7
|
+
<th title="Blocked / Stalled">B/S</th>
|
|
8
|
+
<th title="Priority">P</th>
|
|
7
9
|
<th>Issue</th>
|
|
8
10
|
<th>Status</th>
|
|
11
|
+
<th>Forecast</th>
|
|
9
12
|
<th>Fix versions</th>
|
|
10
13
|
<% if any_scrum_boards %>
|
|
11
14
|
<th>Sprints</th>
|
|
@@ -28,6 +31,7 @@
|
|
|
28
31
|
<td style="text-align: right;"><%= issue_age || 'Not started' %></td>
|
|
29
32
|
<td><%= expedited_text(issue) %></td>
|
|
30
33
|
<td><%= blocked_text(issue) %></td>
|
|
34
|
+
<td><%= priority_text(issue) %></td>
|
|
31
35
|
<td>
|
|
32
36
|
<% parent_hierarchy(issue).each_with_index do |parent, index| %>
|
|
33
37
|
<% color = parent != issue ? "var(--hierarchy-table-inactive-item-text-color)" : 'var(--default-text-color)' %>
|
|
@@ -37,10 +41,14 @@
|
|
|
37
41
|
<%= link_to_issue parent, style: "color: #{color}" %>
|
|
38
42
|
</span>
|
|
39
43
|
<i><%= parent.summary.strip.inspect %></i>
|
|
44
|
+
<% if parent == issue && (text = not_visible_text(issue)) %>
|
|
45
|
+
<br /><%= text %>
|
|
46
|
+
<% end %>
|
|
40
47
|
</div>
|
|
41
48
|
<% end %>
|
|
42
49
|
</td>
|
|
43
|
-
<td><%= format_status issue.status
|
|
50
|
+
<td><%= format_status issue.status, board: issue.board %></td>
|
|
51
|
+
<td><%= dates_text(issue) %></td>
|
|
44
52
|
<td><%= fix_versions_text(issue) %></td>
|
|
45
53
|
<% if any_scrum_boards %>
|
|
46
54
|
<td><%= sprints_text(issue) %></td>
|
|
@@ -50,3 +58,4 @@
|
|
|
50
58
|
<% end %>
|
|
51
59
|
</tbody>
|
|
52
60
|
</table>
|
|
61
|
+
<%= seam_end %>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
<table class='standard' id='<%= issues_id %>'
|
|
1
|
+
<div class='foldable startFolded'>Show details</div>
|
|
2
|
+
<table class='standard' id='<%= issues_id %>'>
|
|
3
3
|
<thead>
|
|
4
4
|
<tr>
|
|
5
5
|
<th>Issue</th>
|